[Objective-C 문법] 블럭 (Block)

요즘 Concurrent programming을 드려보다가 자주 쓰는데 쓰려고 하면 생각이 안나는 이상한(?!) 문법하나 적어두려고 이렇게 포스팅하게 되었다.

애플에서는 C 함수와 생김새가 비슷한 블럭 객체를 NS계열의 라이브러리에 많이 사용하고 있는 것을 볼 수 있다. 개인적으로는 Delegate을 쓸까 Block을 쓸까 고민을 많이 하는 편인데, 이제는 어느정도 머리에 확립이 되었다. 재사용해야하는 경우에는 클래스 기반의 Delegate을 사용고, 한번 사용할 로직이라면 보통 블럭을 사용하고 있다.

혹시 이 글을 읽으시는 분 중에서 더 좋은 경우가 있다면 댓글을 달아주시면 고맙겠다.

암튼. 그럼 블럭인 무엇인지 살펴보자.

Block (블럭)

O’REILY에서 나온 ‘iOS 7 Programming Cookbook’의 저자는 블럭을 다음과 같이 정의했다.

“Block objects are package of code that usually appear in the form of methods in Object-C.” (블럭 객체는 보통 Objective-C에서 메서드 모습을 한 코드 패키지이다.)

만약 누군가 개인적으로 블럭을 정의하라고 한다면 나는 아마 이렇게 대답할 것이다.

“블럭은 로직을 객체화해주는 또 다른 하나의 매커니즘이다.”

물론 Objective-C 프로그래밍은 Foundation에서 지원해주는 NSInvocator라는 클래스를 제공하고 있다. 하지만 보다 간편하게 사용할 수 있기 때문에 사용 빈도가 점점 늘어나고 있다.

어떻게 생겼나?

그럼 이제부터 본격적으로 문법에 대해서 알아보자. 다음은 Objective-C 스타일의 메서드와 C 함수 스타일, 그리고 블럭 스타일의 코드를 보여주고 있다.

 

  1. //#1. Objective-C Style
  2. - (void)CanCallThisBlock:(NSString *)strVal
  3. {
  4.     NSLog(@"strVal: %@", strVal);
  5. }
  6.  
  7. //#2. C style
  8. void CanCallThisBlock(NSString *strVal)
  9. {
  10.     NSLog(@"strVal: %@", strVal);
  11. }
  12.  
  13. //#3. Block
  14. void (^CanCallThisBlock)(NSString *) = ^(NSString *strVal)
  15. {
  16.     NSLog(@"strVal: %@", strVal);
  17. };

 

그리고 자주 사용하는 문법 중 하나는 typedef를 사용하여 컴파일러에게 블럭의 시그니쳐를 알려주는 코드는 다음과 같다.

  1. typedef void(^CanCallThisBlock)(NSString *);

위 코드의 내용은 클래스 내에서 인터페이스 확장자(Interface extension: ‘@interface ViewController()’) 내에 선언된 메서드의 선언문과 동일한 역할을 한다.

특징

블럭 밖에서 선언된 변수는 블럭 안으로 들어오면 ‘__block’ 예약어가 없는 한 read_only 속성으로 copy가 된다. 다음 코드를 살펴보자.

  1. NSUInteger intVal = 10;
  2.  
  3. CopyTest block = ^
  4. {
  5.     NSLog(@"#1. intVal = %d", intVal);
  6. };
  7.  
  8. intVal = 20;
  9.  
  10. //Test Final Result
  11. block();
  12. NSLog(@"#2. intVal = %d", intVal);
  13.  
  14. //결과:
  15. //#1. intVal = 10
  16. //#2. intVal = 20

위 코드를 보는 것 같이 intVal 변수는 ‘__block’이라는 예약어 없이 선언이 되어 있다. 그렇기 때문에 값이 변경 전의 값인 10가 그대로 남아있는 것이다.

그 밖에…

iOS 7이 나오면서 Objective-C 문법 자체에도 더 강화가 되어서 업데이트가 되었다. 사실 iOS 6를 사용해서 개발할 때만 해도 블럭 안에서 self를 사용할 수가 없었다. 그렇기 때문에 사용하기 위해서는 __block으로 선언한 변수를 하나 만들어서 사용을 하던지, 블럭을 정의 할 때 파라미터로 self를 받아서 사용을 했었다. 그런데 최신 버전에는 그런 걱정 없이 정상 작동을 하고 있다.

블럭을 변수에 할당 해서 사용을 하든지 inline(인라인)으로 사용을 하든지 개인적으로 가장 중요하다고 여기는 것은 가독성(Readability)인 것 같다. 블럭을 사용하는 순간, 화면에 뭔가 모를 걸리적 거림이 있는 것은 나 혼자 뿐일지 모르겠지만, 분명 블럭을 사용함에 있어서 깔끔한 느낌을 덜 주게 되는 것은 사실이다. 적당히 알맞은 장소에서 사용을 한다면 정말 강력한 무기가 될 것이고 그렇지 않고 모든 부분에서 블럭을 사용한다면 디버깅 뿐만 아니라 코드를 유지보수하는 측면에서 어려움을 맛 볼 것으로 여겨진다.

Updated December.19.2013

TDD로 개발 중 블럭을 Unit Test하기 위해 리서치를 하다가 못보던 블럭 문법을 발견하여 업데이트 하게 되었다. 블럭은 code에 inline 되어지기 때문에 여간 테스트하기 어려운 존재였는데, 좋은 방법을 찾았다. 다음과 같이 블럭을 메서드화 시켜서 선언해 사용하도록 하자.

  1. //Header 파일에는 다음과 같이 선언한다.
  2. - (void(^)(NSString *))instanceBlock;
  3.  
  4. // .m 파일에는 다음과 같이 정의한다.
  5. - (void(^)(NSString *))instanceBlock
  6. {
  7.     return ^(NSString *str)
  8.     {
  9.         NSLog(@"Output: %@", str);
  10.     };
  11. }
  12.  
  13. //그리고 Test 파일에서 사용할 때는 다음과 같이 사용한다.
  14. [myInstance instanceBlock](@"helloBarty!");

이 기법을 배운 포스트이다. 재사용 가능할 뿐만 아니라 테스트 가능하며, 그리고 가독성도 높여주는 좋은 방법이다.

[링크] How to add a new Unit Test target and OCMock to an existing XCode project

평소에 TDD에 원낙 관심이 많은 나는 어떻게하면 iOS 개발에 있어서 커버리지 100%를 자랑하면 회사 동료들에게 말할 수 있을까 고민 중이었다. 그런데 항상 기본 XCode에서 제공해주는 기능을 가지고 테스트하는 부분에 있어서 항상 한계를 느꼈는데, OCMock이라는 라이브러리를 발견하고 Cocopod 라이브러리를 써서 적용하려고 고생을 하다가. 좋은 글을 하나 찾아서 링크를 걸 어보았다.

링크