^(코딩캣)^ = @"코딩"하는 고양이;

[번역글] Objectiv-C 블록(block)으로 작업하기(3) [完]

Language/Objective-C & Swift
2021. 9. 22. 15:07

이 글은 애플 측 개발자 문서인 Working with Blocks를 바탕으로 작성한 것입니다. Swift의 ‘클로저(closure)’에 대응하는 Objective-C의 ‘블록(block)’에 대한 내용입니다.

Objectiv-C 블록(block)으로 작업하기

Objective-C 클래스는 데이터 및 이에 관계된 행동으로 구성된 객체를 정의합니다. 때때로 Objective-C에서는 일련의 메소드들보다는 하나의 작업 또는 단위 행동을 표현하는 다른 방법을 찾는 것이 더 나은 경우도 있습니다. 블록(block)은 C, Objective-C 및 C++에서 마치 변수처럼 다른 곳으로 어떤 메소드나 함수 조각을 전달하기 위한 언어 수준에서 정의된 기능입니다. 블록은 Objective-C에서 객체인데 이는 NSArrayNSDictionary 같은 콜렉션으로 추가될 수 있음을 뜻합니다. 블록은 또한 자신을 감싸고 있는 스코프에서 값을 캡쳐할 수 있는 능력을 가지고 있는데 이는 타 프로그래밍 언어에서 ‘클로저(closure)’나 ‘람다(lambda)’와 비슷합니다. 이 장에서는 블록을 선언하고 참조하는 구문에 대해 설명하고 콜렉션 열거와 같은 통상적인 작업에서 블록을 사용하는 방법에 대해 보일 것입니다.

  1. 블록 기본 구문; 블록의 매개변수 및 반환 형식
  2. 블록의 스코프 캡쳐와 strong 순환 참조 해결
  3. 블록 응용 예

 

블록은 열거 작업을 간단하게 할 수 있다.

completion handler 뿐만 아니라 많은 Cocoa 및 Cocoa Touch API는 공통의 작업을 단순화시키는 데 블록 구문을 쓰기도 합니다. 예를 들어 콜렉션 열거와 같은 작업들이 있습니다. NSArray 클래스는 세 가지의 블록 기반 메소드를 가지고 있습니다. 예를 들어,

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

이 메소드는 한 개의 매개변수를 받는데 이는 배열 내 각 원소에 도달할 때마다 호출될 블록입니다.

// Objective-C
NSArray * array = /* ... */;
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
    NSLog(@"array[%lu] = \"%@\"", idx, obj);
}];

블록 자체는 세 개의 매개변수를 받습니다. 그 중 처음 두 개는 현재 참조된 객체 및 배열에서 그 원소의 인덱스입니다. 세 번째 매개변수는 boolean 변수에 대한 포인터로서, 열거 작업을 중지시키고 싶을 때 사용될 수 있습니다. 예를 들어 다음과 같습니다.

// Objective-C
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
    if (/* ... */) {
        *step = YES;
    }
}

물론 enumerateObjectsWithOptions:usingBlock: 메소드를 사용하여 열거 작업에 대한 옵션을 지정할 수도 있는데 예를 들어, NSEnumerationReverse를 사용하면 배열에서 각 원소들을 역순으로 참조합니다.

열거 작업을 하는 블록이 processor-intensive 하면서 동시 실행을 해도 안전한 경우라면 NSEnumerationConcurrent 옵션을 사용할 수도 있습니다.

// Objective-C
[array enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL * stop) {
    // ...
}

이 플래그를 지정할 경우 블록의 실행이 여러 쓰레드에 걸쳐거 분신될 수 있습니다. 만일 블록 내의 코드가 processor-intensive하다면 이 속성은 잠재적인 성능 향상을 가져다 줄 것입니다. 다만 주의할 것은 이 옵션을 사용하면 각 원소들의 접근 순서가 일정하지 않게 됩니다.

NSDictionary 클래스도 블록 기반의 메소드를 다음과 같이 제공합니다.

