ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [NSHipster - iOS] Enumeration,Iteration,Traverse.. 반복문의 퍼포먼스에 대해 알아보자
    앱등이에게 살충제를 뿌린다./iOS 2016. 1. 22. 22:56



    <유럽여행 가고싶다 - traverse>



    여러 기법의 프로그래밍에서 반복문은 항상 성능상의 이슈를 데리고 다닙니다.


    절차적/구조적 : Loop내에서 포인터를 증가시킵니다.

    객체지향 : 컬렉션 내부의 각각의 객체에 대한 함수나 메소드를 실행시킵니다.

    함수형 : 재귀와 자료구조를 통해서 작동시킵니다.


    Objective-C에서는 C기반의 절차지향과 Smalltalk기반의 객체지향의 특징을 모두 갖고 있습니다. 아래에서 그 방법들에 대해 설명합니다.


    1. C Loops(for/while)

    for문과 while문 정말 고전적일 정도로 익숙한 방법이죠. 아래와 같습니다.


    이러한 C기반의 반복문을 사용하신분들은 알겠지만, 사소한 실수에도 예외가 발생합니다. **참고로 off-by-one errors라고도 합니다.

    주로 OutOfBoundaryExceptoin이 자주 발생하겠죠..

    다행히도 Smalltalk에서는 list comprehension이라는 개념을 도입해 이런 문제를 해결했습니다. 요즘에 for-in으로 더 잘 알려져있죠.



    2. List Comprehension(for/in)

    추상화(프로토콜)를 이용하여 컬렉션의 모든 요소를 이터레이션할 수 있게 되었습니다.. 에러가 현저히 줄어들 뿐만 아니라, 타이핑 할 양도 줄어들게 되죠.


    Cocoa에서는 NSFastEnumeration프로토콜을 구현하는 객체에 대해서 이 방법을 적용시킬 수 있습니다. 

    **NSArray, NSSet, NSDictionary같은 경우도 이 프로토콜을 구현해놓았기 때문에 사용가능한거죠.


    <NSFastEnumeration>

    NSFastEnumeration은 한 개의 메소드를 선언하고 있습니다.

    • state : 순회에 사용할 컨텍스트 정보에 해당합니다. 그리고 컬렉션이 이터레이션 도중에 변경되지 않았음을 보증해주어야 합니다. (아래에 설명)
    • stackbuf : 순회할 객체를 담아두는  C 기반 배열입니다. 
    • len : stackbuf에 리턴할 최대 객체 수

    겉보기엔 굉장히 복잡해 보이는 메소드입니다. 파라미터들에 대해서 좀 자세히 알아보겠습니다.


    <NSFastEnumerationState>

    • state : 현재 어디까지 이터레이션을 했는지에 대한 정보를 나타냅니다. 메소드가 처음 실행될 때는 0입니다.
    • itemsPtr : 객체를 담는 C배열
    • mutationsPtr : 컬렉션 내부에 값이 변했는지 변하지 않았는지를 확인하는 상태 값
    • extra : 리턴 값들을 담아두는데 사용할 C배열
    (뭔소리지 몰라서 원본첨부)
    • state: Arbitrary state information used by the iterator. Typically this is set to 0 at the beginning of the iteration. 
    • itemsPtr: A C array of objects. 
    • mutationsPtr: Arbitrary state information used to detect whether the collection has been mutated. 
    • extra: A C array that you can use to hold returned values.

    이 메소드에 대해서 깊이 파고든 Mike Ash has a fantastic blog post를 참조하도록 해주세요,


    어쨋든 확실히 알고 넘어가야할 점은 NSFastEnumeration이 빠르다는 겁니다. 최소한 여러분이 작성한 for문보다는 빠를겁니다. 어떤 비밀이 담겨 있는걸까요?

    countByEnumeratingWithState:objects:count: 메소드를 통해 컬렉션의 멤버들을 버퍼에 담습니다. 이를 통해 for문이 Single스레드에서 작동하는 것과는 달리, 객체들이 동시에 로드되게 됩니다. 사용가능한 시스템자원으로 병렬작업을 하기 때문에 더 나은 퍼포먼스를 제공하게 되는겁니다.


    애플은 NSFastEnumeration for-in을 사용하기를 강력히 권장하고 있습니다. 실제로 쓰기도 쉽고 퍼포먼스도 좋습니다. 쓰세요!!



    3. NSEnumerator

    NSFastEnumeration이전에도 꽤나 훌륭햔 NSEnumerator도 있었습니다. NSEnumerator는 두개의 메소드를 선언하고 있는 클래스입니다.


    nextObject는 컬렉션에 있는 다음 객체를 리턴하게 됩니다. 만약 없다면 nil을 리턴하게 되구요. 

    allObjects는 남아있는 모든 객체를 리턴합니다. NSEnumerator는 앞으로만 갈 수 있으며, 한 칸씩만 갈 수 있습니다.


    컬렉션의 모든 객체를 순회하려면 아래와 같이 구현할 수 있습니다.


    또는 NSEnumerator가 NSFastEnumeration 프로토콜을 따르기 때문에 아래와 같이 사용하기도 합니다.


    만약 여러분의 클래스(컬렉션을 상속받지 않는)에서 fast enumeration을 구현할 편한 방법을 찾고 계신다면 복잡한 NSFastEnumeration을 구현하는 것보다 NSEnumerator가 적절한 선택이 될 것 같습니다.



    4. Enumerate With Blocks

    Block의 등장으로 컬렉션을 순회하는데에도 Block을 사용할 수 있게 되었습니다.


    NSArray, NSSet, NSDictionary, NSIndexSet과 같은 컬렉션들은 위와 유사한 block enumeration메소드들을 모두 갖고 있습니다.

    이 방식의 장점은 객체를 순회할 때 객체에 해당하는 index를 같이 사용할 수 있다는 것입니다. BOOL포인터는 이터레이션을 빠르게 종료시킬 수 있는 기능을 제공합니다. break문과 같은 역할이라고 보시면 됩니다.


    만약 index를 통해서 객체의 순서와 관련된 작업을 하실게 아니라면 for/in NSFastEnumeration을 사용하는게 더 빠릅니다.

    마지막으로 기억하셔야 할 options파라미터에 대해서 알아봅시다.


    <NSEnumerationOptions>

    • NSEnumerationConcurrent : Block을 통한 순회가 병렬로 이루어질 것인지를 나타냅니다. 순회하게 되는 순서에 대해서는 장담할 수 없고 정의되지도 않습니다. 이 플래그 값은 힌트일 뿐이며, 특정 상황에서는 무시될 수도 있습니다. (Block에 사용되는 코드는 병렬처리에 있어서 안전한 코드여야합니다.)
    • NSEnumerationReverse : 순회가 거꾸로 이루어 져야할지를 나타내냅니다. 이 옵션은 NSArray와 NSIndexSet클래스에서 사용가능합니다. NSDictionary와 NSSet클래스에서의 작동은 정의되어 있지 않습니다.
    다시 한번,, Fast enumeration이 거의 대부분의 경우에 block enumeration보다 빠릅니다. 하지만 block을 사용한 순회가 유용한 순간이 분명 있을 것입니다.



    Objective-C와 Cocoa의 이터레이션/순회 방법들에 대해 알아보았습니다. 흥미로운 점은 추상클래스의 파워를 새삼 느낄 수 있었다는 것입니다. High level의 추상화는 쓰고 이해하기 쉬울 뿐만 아니라 "hard way"에 비해 훨씬 좋은 퍼포먼스를 낼 수 있습니다.



    출처 : http://nshipster.com/enumerators/

Designed by Tistory.