block에 대한 매우매우 기본적인 소개

Apple의 세계에서 삽질/Objective-C로 삽질 2010. 9. 18. 07:35

items = [1, 2, 3, 4, 5].freeze

items.each do |item|
	p item
end

5.times {
	|n| p n
}

루비를 써보셨다면 익히 보셨을 저런 짓들이 이제는 Objective C의 세계에서도 된다...뭐 그런거지요. 바로 실행 가능한 코드 뭉치(어찌보면 함수라고 할 수도 있고)를 다른 함수의 인자나 기타 등등으로 넣어버리는 것입니다. 사실은 함수 포인터 등등의 방법으로 가능하긴 한데, 그걸 언어 자체의 기능으로 넣어서 좀더 쓰기 편하고 안정적으로 하도록 한 것이라고 할까...물론 기타 다른 언어에서도 지원되던 기능이지요.

block의 기본은
^
입니다. 문법적으로는 대강 아래와 같이 쓰이는데...뭐 자세한 것은 Help Document에서 Blocks Programming Topics를 참고하시는 것이 좋을 듯 합니다.


// ^ 로 시작, 다음에 오는 것은 return value type, 그리고 ()안에 인자, {}안에 실행 코드
^BOOL(id item){ return [item length] > 0; }

// 만일 return value이 void라면 생략하고 다음과 같이 가능
^(id item){ NSLog(@"%@", [item description]); }

// 만일 return value와 인자가 모두 void라면 생략하고 다음과 같이 가능
^{ NSLog(@"어쩌라구?"); }

// 혹은 아예 block을 변수로 선언하려면
int (^myBlock)(int) = ^(int n) { return n * 100; };

// 그리고 부를 때는
NSLog(@"%d", myBlock(5));

그 외에 block자체는 Objective C 객체로 처리되기 때문에 retain/release등을 사용 가능합니다. 근데 자주 사용하게 되지는 않는 것 같네요.

중요한 점

// WWDC 2010 Session 102에서
int multiplier = 7;
int (^myBlock)(int) = ^(int num) { return num * multiplier; };
multiplier = 13;

printf("%d\n", myBlock(3));

다음의 결과는 뭘까요? 13 * 3이니 39?
답은 21입니다.

block안에서 접근하는 외부의 로컬 변수나 기타 등등의 것들은, block이 선언되는 시점의 값이 상수로 복사가 됩니다. Obj-C 객체들은 자동을 retain하고 들어간 뒤에 끝날 때 release되구요.

그렇다면 block은 외부와의 단절을 뜻하나...?

물론, block내부에서 외부로 연락을 취할 방법이 없다면 사용 용도가 지극히 제한될테니, 다음과 같은 녀석이 나타납니다.

__block storage class

H군이 또 질문을 했습니다. block구문을 처음 써서 indexset안에 있는 값을 다 더한 값을 얻고 싶다. 뭐 그런것이었습니다. 근데 아무리 해도 값이 안더해지고 0이 나오더라...뭐 이런 질문을...
	// indexes는 NSIndexSet
	NSUInteger total = 0;
	
	[idxSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
		total += idx;
	}];
	
	NSLog(@"Total = %d", total);
무려 WWDC 2010에 '직접' 갔다온 말이 많은 T라는 녀석도 아..이거 뭔가 방법이 있을텐데...이러고 있습니다. 뭐하러 갔나?

이친구들아 문서를 좀 보게나. 아니면 WWDC세션 비디오라도...

라고 하고 싶기도 했지만 그냥 멋지게 단 7자를 더 쳐서 문제를 해결해서 폼을 잡아보기로 했습니다(block을 사용한 enumeration은 다음 기회에 이야기 하고 싶은데, 여기서 잠깐 맛뵈기로...). 

	// indexes는 NSIndexSet
	__block NSUInteger total = 0;
	
	[idxSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
		total += idx;
	}];
	
	NSLog(@"Total = %d", total);

문제 해결... 참 쉽죠? 예 쉽습니다. __block이라는 녀석은 해당 변수를 스택이 아닌 힙에 만들어서 block안에서도 변경가능하게 만들어 주는 마법(?)을 부립니다. 일반적으로 block은 스택에 만들어지고, 해당 변수들도 몽땅 원본이 아닌 '사본'이 스택에 올라가기 때문에 그렇죠.

그럼 Objective C 객체들은 어떠한가? 별다른 처리가 필요하지는 않습니다.
그러니까 이런식으로 쓸 수 있다는 것입니다...
	NSArray *a = [NSArray arrayWithObjects:@"A", @"B", @"C", nil];
	
	NSMutableString *str = [NSMutableString string];

	[a enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
		[str appendFormat:@"(%d:%@)", idx, obj];	
	}];

	NSLog(@"%@", str); // (0:A)(1:B)(2:C)

물론 반환값을 block내에서 만들어내겠다...이런다면 __block으로 해야겠지요. 근데 이런 경우가 딱히 많지는 않을 듯 싶군요. 대부분은 return value로서 받으면 되니까.
 
기본적으로는 block이 선언되면 block안에서 접근하는 객체들은 block실행도중에 죽어버리는 것을 막기 위해서 자동 retain, release가 되는데 __block으로 선언하면 그걸 꺼버린답니다(당연히 개발자가 어떤 행동을 원하는지 알 길이 없기에 그러는 것이지요).

다른 사용예는 다음 기회에. enumeration과 GCD등에서 편리하게 쓰이거든요.
Posted by 타이가장관
,