[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 를 활용하여 병렬로 열거를 할 수 있게 한다.
이는 다른 열거 방법으로는 하기 어려운 일이다.
-
만약 알고 있다면 블록 시그너처에서 정확한 객체 타입을 가리키도록 수정하라.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Effective Objective-C] #49 커스텀 메모리 관리 시맨틱을 가진 컬렉션을 만들기 위해 무비용 전환을 사용하라 (0) | 2017.10.17 |
---|---|
[Effective Objective-C] 목차와 요약을 통해 한 눈에 알아보는 Effective Objective-C #41 ~ #48 (0) | 2017.10.16 |
[Effective Objective-C] #47 시스템 프레임워크를 숙지하라 (0) | 2017.10.14 |
[Effective Objective-C] #46 dispatch_get_current_queue 사용을 피하라 (0) | 2017.10.13 |
[ios] permission category (0) | 2017.10.12 |
댓글