[Swift] 컬렉션 타입에 고차함수를 쓸 때 지켜야 할 것

Mason Kim·2022년 12월 30일
0

swift

목록 보기
2/7

이 글은 Swift Lee 의 글을 번역 한 글입니다!
출처: https://www.avanderlee.com/swift/performance-collections/

  • Swift 는 함수형 프로그래밍을 지향하고 고차함수 사용을 권장한다.
  • 하지만 불필요한 상황에서의 filter, map 과 같은 고차함수의 사용은 성능에 문제를 야기시킬 수 있다
  • 같은 결과값을 도출하기 위함이라 해도, 성능 향상을 위한 더 좋은 접근 방식이 있기 마련!
    SwiftLee 의 글을 한글로 번역해 정리해보자 :)
  • 컬렉션 타입에 대한 글이긴 하지만, 편의상 "배열"로 지칭 해 부르겠습니다!

특정 요소를 포함하고 있는지 체크할 땐 .contains 로

  • first(where:) != nil 대신에 contains 를 사용하자

  • 여러 방법이 있지만 최상의 성능을 위해서 contains 를 사용하자

  • ✅ Best practice
    let numbers = [0, 1, 2, 3]
    numbers.contains(1)
  • ❌ Bad practice
    let numbers = [0, 1, 2, 3]
    numbers.filter { number in number == 1 }.isEmpty == false
    numbers.first(where: { number in number == 1 }) != nil

빈 배열인지 확인할 땐 .isEmpty 로

  • 배열의 count 가 0인지 비교하는 대신  isEmpty 인지 확인하자

  • 이유를 설명하는 가장 좋은 방법은 isEmpty 문서 를 인용하는 것 입니다.

    [****Discussion]*** 컬렉션이 비어 있는지 확인해야 하는 경우, count 속성이 0인지 확인하는 대신 isEmpty 속성을 사용하십시오. RandomAccessCollection을 준수하는 컬렉션이 아니라면 count 속성에 액세스하면 컬렉션의 요소를 반복(iterate)합니다.*

    • isEmpty 는 항상 복잡도가 O(1) 이고, countO(n)이 될 수 있다. (RandomAccessCollection프로토콜을 준수하지 않는 경우)
  • ✅ Best practice

    let numbers = []
    numbers.isEmpty
  • ❌ Bad practice

    let numbers = []
    numbers.count == 0

빈 문자열도 .isEmpty

  • Swift 에서 String 은 Character 의 Collection 으로 볼 수도 있기에 이유는 위와 동일함!
  • ✅ Best practice
    myString.isEmpty
  • ❌ Bad practice
    myString == ""
    myString.count == 0

조건과 일치하는 첫 번째 (.first) 요소를 가져오자

  • .first(where:) 은 첫 번째 요소를 찾는 즉시 순회를 중지하지만,
    후자의 방식은 .filter 로 모든 요소를 순회하고, .first 를 찾아오기에 훨씬 비효율적일 수 있음
  • ✅ Best practice
    let numbers = [3, 7, 4, -2, 9, -6, 10, 1]
    let firstNegative = numbers.first(where: { $0 < 0 })
  • ❌ Bad practice
    let numbers = [3, 7, 4, -2, 9, -6, 10, 1]
    let firstNegative = numbers.filter { $0 < 0 }.first

컬렉션의 최소 / 최대 요소는 바로 가져오자

  • 전체 배열을 정렬하는 sorted() 는 복잡도가 O(*n* log *n*) 인 반면에,
    .min() / .max() 는 복잡도가 O(*n*) 이므로 더 효율적인 작업이다.
  • ✅ Best practice
    let numbers = [0, 4, 2, 8]
    let minNumber = numbers.min()
    let maxNumber = numbers.max()
  • ❌ Bad practice
    let numbers = [0, 4, 2, 8]
    let minNumber = numbers.sorted().first
    let maxNumber = numbers.sorted().last

컬렉션의 모든 요소의 조건을 확인할 때

  • .allSatisfy 를 사용하자

  • 컬렉션의 모든 요소가 특정 조건에 부합하는지 확인하기 위해서,
    .filter 로 특정 조건이 아닌 요소를 찾고 .isEmpty 를 통해서 조건이 아닌 요소가 없다. 를 확인하곤 하는데

  • Swift 4.2 에서 도입된 .allSatisfy 를 사용하는 것이 낫다.

    🤔 왜 더 나을까? .allSatisfty 도 배열의 한 요소씩 순회하며 체크하고 O(n) 의 복잡도를 갖는데…
    → filter 는 확인 후, 배열을 리턴 하고 또 isEmpty 로 체크하지만 allSatisfy 는 확인만 하면 끝이니까?

    • This approach is far more readable than the previous option, and anyone reading this code can immediately see the exact requirements that will cause this function to return true.
    • 훨씬 읽기 쉽고, Bool 을 리턴할 것이 더 명시적이어진다!
  • ✅ Best practice

    let numbers = [0, 2, 4, 6]
    let allEven = numbers.allSatisfy { $0 % 2 == 0 }
  • ❌ Bad practice

    let numbers = [0, 2, 4, 6]
    let allEven = numbers.filter { $0 % 2 != 0 }.isEmpty

코드가 모범 사례인지 확인하기 위해 SwiftLint 를 사용하자

  • 위의 이러한 모범 사례 (Best Practices) 들은 대부분 SwiftLint 에 구현되어 있다
  • 모범 사례에 대한 코드 컨벤션을 지키기 쉽게 하는 라이브러리인 SwiftLint 사용을 고려해보자!
profile
iOS developer

0개의 댓글