본문 바로가기
프로그래밍 놀이터/iOS

[Effective Objective-C] #48 반복문에는 블록 열거를 사용하라

by 돼지왕 왕돼지 2017. 10. 15.
반응형

 [Effective Objective-C] #48 반복문에는 블록 열거를 사용하라


출처 : Effective Objective-C


-

최신 오브젝티브-C 에는 열거하는 방법이 많다.

표준 C 반복문부터 오브젝티브-C 1.0의 NSEnumerator, 그리고 오브젝티브-2.0 의 빠른 열거자(fast enumeration)도 있다.



for 루프


-

컬렉션을 열거하는 첫 번째 메서드는 훌륭하고 오래된 방법인 for 루프다.

NSArray *anArray = …;

for (int i=0; i < anArray.count; i++){

     id object = anArray[i];

     // do something

}


이 방법은 쓸만하지만 사전이나 집합을 반복하면 훨씬 복잡해진다.

NSDictionary *aDictionary = …;

NSArray *keys = [aDictionary allKeys];

for( int i=0; i < keys.count; i++ ){

     id key = keys[i];

     id value = aDictionary[key];

     // do something

}


NSSet *aSet = …;

NSArray *objects = [aSet allObjects];

for( int i=0; i < objects.count; i++ ){

     id object = objects[i];

     // do something

}


정의에 의하면 사전과 집합은 정렬되어 있지 않다고 되어 있다.

그래서 특정 정수 인덱스로 값에 직접 접근할 수 없다.

그렇기 때문에 사전의 모든 키에 대해 요청을 하거나 집합의 모든 객체에 대해 요청을 해야 한다.

두 경우 모두 정렬되어 있는 배열을 반환받아 배열의 값에 각각 접근하는 대신 그 배열을 열거할 수 있다.

이런 추가 배열을 생성하는 것은 추가적인 일일 뿐 아니라 컬렉션의 객체를 리테인하는 추가 객체까지 생성한다.

나머지 모든 열거 기술은 이런 추가 임시 배열을 생성하는 것을 최대한 줄여준다.




NSEnumerator 를 사용하는 오브젝티브-C 1.0 열거


-

NSEnumerator 객체는 concrete subclass 가 구현해야 할 메서드 두 개를 정의한 추상 기본 클래스(abstract base class)이다.

-(NSArray*)allObjects

-(id)nextObject



-

핵심 메서드는 nextObject 다.

이 메서드는 열거형에서 다음 순서의 객체(next object)를 반환한다.

메서드가 호출될 때마다 다음번 호출에는 그 다음 순서의 객체가 반환될 수 있도록 내부 데이터 구조를 갱신한다.

열거형의 모든 객체가 반환된 후에는 열거형의 마지막을 뜻하는 nil 이 반환된다.

NSArray *anArray = …;

NSEnumerator *enumerator = [anArray objectEnumerator];

id object;

while( (object = [enumerator nextObject]) != nil ){

     // do something

}


이 방법의 유일한 장점은 어떤 컬렉션을 열거하더라도 비슷한 문법으로 할 수 있다는 것이다.

NSDictionary *aDictionary = …;

NSEnumerator *enumerator = [aDictionary keyEnumerator];

id key;

while( (key = [enumerator nextObject]) != nil ){

     id value = aDictionary[key];

     // do something

}


NSSet *aSet = …;

NSEnumerator *enumerator = [aSet objectEnumerator];

id object;

while( (object = [enumerator nextObject]) != nil ){

     // do something

}



-

NSEnumerator 의 또 다른 장점은 여러 종류의 열거형을 사용할 수 있는 것이다.

예를 들어 배열의 경우 역열거자(reverse enumerator)라는 것이 있다.

이를 사용하면 컬렉션을 거꾸로 열거할 수 있다.

[anArray reverseObjectEnumerator];




빠른 열거


-

빠른 열거는 오브젝티브-C 2.0에서 새로 나온 기능이다.

빠른 열거는 NSEnumerator 를 사용해 열거하는 것과 비슷하다.

하지만 for 루프 문법에 새로운 in 키워드를 추가하여 문법이 훨씬 간결해졌다.

이 키워드 덕분에 배열과 같은 컬렉션을 열거하는 문법은 매우 간결해졌다.

NSArray *anArray = …;

for (id object in anArray ){

      // do something

}


이는 NSFastEnumeration 프로토콜을 이용해 동작한다.

빠른 열거를 지원한다는 것을 표시하기 원하는 객체는 이 프로토콜을 따르면 된다.

이 프로토콜은 한 개의 메서드만 정의되어 있다.

-(NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState*)state objects:(ud*)stackbuffer count:(NSUInteger)length



-

사전과 집합을 열거하는 것이 다음처럼 간단해진다.

NSDictionary *aDictionary = …;

for( id key in aDictionary ){

     id value = aDictionary[key];

     // do something

}


NSSet *aSet = …;

for( id object in aSet ){

     // do something

}



-

거꾸로 열거하는 것도 NSFastEnumeration 을 구현한 NSEnumerator 객체로 할 수 있다.

for ( id object in [anArray reverseObjectEnumerator] ){

     // do something

}



-

이 열거 메서드는 지금까지의 열거하는 방법 중 문법과 효율 면에서 최고의 방법이다.

그러나 사전을 열거할 때 특히 키와 값을 동시에 열거해야 한다면 여전히 추가 과정이 필요하다.

