Cocoa Internals - 블록

블록과 클로저

Cocoa는 기본적으로 객체 중심 디자인을 따라 설계되었으나, Swift가 나오기 이전에 함수 중심 프로그래밍의 일부 개념을 구현해 두었다.

Objective-C는 함수 중심 언어의 클로저 개념을 블록 객체 형태로 구현하였다.

블록

2010년 WWDC에서 블록block 객체가 소개되었다. 이는 GCD에서 관리하는 코드 묶음 단위다.

함수 중심 언어에 있는 람다 계산식 또는 클로저의 개념을 구현한 것이다.

블록 객체 구현

블록 객체를 관리하는 구조체, 블록 리터럴 구조체(Block_literal_1)가 있으며 다음의 특징을 갖는다.

  • &_NSConcreteStackBlock 또는 &_NSConcreteGlobalBlock 객체 포인터 변수를 갖는다.
  • 블록 객체 종류에 대한 플래그를 갖는다.
int main(int argc, const char * argv[]) {
void(^display)(void) = ^{ NSLog(@"Hello World!"); };
display();
return 0;
}

위와 같은 코드를 컴파일하여 내부적으로 만들어지는 코드를 C++ 수준에서 살펴보면 다음의 일들이 일어난다.

  • 작성된 코드 블록은 내부적으로 C 언어 함수로 만들어진다.
  • 블록 리터럴 구조체가 만들어진다.

Objective-C의 모든 객체는 힙에 저장되지만, 블록 객체는 스택에 만들어지며, 대부분 스택 함수 범위에 존재하여 속도 측면에서 이점이 있다.

또한 대부분의 블록 객체는 스택 함수 범위 내에 존재하므로, 스택 포인터가 반환되면 블록 객체는 사라진다. 이를 염두에 두어 소유권을 획득하려면 copy 메세지를 보내어 힙에 복사해야 한다.

글로벌 블록 객체는 복사되지 않으며, 이미 복사된 힙 블록 객체는 또다시 복사되지 않고 참조 횟수만 증가한다.

변수 캡처하기

int main(int argc, const char * argv[]) {
NSString* blockString = @"Hello, My name is %s.";
char* name = "presto";
int since = 1995;
void(^display)(void) = ^{ NSLog(blockString, name) };

}

블록 객체가 변수를 캡처하는 경우 내부적으로 생성되는 코드는 좀더 복잡한 형태를 갖는다.

  • 블록 리터럴 구조체는 캡처하는 값에 대한 변수를 갖는다.
  • 객체를 캡처하는 경우 해당 객체의 소유권을 관리하기 위한 함수가 만들어진다.

__block 지시어

블록 바깥의 외부 변수 자체를 바꾸기 위해서는 __block 지시어를 지정해야 한다.

int main(int argc, const char * argv[]) {
__block int since = 1995;
void(^display)(void) = ^{
since = 1996;
NSLog(@"since %d", since);
};
display();
return 0;
}

요약

블록 객체는 C 언어 수준에서 구현되었다.

블록 객체를 넘기는 Cocoa 프레임워크 API가 많아지고 있다. 간단한 콜백 함수를 대체할 수 있으며, 델리게이트 패턴을 대체할 수 있는 수단이다.

Cocoa 프레임워크에서 사용하는 블록 객체는 Swift의 클로저와 연결된다.

Swift 클로저

클로저는 접근 가능한 특정 범위scope 내에서 사용하는 값을 갖고 있는 함수를 의미한다.

Swift의 함수는 모두 클로저다.

클로저는 이름 없는 함수이거나 함수다.

클로저 형식

  • 참조 범위가 전체인 글로벌 함수는, 이름은 있지만 캡처하는 변수가 없는 클로저가 된다.
  • 다른 함수 내부에 선언한 중첩 함수는, 감싸고 있는 함수 범위에 접근 가능한 변수를 캡처하는, 이름 있는 클로저가 된다.
  • 클로저 표현식은, 코드를 감싸고 있는 컨텍스트 변수들의 값을 캡처하는 방식이다.

클로저 표현식은 다음과 같은 변형 방식을 지원한다.

