Objective-C 블록 문법

2023년 11월 2일 수정

블록(block)

블록(block)은 Objective-C에서 쓸 수 있는 동적 함수 혹은 클로저와 비슷한 기능이다. 필요할 때 동적으로 생성될 수 있고 이름 없이도 구현할 수 있는 등 다른 여러 언어들의 클로저 개념과 상당히 유사하다.

실제로는 함수 포인터 형식으로 유추. 실제 코드에 포인터를 의미하는 `*` 문자가 사용되지 않지만 `nil` 을 가질 수 있는 등 C 포인터로 대체되어 컴파일 되는 것 같다.

블록 문법

꺽쇠 즉 ^ 문자가 블록을 가리키는 핵심적인 오퍼레이터다.

^{
    ...
}

위가 블록 문법을 알려주는 가장 기본적인 문법이지만 실제로는 아래와 같은 식의 문법을 쓰게 되는 것 같다.

returntype (^name)(parameters) = ^(parameters) { ... }

parameters라 적어 놓은 부분은 매개변수 목록이 C 언어의 함수와 비슷한 방식으로 작성되는 부분이다. 예시를 보면 알 수 있지만 선언 시에는 타입만 명시하고 실제로 구현될 때 각 매개변수 이름을 실제로 결정하는 방식으로 많이 쓴다. 지극히 C 스러운 부분이다. 물론 선언 시 이름을 바로 명시할 수도 있다.

하지만 위 문법이 모든 것을 알려주지는 않는다. 상황에 따라 이름 위치가 바뀌는 등 곤란한 느낌이 든다. 그리서 좀 더 실전적인 예제 위주로 정리해본다.

조금더 실전적인 블록 예제

이제부터 나올 예제는 정수와 문자열을 매개변수로 받고 정수형을 리턴하는 블록 함수를 선언하고 구현하는 방법을 여러 상황에서 나열한다.

메서드 매개변수에 블록 정의하기

아마도 가장 흔하게 사용될 패턴으로 특정 클래스의 메서드에서 블록을 매개변수로 받는 경우다. 이 경우 아래와 같은 식으로 ^ 를 끼워넣어야 한다.

- (void)someMethodWithBlock:(int (^)(int, NSString *))blockFunc {
    ...
}

요즘에는 nullability를 챙기는 추세이므로 필요하다면 ^ 뒤에 nullability를 명시할 수도 있다.

- (void)someMethodWithBlock:(int (^nullable)(int, NSString *))blockFunc {
    ...
}

이런 식으로 구현한 블록은 아래와 같이 호출할 수 있다.

[someInstance someMethodWithBlock:^int(int value, NSString *name) {
    ...
}];

반환 값이 없는 블록의 경우 매개변수에서 구현할 때 ^ 뒤에 반환 타입을 그냥 생략해도 된다.

클래스 프로퍼티를 블록으로 선언해보기

프로퍼티의 경우는 일회성이 아닌 지속적으로 호출될 필요가 있는 블록을 담기에 유리하다.

@property (nonatomic, nullable) int (^propertyFunc)(int, NSString *);

위처럼 프로퍼티의 경우라면 ^ 뒤에 프로퍼티 이름이 온다. 그리고 nullability 명시도 기존 프로퍼티에 명시하는 형태로 쓰면 된다.

이 프로퍼티에 블록을 구현해서 지정하는 경우는 아래와 같은 식이다.

someInstance.propertyFunc = ^int(int value, NSString *name) {
    ...
}

물론 반환 타입을 안 쓰는 경우라면 생략할 수 있다.

블록으로 지역 함수를 만들어보기

위 문법 항목의 두 번째 문법으로 적어놓은 임시 코드가 해당될 것 같다. 제목대로 지역 변수에 블록을 담으면 로컬 함수처럼 사용할 수 있다.

int (^localFunc)(int, NSString *) = ^int(int value, NSString *name) {
    ...
};

이렇게 하면 `localFunc`라는 변수를 함수처럼 사용할 수 있게 된다.

int res = localFunc(0, @"foo");

같은 코드의 블록을 여러번 넘기려면 이런 식으로 구현하면 효율적일 것 같다.

블록을 타입과 비슷하게 선언하기

블록을 사용함에 있어 가장 많이 사용되는 스타일이 바로 `typedef` 등을 이용해 타입을 미리 특정 별명에 선언해 주는 방식이다.

typedef int (^SomeBlockFuncType)(int, NSString *);

위처럼 선언해두면 이제 기존의 복잡한 블록 문법 대신 SomeBlockFuncType 이라는 이름을 선언 타입으로 쓸 수 있다.

- (void)someMethodWithBlock:(SomeBlockFuncType)blockFunc {
    ...
}

물론 특정 변수에 담는 경우도 비슷하게 쓸 수 있다.

SomeBlockFuncType localFunc = ^int(int value, NSString *name) {
    ...
};

사족

사실 블록 관련 글은 다른 블로그에서도 이미 쓴 적이 있고 개인 메모장에도 기록이 되어 있음에도 또 이런 식으로 메모를 남기게 된다. 단순하다. 문법을 자꾸 까먹는다. 그리고 적어 놓은 곳도 까먹는다. 그러니 자꾸만 다시 찾아보고 메모를 하게 된다. 특히 이 글의 사용 예시는 내가 필요한 목적으로 쓰는 부분이다.

그만큼 Objective-C의 블록 문법이 가독성 측면에선 그다지 좋지 않다는 의미일지도 모른다. 애초에 C를 기반으로 하는 언어이기 때문에 어쩔 수 없는 측면이 있기에 이해는 해야겠지만 말이다.