도한 열거형의 인덱스는 전통적인 for 루프에 비해 쉽게 접근할 수 없다.

많은 알고리즘이 인덱스를 사용하기 때문에 반복 도중에 인덱스가 필요할 때가 있다.






블록 기반 열거


블록 기반 메서드는 최신 오브젝티브-C 에서 사용할 수 있는 마지막 열거형 종류다.

배열을 열거하는 가장 기본적인 메서드는 다음과 같다.

NSArray 에 정의되어 있다.

-(void)enumerateObjectsUsingBlock:(void(^)(id object, NSUInteger idx, BOOL *stop))block


이 메서드군의 나머지 메서드들은 열거를 제어할 수 있는 여러 가지 옵션을 가질 수 있다.



-

배열과 집합에서 반복마다 수행될 블록은 현재 반복 차수에 해당하는 객체(object)와 해당 차수의 인덱스(idx)와 불린을 가리키는 포인터를 파라미터로 받는다.

처음 두 파라미터는 파라미터 이름에서 의미를 알 수 있다.

세번째 파라미터 stop 은 열거를 중단하는 방법을 제공한다.

NSArray *anArray = …;

[anArray enumerateObjectsUsingBlock: ^(id object, NSUInteger idx, BOOL *stop){

     // do something

     if ( shouldStop ){

          *stop = YES;

     }

}];



-

NSSet 에도 동일한 블록 열거(block enumeration) 메서드가 있다.

그리고 NSDictionary 의 것은 약간 다르다.

-(void)enumerateKeysAndObjectsUsingBlock(void (^)(id key, id object, BOOL *stop))block


NSDictionary *aDictionary = …;

[aDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop){

     // do something

     if ( shouldStop ){

          *stop = YES;

     }

}];


NSSet *aSet = …;

[aSet enumerateObjectsUsingBlock:^(id object, BOOL *stop){

     // do something

     if ( shouldStop ){

          *stop = YES;

     }

}];


여기서 큰 장점은 블록 내에서 더 많은 정보를 직접 얻을 수 있다는 것이다.

배열의 경우 열거의 인덱스를 얻을 수 있다.

정렬된 집합(NSOrderedSet)도 마찬가지다.

사전의 경우 추가 작업 없이 키와 값을 동시에 얻을 수 있다.

그렇게 함으로써 주어진 키에 대한 값을 얻기 위해 필요한 추가적인 메서드 호출을 아낄 수 있다.

대신에 사전은 두 값(즉 키와 값)을 동시에 줄 수 있다.

이는 훨씬 효율적이다.



-

또 다른 이점은 캐스팅을 할 필요가 없게 하기 위해 블록의 메서드 시그너처를 변경할 수 있다는 것이다.

사실 캐스팅을 블록 메서드 시그너처로 밀어 넣은 것이다.



-

거꾸로 반복하는 능력이 사라진 건 아니다.

배열, 사전, 집합 모두 이전 메서드의 변종들을 구현하고 있다.

이 메서드에는 추가로 option 이라는 마스크(mask) 파라미터(OR 비트 연산으로 한 개의 하라미터를 이용해 여러 가지 값을 동시에 전달할 수 있는 파라미터)를 전달할 수 있다.

-(void)enumerateObjectsWithOptions:(NSEnumerationOptions)options usingBlock:(void(^)(id obj, NSUInteger idx, BOOL *stop))block


-(void)enumerateKeysAndObjectsWithOptions:(NSEnumerationOptions)options usingBlock:(void(^)(id key, id obj, BOOL *stop))block


NSEnumerationOptions 타입은 OR 비트 연산을 할 수 있는 enum 값이다.

이 값은 열거형이 어떻게 행동해야 하는지 나타낸다.



-

반복이 동시에 동작하도록 요청할 수 있다.

각 반복의 블록이 현재 시스템 리소스로 가능하다면 병렬로 실행되는 것을 의미한다.

이는 NSEnumerationConcurrent 옵션을 이용해 할 수 있다.



-

거꾸로 반복하는 것에 대한 요청은 NSEnumerationReverse 옵션으로 할 수 있다.

이는 배열과 정렬된 집합 같이 거꾸로 열거하는 게 말이 되는 상황에서만 가능함을 명심하라.



-

종합하면 블록 열거형은 나머지 메서드의 장점을 모두 합한 것보다 더 많은 장점이 있다.

이는 빠른 열거보다는 좀 더 장황하지만 열거의 인덱스를 얻을 수 있는 이점이 있고

사전 열거에서는 키, 값을 동시에 얻을 수 있는 이점, 그리고 동시에 여러 반복을 수행할 수 있는 이점이 있어 추가적인 코드를 작성하더라도 충분히 감내할 수 있다.




기억할 점


-

컬렉션을 열거하는 방법은 네 가지가 있다.

for 루프는 가장 기초적인 방법이다.

그 다음은 NSEnumerator 와 빠른 열거를 사용해 열거하는 것이다.

가장 현대적이고 진보된 방법은 블록 열거 메서드를 사용하는 방법이다.



-

블록 열거는 추가 코드 없이 GCD 를 활용하여 병렬로 열거를 할 수 있게 한다.

이는 다른 열거 방법으로는 하기 어려운 일이다.



-

만약 알고 있다면 블록 시그너처에서 정확한 객체 타입을 가리키도록 수정하라.




반응형

댓글