let result = numbers.map({ (n: Int) -> Int in return n * n })
  • 인자 값이나 리턴 값에 대한 타입을 생략 가능하다.

    • Swift의 타입 추론에 의해 문맥에서 이들을 유추할 수 있다.
    let result = numbers.map({ n in return n * n })
  • 한 줄 표현 클로저에서 리턴 구문을 생략할 수 있다.

    • return 구문이 없어도 해당 변수를 리턴한다는 것을 유추할 수 있다.
    let result = numbers.map({ n in n * n })
  • 인자 값에 대한 값 축약 표현식(&nn)을 사용할 수 있다.

    let result = numbers.map({ $0 * $0 })
  • 함수 마지막 인자 값이 클로저인 경우 꼬리 클로저를 사용할 수 있다.

    • 이는 함수 중심 언어에서 꼬리 재귀 반복 동작을 최적화할 수 있는 중요한 요소다.
    let result = numbers.map() { $0 * $0 }
  • 클로저에 전달하는 인자 값이 없는 경우 괄호를 생략할 수 있다.

    let result = numbers.map { $0 * $0 }

함수 유형

컴파일러는 Swift 코드를 분석하면서 모든 클로저와 함수를 분석하고 분류하기 때문에 이들을 동일하게 처리할 수 있다.

내부적으로 함수와 클로저는 다음의 유형으로 구분된다.

함수 유형 설명
Subscript 서브스크립트(배열 또는 딕셔너리 데이터에 접근하기 위한 함수)
Constructor 객체 생성 함수
Destructor 객체 소멸 함수
LocalFunction 지역 범위 함수
GlobalFunction 전역 범위 함수
OperatorFunction 연산자 함수
Method 객체 메소드
StaticMethod 고정 메소드(재정의 불가능한 정적 함수)
ClassMethod 클래스 메소드(재정의 가능한 정적 함수)
Getter 프로퍼티 참조 함수
Setter 프로퍼티 설정 함수
MaterializeForSet 컴파일러 내부에서 사용하는 설정 함수
Addressor 불변 객체에 우회하여 접근하기 위한 함수
MutableAddressor 가변 객체에 우회하여 접근하기 위한 함수
WillSet 프로퍼티 설정 이전 옵저버 함수
DidSet 프로퍼티 설정 이후 옵저버 함수

함수 표현 유형

Representation Type

SIL에 다음과 같은 함수 표현 유형이 선언되어 있다.

C 언어나 Objective-C와 함께 사용하여 연결 가능한 함수 표현과, Swift 내부에서만 사용하는 함수 표현이 나누어져 있다.

함수 표현 유형 연결 가능 설명
CFunctionPointer O C 언어 함수 포인터
ObjcMethod O Objective-C 객체 메소드
Block O 블록 객체
Thin X 컨텍스트가 없거나 로컬 변수 캡처가 없는 함수 또는 커링 함수
Thick X 캡처한 변수를 포함하는 함수
Method X 네이티브 타입의 생성자, 소멸자, 컨텍스트를 가지는 함수
WitnessMethod X 프로토콜 구현 함수

1장의 allocating_init 함수는 컨텍스트가 없어 thin의 유형을 가지고 있음을 확인할 수 있다.

  • Swift 컴파일러가 연결 가능한 외부 언어foriegn language는 C 언어와 Objective-C다.
  • C 언어 구조체, 열거형, 함수와 Objective-C 언어의 객체는 연결 가능하다.
    • 객체인 경우 메타 타입 정보가 있어야 한다.
    • 함수인 경우 재귀 호출 방식이 아니거나, 입력 변수 타입이나 출력 타입에 튜플 타입이 없어야 한다.
  • 외부 함수 연결은 다음의 형태로 나누어 처리된다.
    • 고정 연결StaticBridged : 컴파일 시점에 결정됨
    • 동적 연결Bridged : 런타임에 동적으로 연결 가능
    • 객체 연결Object : 블록 객체로 연결
    • 나머지Trivial : 나머지 함수 연결
  • Swift로 만든 함수를 C 언어에서 사용하려면 @convention(c) 지시어를 붙여서 호출 규격을 C 언어 스타일로 지정해 주어야 한다.

요약

함수는 기능 구현을 위한 가장 작은 단위다.

다양한 함수의 표현 방식을 이해하고, 함수의 역할에 따라 적합한 방식을 적용할 수 있도록 하자.