// Objective-C
NSDictionary * dictionary = /* ... */;
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL * stop) {
    NSLog(@"dictionary[\"%@\"] = \"%@\"", key, obj);
}];

이렇게 열거 메소드를 사용하여 각 원소들을 순회한다면 통상적인 반복문을 사용하는 것보다 더 편리합니다.

 

블록은 동시 수행될 작업을 단순화시킬 수 있다.

블록은 “실행 가능한 코드와 스코프로부터 캡쳐된 옵셔널 상태들로 이루어진 구분되는 단위 작업(a distinct unit of work, combining executable code with optional state captured from the surrounding scope)”입니다. 이것은 OS X이나 iOS에서 지원하는 동시성 옵션 중 하나를 사용하게 되는 비동기 호출과도 잘 맞습니다. 쓰레드(thread)처럼 저 수준 메커니즘을 가지고 어떻게 작업해야 하나 찾아봐야 하는 대신, 여러분은 블록을 사용하여 여러분의 작업을 간단하게 정의하고 그 작업을 프로세서 자원이 가능하게 되는대로 시스템에게 수행하도록 맡기면 됩니다.

OS X이나 iOS는 태스크 스케줄링 메커니즘인 Operation Queue와 Grand Central Dispatch를 포함하여 다양한 동시성 기술을 제공하는데, 이들 메커니즘은 호출되어야 할 작업들이 순서를 기다리고 있는 큐(queue)의 형태로부터 구현된 것입니다. 호출시키고자 하는 블록을 큐에 추가하면 시스템은 프로세서 시간과 자원이 사용 가능해졌을 때 순서대로 큐에서 블록들을 꺼내게 됩니다.

시리얼 큐(serial queue)는 한 번에 하나의 오직 하나의 작업만을 실행합니다. 이전에 실행해 두었던 작업이 종료되기 전까지는 큐에 대기 중인 다음 작업이 호출되지 않습니다. 동시성 큐(concurrent queue)는 이전 작업이 종료되기를 기다리지 않고 가능한 많은 작업들을 한꺼번에 호출합니다.

 

오퍼레이션 큐를 가지고 블록 오퍼레이션 사용하기

오퍼레이션 큐(operation queue)는 작업 스케줄링을 하기 위한 Cocoa 및 Cocoa Touch의 접근법입니다. 여러분은 실행할 작업과 이에 수반되는 각종 데이터들을 NSOperation 클래스형 인스턴스로 캡슐화할 수 있습니다. 이렇게 해서 만들어진 오퍼레이션을 실행을 위해 NSOperationQueue로 전달할 수 있습니다. 물론 여러분은 복잡한 작업들을 구현하기 위해 NSOperation을 상속받아서 새로운 클래스를 만들 수도 있기는 하나 다음과 같이 미리 준비된 NSBlockOperation을 사용하면 블록을 생성해서 작업을 기술할 수 있습니다.

// Objective-C
NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock:^{
    // ...
}];

이렇게 만든 오퍼레이션은 여러분이 직접 호출할 수도 있지만 대개는 이미 존재하는 오퍼레이션 큐, 또는 여러분이 새로 만든 오퍼레이션 큐에 추가하여 실행되도록 대기시킵니다.

// Objective-C
// main queue에서 작업 스케줄링
[[NSOperationQueue mainQueue] addOperation:operation];

// background queue에서 작업 스케줄링
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];

여러분이 오퍼레이션 큐를 사용한다면 여러분은 큐에 있는 오퍼레이션 사이의 우선 순위라든가 의존성을 설정할 수도 있습니다. 예를 들어 몇 개의 오퍼레이션들로 이루어진 오퍼레이션 그룹이 완전이 종료될 때까지 어느 하나의 오퍼레이션 실행을 보류하도록 지시할 수도 있습니다. 또한 key-value observing을 통해 여러분의 오퍼레이션의 상태 변화를 관찰할 수도 있는데, 이는 예를 들어 작업이 완료될 때마다 진행율 표시기에 나타날 숫자를 변경하는데 유용할 것입니다.

 

카테고리 “Language/Objective-C & Swift”
more...