[Obj-C] 개체 복사하기 – NSCopying -

Objective-C에서 클래스 인스턴스를 복사할 때 구현 되어 있어야 하는 Protocol은 NSCopying이다. 해당 Protocol은 하나의 인터페이스를 제공한다.

  1. - (id)copyWithZone:(NSZone *)zone

여기에서 Zone은 메모리에 있는 데이터의 Segment들이 있는 구역을 뜻한다. 옛날 그 어느날에는 memory segment를 특정 영역에 생성하여 관래했다고 한다. 하지만 꽤 오래전 부터 사용하지 않는 기술이다. 지금은 모든 앱은 하나의 Zone을 가지고 있다. 그러므로 인수 값인 zone에 대해서 크게 신경 쓰지 말고 기본적으로 무엇인지에 대해서만 인지하고 있으면 될 것이다.

보통 개발을 하다가 복사를 하고 싶으면 NSObject에 있는 copy 메서드를 사용하거나 mutableCopy를 사용하여 mutable 개체를 복사하고 있을 것이다. 사용자가 정의한 개체 복사를 위해는 NSObject의 copy나 mutableCopy 메서드를 override해서 사용하면 안된다. Copy 메서드가 호출하는 copyWithZone: 메서드를 override해서 사용해야 한다.

  1. #import <Foundation/Foundation.h>
  2.  
  3. @interface EOCPerson : NSObject <NSCopying>
  4.  
  5. @property (nonatomic, copy, readonly) NSString *firstName;
  6. @property (nonatomic, copy, readonly) NSString *lastName;
  7.  
  8. - (id)initWithFirstName:(NSString*)firstName
  9.             andLastName:(NSString*)lastName;
  10.  
  11. - (void)addFriend:(EOCPerson*)person;
  12. - (void)removeFriend:(EOCPerson*)person;
  13.  
  14. @end
  15.  
  16. @implementation EOCPerson {
  17.     NSMutableSet *_friends;
  18. }
  19.  
  20. - (id)initWithFirstName:(NSString*)firstName
  21.             andLastName:(NSString*)lastName {
  22.     if ((self = [super init])) {
  23.         _firstName = [firstName copy];
  24.         _lastName = [lastName copy];
  25.         _friends = [NSMutableSet new];
  26.     }
  27.     return self;
  28. }
  29.  
  30. - (void)addFriend:(EOCPerson*)person {
  31.     [_friends addObject:person];
  32. }
  33.  
  34. - (void)removeFriend:(EOCPerson*)person {
  35.     [_friends removeObject:person];
  36. }
  37.  
  38. - (id)copyWithZone:(NSZone*)zone {
  39.     EOCPerson *copy = [[[self class] allocWithZone:zone]
  40.                        initWithFirstName:_firstName
  41.                              andLastName:_lastName];
  42.     copy->_friends = [_friends mutableCopy];
  43.     return copy;
  44. }
  45.  
  46. @end

위 코드에서 copyWithZone: 메서드의 copy->_friends를 주목하자. _friends는 internal 인스턴스이기 때문에 -> 문법을 사용했음을 기억하자.

복사 개념에는 ‘Shallow Copy’(얕은 복사)와 ‘Deep Copy’(깊은 복사)가 있다. 얕은 복사란 인스턴스 프로퍼티로 Collection 타입의 개체가 있을 때 컨테이너는 복사를 하되 속의 있는 내용물은 기존 컨테이너가 가지고 있던 레퍼런스를 포인트하고 있는 개념이다. 깊은 복사는 말 그대로 클론을 하나 만들어서 새로운 메모리 영역에 올리는 것을 뜻한다.  NSCopying는 기본적으로 얕은 복사를 한다. 그러므로 깊은 복사를 하기 위해서는 추가적인 조취가 필요하다.

Effective Objective-C 2.0 Matt Galloway

Effective Objective-C 2.0 Matt Galloway

위 사진을 보면 얕은 복사는 컨테이너는 복사가 되었지만 내용물은 원본 개체의 내용물을 가르키고 있는 것을 볼 수있다. 반면 깊은 복사는 클론을 만들어 낸 것을 볼수 있다.

  1. - (id)deepCopy {
  2.     EOCPerson *copy = [[[self class] alloc]
  3.                        initWithFirstName:_firstName
  4.                              andLastName:_lastName];
  5.     copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
  6.                                              copyItems:YES];
  7.     return copy;
  8. }

NSMutableSet의 initWithSet:copyItems: 생성자를 통해서 깊은 복사를 할 수 있다.

[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!");

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

[Algorithm] Merge Sort

정렬 알고리즘은 CS 수업을 듣게 되면 꼭 한번씩은 접하게 되는 알고리즘일 것이다. CS 학생이 기본을 탄탄히 하기 위해서 꼭 들어야 할 과목을 뽑는다면, Data Structure과 Algorithm을 개인적으로 뽑을 것이다. 하지 아이러니한 것은 실무에 들어가면 High-End 언어들의 편리성으로 크게 고려하지 않거나 고민하지 않은체 개발하는 개발자들이 상당히 많다는 것이다. 하지만 시간이 점점 지날수록 소프트웨어 개발에 경력을 쌓다보면 절대 중요한 과목이며, 기본기가 아닐까 싶다. 그런 의미에서 본인도 알고리즘과 자료구조에 대해 다시 상기하는 마음으로 공부하기 시작했다.

본 포스트는 개인 스터디의 결과물로써 기록하고자 하는 목적과 동시에 가능하다면 많은 사람과 함께 지식 교류를 해보고자 하는 목적으로 블로그에 작성하기 되었다. 내용면에서 부족한 부분이나 틀린 부분이 있다면 주저말고 댓글을 달아주길 바란다.

 

Algorithm이 란?

본격적으로 Merge Sort 알리즘에 대해서 알아보기 전에 간단한 배경 지식을 언급하고 지나가야 할 것 같다. 알고리즘이란, ‘The Algorithm Design Manual’이라는 책을 보면 다음과 같이 정의하고 있다.

“An algorithm is a procedure to accomplish a specific task.”

“알고리즘이란 특정 Task를 완수하기 위한 과정이다.”

사실 소프트웨어 개발 뿐만이 아니라 실생활에서도 많은 일을 처리하기 위해서 여러 과정을 걸쳐 처리를 하고 있다. 하지만 왜 소프트웨어 개발 산업에서 알고리즘을 중요하게 여기는 이유는 한정적인 자원에서 (CPU 성능, 메모리 공간) 효율적으로 더 많은 일을 빠르게 처리하기 위함일 것이다. 같은 결과를 내는 코드를 짜더라도 어떤 방식(알고리즘)으로 짰느냐에 따라 성능과 사용자에게 주는 제품의 인상은 천지 차이이다. 하드웨어의 눈부신 발전으로 많은 개발자들은 크게 고민하지 않고 이득을 보았을지 모르지만, Big-Data 시대에 더 많은 데이터를 더욱 안정적이며 신속하게 전달하기 위해서는 올바른 알고리즘 선택의 중요성을 더욱 실감할 수 있을 것이다.

 

Merge Sort

이제 본격적으로 본 포스트에서 다루고자 하는 Merge Sort에 대해서 설명해보겠다. 위에서 살펴보았지만 정렬 알고리즘 중에서 성능이 좋은 알고리즘 중에 하나로 뽑히는 Merge Sort는 평균과 최악의 경우 O(n log(n)) 만큼 런타임이 걸린다. 이 알고리즘은 배열을 반으로 쪼개고 쪼개어 각각의 절반을 비교하며 합병하면서 정렬하는 방식이다.

그럼 콘셉트를 백번 설명하는 것 보다 코드를 한번 보면서 설명하는 것이 이해를 돕는 것이 헐씬 쉬울 것으로 예상이 된다. 다름은 Objective-C로 짠 Merge sort 알고리즘이다.

  1. - (NSArray *)mergeSort:(NSArray *)unsortedArr
  2. {
  3.     NSInteger numOfItems = unsortedArr.count;
  4.  
  5.     //#1.
  6.     if (numOfItems < 2)
  7.         return unsortedArr;
  8.  
  9.     //#2.
  10.     NSInteger middle = numOfItems / 2;
  11.     NSRange left = NSMakeRange(0, middle);
  12.     NSRange right = NSMakeRange(middle, numOfItems – middle);
  13.  
  14.     //#3.
  15.     NSArray *leftArr = [unsortedArr subarrayWithRange:left];
  16.     NSArray *rightArr = [unsortedArr subarrayWithRange:right];
  17.  
  18.     //#4.
  19.     return [self merge:[self mergeSort:leftArr]
  20.               andRight:[self mergeSort:rightArr]];
  21. }
  22.  
  23. - (NSArray *)merge:(NSArray *)leftArr andRight:(NSArray *)rightArr
  24. {
  25.     //#5.
  26.     NSInteger resultArrCapa = leftArr.count + rightArr.count;
  27.     NSMutableArray *resultArr = [[NSMutableArray alloc]
  28.                                  initWithCapacity:resultArrCapa];
  29.     //#6.
  30.     NSInteger left = 0;
  31.     NSInteger right = 0;
  32.  
  33.     //#7.
  34.     while (left < leftArr.count && right < rightArr.count)
  35.     {
  36.         //#8.
  37.         NSComparisonResult compareResutl = [leftArr[left]
  38.                                             compare:rightArr[right]];
  39.  
  40.         //#9.
  41.         if (compareResutl == NSOrderedAscending)
  42.             [resultArr addObject:leftArr[left++]];
  43.         else
  44.             [resultArr addObject:rightArr[right++]];
  45.     }
  46.  
  47.     //#10.
  48.     NSRange leftRange = NSMakeRange(left, leftArr.count – left);
  49.     NSRange rightRange = NSMakeRange(right, rightArr.count – right);
  50.     NSArray *newLetfArr = [leftArr subarrayWithRange:leftRange];
  51.     NSArray *newRightArr = [rightArr subarrayWithRange:rightRange];
  52.  
  53.     [resultArr addObjectsFromArray:newLetfArr];
  54.     [resultArr addObjectsFromArray:newRightArr];
  55.  
  56.     //#11.
  57.     return resultArr;
  58. }

 

코드 설명

먼저 크게 두 개의 메소드로 나누어 보았다. 첫 번째 ‘mergeSort:’는 배열을 반으로 쪼개주는 역할을 한다. 두 번째 ‘merge:andRight:’ 메서드는 두 개의 배열에 있는 각 요소들을 비교하여 정렬하여 합병해주는 메서드이다. 코드 한줄 한줄을 따라가며 설명하기 전에 알아둬야 할 점은 ‘mergeSort:’ 메서드는 배열에 속한 요소의 갯수가 하나가 될 때까지 재귀적으로 돌아간다는 점을 알면 어떻게 동작하는지 쉽게 알 수 있을 것이다.

  1. 배열의 요소의 갯수가 하나이면 반환을 해준다. 그러므로써 재귀적으로 반복의 종료를 한다.
  2. 인수로 들어 온 배열을 반으로 쪼개기 위해서 다음과 같은 변수를 만들어 준다.
  3. NSRange 객체를 통해 원본 배열을 두 개의 Sub-Array 쪼갠다.
  4. ‘mergeSort:’메서드에서 가장 눈여겨 봐야하는 부분이다. 쪼갠 왼쪽, 오른쪽 배열을 다시 쪼갤 수 있을 만큼 쪼개며, 각각의 반환된 배열을 ‘merge:andRight:’로 넘겨준다.
  5. 합병하여 반환할 배열을 왼쪽 배열과 오른쪽 배열의 요소 갯수를 더한 만큼의 배열을 만들어준다.
  6. 왼쪽 배열과 오른쪽 배열의 요소들을 비교하기 위해서 사용할 인덱스 변수를 초기화 한다.
  7. 두 인덱스가 왼쪽 배열과 오른쪽 배열의 요소 갯수가 안넘는 선 안에서 while문으로 반복이 된다.
  8. 왼쪽 배열의 왼쪽 인덱스에 있는 값과 오른쪽 배열의 오른쪽 인덱스에 있는 값을 비교하여 NSComparisonResult 이넘 형 변수에 대입한다.
  9. 비교 결과 값 변수가 오름 차순이면 왼쪽 배열의 왼쪽 인덱스에 있는 값을 반환 배열에 넣어주고, 내림 차순이면 오른쪽 배열의 오른쪽 인덱스에 있는 값을 넣어준다.
  10. 왼쪽 배열과 오른쪽 배열을 비교하면서 합병을 했지만 한쪽 배열이 반환 배열에 다 들어가게 되면 분명 비교 되지 못한체 아직 기다리고 있는 요소들이 있을 것이다. 그 나머지 요소를 위해 다음과 같은 NSRange를 만들어서 남어지 요소를 반환 배열에 넣어준다.
  11. 정렬된 반환 배열을 전환해주면 메서드는 종료를 한다.

Merge Sort 알고리즘은 뛰어난 성능으로 정렬 알고리즘에서 유명한 알고리즘이다. 물론 실무에서 사용할 일이 정말 있을까 싶지만, 알고리즘을 이론으로만 알고있고, 구현해 본 경험이 없었다면 한번 시도해보길 권유한다. 좋은 알고리즘을 구현해보는 것은 생각보다 만만치 않은 일이지만, 실무하는데 있어서 얼마나 지금까지 생각을 깊게 하지 않고 편한데로만 짰었는지, 자신을 돌아보는 시간이 되지 않을까 싶다. 대학 시절 정말 싫어했던 알고리즘을 경력을 쌓고 다시 보니 지난 세월을 돌아보게하는 좋은 경험인 것 같다. 앞으로는 코드 하나를 짜더라도 한번 더 생각해보고 짜야겠다는 마음을 가져본다.

[Objective-C 문법] GCD를 사용한 Concurrent Programming

개요

Concurrent Programming은 iOS 앱뿐만이 아니라 계산을 처리하는 CPU 코어가 하나 이상 사용되어지는 곳에서는 보다 효율적인 코딩을 위해서 사용되어 지는 기법이다. 사실 CPU는 한번에 한 가지의 계산 처리밖에 할 수 없는 존재이다. 하지만 워낙에 빠르기 때문에 시간을 쪼개어서 여러가지 일을 돌아가면서 실행하기 때문에 엔드 유저들에게 있어서 한번에 여러가지 일이 동시에 실행되어지는 것으로 착각하게 만든다. 하지만 하드웨어의 발전을 통해서 계산 처리 속도도 빠르게 업그레이드 했지만, 하드웨어 발전의 한계에 도달한 나머지 이제는 한 개의 코어 자체의 처리 속도를 올리려고 하기 보다는, 여러가지 처리르 동시에 할 수 있도록 여러 개의 코어를 한 CPU 칩에 탑재하는 방식의 최신 기술이 나오고 있는 실정이다.

하지만 이러한 혜택을 누리기 위해서는 하드웨어 뿐만이 아니라 소프트웨어 차원에 기법이 적용되었을 때 비로써 진정한 Concurrent 프로그래밍의 힘을 발휘할 수 있게 되는 것이다.

그렇다면 애플의 Objective-C를 사용한 프로젝트에서는 어떤 기법을 통해 Concurrent 프로그래밍을 할 수 있는지 알아보자. 먼저 애플은 다음 세가지 기법을 사용하여 Concurrent 프로그래밍을 할 수 있을 것이다.

  • GCD (Grand Central Dispatch)를 사용한 기법.
  • NSOperation이 포함되어 있는 Foundation 프래임워크를 사용한 기법.
  • NSThread 직접 쓰레드를 사용한 기법.

이번 포스팅에서는 GCD에만 집중하여 설명을 하게 될 것이다. 위의 기법이 어떻게 다른지 서로 비교하기 위해서는 먼저 하나하나 사용할 문법에 대해서 알아보는 것이 필요할 것이다.

1. 메인 큐에서 UI 관련 된 Task 실행하기

아이폰은 안드로이드 폰에 비해서 원활한 UI이 작동이 큰 장점 중 하나이다. 그것이 가능한 이유는 모든 UI 관련 처리는 main_queue에서만 처리를 하기 때문에 어떤 로직이 오래 동안 계산한다고 해서 UI 작동을 막지 못하기 때문이다. 그렇다면 어떻게해서 UI 관련 작업을 GCD를 통해 처리 할 수 있는지 아래 코드를 살펴보자.

  1. dispatch_queue_t mainQueue = dispatch_get_main_queue();
  2. dispatch_async(mainQueue, ^
  3. {
  4.     UIAlertView *alert = nil;
  5.     alert = [[UIAlertView alloc] initWithTitle:@"GCD TEST" 
  6.                                  message:@"HelloBarty" 
  7.                                  delegate:nil 
  8.                                  cancelButtonTitle:@"OK" 
  9.                                  otherButtonTitles:nil, nil];
  10.  
  11.     [alert show];
  12. });

 

2. UI 처리와 관련 없는 작업을 Synch하게 처리하기

큐에 들어 온 순서대로 처리하고 싶다면, 다음 코드를 참조하여 처리한다. 그런데 기억해야 할 것은 UI 작업이 처리될 main 큐에서는 순차적인 처리를 안해준다. (애플에서는 화면을 멈추게 하는 것을 막기위한 처신으로 보여진다.)

  1. void (^Print1To1000)(void) = ^
  2. {
  3.     NSUInteger counter = 0;
  4.     for(counter = 1; counter &lt;= 1000; counter++)
  5.     {
  6.         NSLog(@"counter = %lu – Thread = %@", counter, [NSThread currentThread]);
  7.     }
  8. };
  9.  
  10. void (^Print1To100)(void) = ^
  11. {
  12.     NSUInteger counter = 0;
  13.     for(counter = 1; counter &lt;= 100; counter++)
  14.     {
  15.         NSLog(@"Counter = %lu – Thread = %@", counter, [NSThread currentThread]);
  16.     }
  17. };
  18.  
  19. - (void)executeSynchrously
  20. {
  21.     dispatch_queue_t concurrentQue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  22.  
  23.     dispatch_sync(concurrentQue, Print1To1000);
  24.     dispatch_sync(concurrentQue, Print1To1000);
  25. }

 

3. UI 처리와 관련 없는 작업을 Asynch하게 처리하기

이번에는 순차적인 처리가 아닌 비동기적인 처리하는 법을 알아 보겠다. 크게 두가지 코드를 통해 알아 볼 것인다. 첫 번째 코드는 다음과 같다.

  1. void (^Print1To1000)(void) = ^
  2. {
  3.     NSUInteger counter = 0;
  4.     for(counter = 1; counter &lt;= 1000; counter++)
  5.     {
  6.         NSLog(@"counter = %lu – Thread = %@", counter, [NSThread currentThread]);
  7.     }
  8. };
  9.  
  10. void (^Print1To100)(void) = ^
  11. {
  12.     NSUInteger counter = 0;
  13.     for(counter = 1; counter &lt;= 100; counter++)
  14.     {
  15.         NSLog(@"Counter = %lu – Thread = %@", counter, [NSThread currentThread]);
  16.     }
  17. };
  18.  
  19. - (void)executeAsynchrously
  20. {
  21.     dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  22.  
  23.     dispatch_async(concurrentQueue, Print1To100);
  24.     dispatch_async(concurrentQueue, Print1To100);
  25. }

이번에는 네트워크를 통해 이미지 데이터를 다운로드 받아온 다음 ViewController에 집어 넣는 작업을 해보자.

  1. - (void)downloadImgAndShowAsynchronously
  2. {
  3.     dispatch_queue_t mainThread = dispatch_get_main_queue();
  4.     dispatch_queue_t concurrentQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  5.  
  6.     dispatch_async(concurrentQ, ^
  7.     {
  8.         __block UIImage *image = nil;
  9.  
  10.         //#. Download Task
  11.         dispatch_sync(concurrentQ, ^
  12.           {
  13.               NSLog(@"## 1");
  14.  
  15.               NSString *urlStr = @"http://yannickloriot.com/wp-content/uploads/2011/04/xcode.png";
  16.               NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
  17.               NSError *downloadError = nil;
  18.               NSData *imageData = [NSURLConnection
  19.                                    sendSynchronousRequest:req
  20.                                    returningResponse:nil
  21.                                    error:&downloadError];
  22.               if (downloadError == nil && imageData != nil)
  23.                   image = [UIImage imageWithData:imageData];
  24.               else if(downloadError != nil)
  25.                   NSLog(@"Error: %@", downloadError);
  26.               else
  27.                   NSLog(@"No data could get downloaded from URL..");
  28.           });
  29.  
  30.         //#. Show image Task
  31.         dispatch_sync(mainThread, ^
  32.           {
  33.               NSLog(@"## 2");
  34.  
  35.               if (image == nil)
  36.               {
  37.                   NSLog(@"Image isn't downloaded. Nothing to display.");
  38.                   return;
  39.               }
  40.  
  41.               UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
  42.               [imageView setImage:image];
  43.               [imageView setContentMode:UIViewContentModeScaleAspectFit];
  44.               [self.view addSubview:imageView];
  45.           });
  46.     });
  47. }

위 예제 코드를 보면 큰 Asynch 괄호에 두 개의 작업이 synch로 선언되어 있다. 이렇게 작업한 이유는 이미지 데이터가 다운로드가 끝난 다음에 ‘main 큐’에서 UI로 뿌려주기 위함이다. 하지만 본 블러그에서 잠시 언급했었지만, 원래 main 큐에서는 동기식 처리가 안되나, 이 작업은 비동기식 틀안에 있는 동기식 작업이기 때문에 처리가 허용된다. 만약 main 큐에다가 동기식 처리를 요청하게 되면은 Runtime 때 실행 조차 되지 않는다.

 

4. 작업을 지연하여 실행하기

GCD를 사용하여 작업에 타이머를 줄 수도 있다. 자동 Xcode 5의 자동완성 기능이 ‘dispatch_after’만 쳐도 코드를 완성해주는데 다음과 같다.

  1. double delayInSeconds = 5.0;
  2. dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
  3. dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  4.  
  5. dispatch_after(popTime, concurrentQueue, ^(void)
  6. {
  7.     NSLog(@"Hello Barty!");
  8. });

 

5. 애플리케이션이 살아 있는 동안 한번만 실행하기

디자인 패턴에서는 ‘싱글톤 패턴’이 해당 기능의 역할을 한다. 앱이 살아 있는 동안 한번만 실행되게 해주는 기법인다. GCD를 통해서도 실현할 수 있다.

  1. void (^performThisOnlyOneTime)(void) = ^
  2. {
  3.     static NSUInteger numberOfEntries = 0;
  4.     numberOfEntries ++;
  5.  
  6.     NSLog(@"Executed %lu time(s)", (unsigned long)numberOfEntries);
  7. };
  8.  
  9. - (void)performOnce
  10. {
  11.     static dispatch_once_t onceToken;
  12.  
  13.     dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  14.     dispatch_once(&onceToken, ^
  15.     {
  16.         dispatch_async(concurrentQueue, performThisOnlyOneTime);
  17.     });
  18.  
  19.     return;
  20. }

그래서 실제로 싱글톤 패턴을 구현할 때 GCD를 사용하여 구현하는 경우도 많이 있다.

 

6. 그룹화 된 작업 처리하기

그룹화 된 작업을 처리하는 GCD가 있다.

  1. - (void)reloadTableView
  2. {
  3.     NSLog(@"reloadTableView");
  4. }
  5.  
  6. - (void)reloadScrollView
  7. {
  8.     NSLog(@"reloadScrollView");
  9. }
  10.  
  11. - (void)reloadImageView
  12. {
  13.     NSLog(@"reloadImageView");
  14. }
  15.  
  16. - (void)performGropingTasks
  17. {
  18.     dispatch_group_t group = dispatch_group_create();
  19.     dispatch_queue_t mainQueue = dispatch_get_main_queue();
  20.  
  21.     dispatch_group_async(group, mainQueue, ^
  22.     {
  23.         [self reloadTableView];
  24.     });
  25.  
  26.     dispatch_group_async(group, mainQueue, ^
  27.     {
  28.         [self reloadScrollView];
  29.     });
  30.  
  31.     dispatch_group_async(group, mainQueue, ^
  32.     {
  33.         [self reloadImageView];
  34.     });
  35.  
  36.     dispatch_group_notify(group, mainQueue, ^
  37.     {
  38.         UIAlertView *alertView = nil;
  39.         alertView = [[UIAlertView alloc] initWithTitle:@"Finished"
  40.                                                message:@"All tasks are finished"
  41.                                               delegate:nil
  42.                                      cancelButtonTitle:@"OK"
  43.                                      otherButtonTitles:nil, nil];
  44.  
  45.         [alertView show];
  46.     });
  47. }

 

7. 사용자 정의 큐 만들어 사용하기

사용자가 이름을 주고 순차적인 큐를 만들 수 있다. 보통 이름을 줄때는 ‘reversed domain 방식’으로 메겨진다 다음 코드르 살펴보자.

  1. - (void)performTasksOnSerialCustomeQueue
  2. {
  3.     dispatch_queue_t customQue = dispatch_queue_create("com.barty.personal.gcd", 0);
  4.  
  5.     dispatch_async(customQue, ^
  6.     {
  7.         for (int i = 0; i < 5; i++)
  8.         {
  9.             NSLog(@"#1. iteration: %d", i);
  10.         }
  11.     });
  12.  
  13.     dispatch_async(customQue, ^
  14.    {
  15.        for (int i = 0; i < 10; i++)
  16.        {
  17.            NSLog(@"#2. iteration: %d", i);
  18.        }
  19.    });
  20.  
  21.     dispatch_async(customQue, ^
  22.    {
  23.        for (int i = 0; i < 15; i++)
  24.        {
  25.            NSLog(@"#3. iteration: %d", i);
  26.        }
  27.    });
  28. }

 
복잡한 Thread 처리 없이 여러가지 기능을 제공해주는 GCD는 요즘 한참 유행처럼 많은 개발자들 사이에서 사용되어 지고 있다. 하지만 간편한 사용에 비해 ‘취소하기’와 같은 기능이 없어서 아쉬울 때가 있다. 무겁지 않은 가벼운 concurrent를 처리할 때에는 GCD만큼 간변하고 좋은 것이 없는 것 같다. 하지만 GCD가 채워주지 못하는 부분은 NSOperation이나 NSThread에서 처리를 해보자.

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

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

링크

[관찰] Quiet Time with Facebook SDK -Part 1-2. Authentication (Session)-

Screen Shot 2013-09-03 at 11.11.11 AM

안녕하세요. 바티입니다. 오늘은 Facebook SDK의 인증부분에서 중요한 ‘Session’에 대한 설명을 해보도록 하겠습니다. 본 포스팅은 링크를 바탕으로 부분적 번역 및 이해를 돕기 위해 추가적으로 간략하게 정리해보았습니다. 크게 설명한 내용은 다음과 같습니다.

  • 세션 상태의 생명주기
  • 세션 토큰 캐쉬하기

‘세션 상태의 생명주기’에서는 FBSession의 상태가 앱의 상태와 플로우를 통해서 어떻게 변하는지 설명하게 될 것고, ‘세션 토큰 캐쉬하기’에서는 FBSession을 한번 열었을 때 그것을 저장하는데, 어떤 메서드를 통해서 관리가 되는지 큰 그림을 그려보도록 하겠습니다.

#Session State Lifecycle (세션 상태의 생명주기)

Facebook SDK는 세션 상태의 생명주기를 관리하기 위해서 ‘세션 상태 머신’을 사용한다. 이 상태들을 통해서 올바른 상태 전환에 따른 처리와 ‘access token’에 저장 및 사용 그리고 에러 시나리오에 대한 처리를 할 수 있게 도와준다. 자세한 설명에 들어가기 앞어서 기억해야하는 두 가지가 있는다.

  • 세션은 한번만 열수 있다. 세션이 닫히면 똑같은 세션을 다시 열수 없다. 대신에 새로운 세션을 열수 있다. 보통 모바일 앱에서는 active 세션을 한번 열것이다. Facebook SDK에서는 열수 있는 새 세션 인스턴스를 관리해주는 static 메서드를 제공한다.
  • 세션의 오픈 APIs는 완전한 핸들러를 제공한다. 핸들러는 세션 인스턴스와 연관되어지고, 세션 상태가 바뀔 때마다 호출해 준다. 핸들러는 세션이 열렸을 때, 토큰이 업데이트 되었을 때, 세션이 닫히거나 다른 상태로 바뀔때 호출되어 진다.

이어서 세션 상태를 설명하기 위해서 Facebook SDK에서 제공해주고 있는 샘플 ‘Scrumptious’를 가지고 설명을 알 것이다.

i- 앱 실행

Facebook의 샘플 코드인 ‘Scrumptious’ 앱이 처음 실행하면 FBSession의 ‘openActiveSessionWithReadPermissions:allowLoginUI:completionHandler:’가 호출된다.

Screen Shot 2013-09-09 at 7.59.19 AM

FBSessions는 ‘FBSessionStateCreated’로 시작해서 만약 토근이 캐시에서 찾게 되면 ‘FBSessionStateOpen’가 된다.

‘OpenActiveSession*’ 메서드 대신 다음과 같은 과정으로 같은 것을 성취할 수 있다.

  1. FBSession 인스턴스의 init 메서드를 호출.
  2. 상태 값이 FBSessionStateCreated로 전환이 되었는지 체크.
  3. 만약 상태 값이 FBSessionStateCreated이면 FBSession 인스턴스의 open이 들어가는 함수를 호출.

만약 위 흐름을 사용하고 싶으면 사용자는 Static 메서드를 사용 할 수 없다. 예를 들어 만약 프로그램 소스 구현에서 캐시 전략을 사용하고 싶으면 세션을 초기화할 때 정보를 전달해야 한다.

 

ii- 로그인

Scrumptious 샘플에서 로그인 UI는 FBSession의 클래스 메서드 openActiveSessionWithReadPermissions:allowLoginUI:completionHandler:의 호출을 통해서 이다. allowLoginUI를 YES를 넘겨줌으로써 강제로 로그인 UI를 보여준다.

iOS 6부터 OS 차원에서 Facebook 계정을 연동하기 시작하면서 처음에는 OS 차원에서 직접 지원하는 Facebook 시스템의 로그인을 시도한다. 만약 iOS에 설정이 안되어 있다면 iOS용 Facebook 앱이나 사파리 브라우저로 대처 한다.

아래 그림은 iOS 차원에서 지원하는 Facebook 시스템의 플로우다.

Screen Shot 2013-09-09 at 8.05.20 AM

네이티브 로그인 창이 나오기 전에는 상태 값은 FBSessionStateCreated에서 FBSessionStateCreatedOpening으로 변환된다. 만약 사용자가 ‘OK’를 클릭하면, FBSessionStateOpen으로 상태가 변화된다. 만약 사용자가 수락하지 않으면, 상태 값은 FBSessionStateClosedLoginFailed로 변화된다. 에러가 나면 화면에 보여줄 수 있게 정보를 전달해준다.

Screen Shot 2013-09-09 at 8.06.43 AM

앱은 iOS용 Facebook 앱이나 사파리 브라우저로 전환되기 전에 상태 값은 FBSessionStateCreated에서 FBSessionStateCreatedOpening로 전환된다. 만약 사용자가 로그인 창에서 퍼미션을 수락하면 상태 값은 FBSessionStateOpen으로 전환된다. 하지만 만약 사용자가 취소 버튼을 클릭하면 상태 값은 FBSessionStateClosedLoginFailed로 전환고 에러에 대한 정보도 함께 전달된다. 여기에서 또다른 경우가 생기는데 사용자가 ‘취소’ 버튼을 클릭하지 않고 홈 버튼을 클릭하면 이 시나리오에서는 iOS용 Facebook앱은 로그인 흐름이 끊겼다는 것에 대한 정보를 전달할 방법을 잃게 된다. 이런 상황을 처리하기 위해서는 AppDelegate 파일에서 handleDidBecomeActive 메서드를 통해서 처리할 수 있다. 이 경우에는 상태의 값이 FBSessionStateCloseLoginFailed로 전환된다.

 

iii- 추가적인 허가를 요청할 때

개발 중인 앱에서 사용자가 인증을 마치고 나서 추가적인 퍼미션을 요구하는 상황이 생길 수도 있다. FBSession 인스턴스 메서드의 requestNew*를 통해 다음 흐름을 초기화 할 수 있다.

Screen Shot 2013-09-09 at 8.07.34 AM

위 다이어그램은 iOS 6 기기에서 사용자가 시스템 계정으로 로그인 되는 흐름을 보여준다. 이 흐름은 기본 적으로 빠른 앱 스위칭 케이스와 동일하다. 만약 사용자가 추가적인 퍼미션을 수락한다면 세션의 상태는 FBSessionStateOpenTokenExtended로 변화할 것이다.

하지만 만약 수락하지 않는다면 세션의 상태는 FBSessionStateOpen이나 FBSessionStateOpenTokenExtended로 남게 될 것이다.

 

iv- 로그아웃

Scrumptious 샘플 프로그램에서 로그 아웃 버튼을 클릭하게 되면 현재 활성된 세션에서 closeAndClearTokenInformation 메서드가 호출 된다.

Screen Shot 2013-09-09 at 8.08.18 AM

이 전환은 FBSessionSateOpen이나 FBSessionStateOpenTokenExtended 상태에서 FBSessionStateClosed로 전환된다.

closeAndClearTokenInformation 메서드는 세션을 닫는과 동시에 캐시에 대한 정보도 지우게된다. 이 말의 뜻은 앱을 재 실행하면 로그인 창이 뜨게 된다는 말이다.

FBSession의 close 메서드 호출도 상태 값을 FBSessionStateClosed 로 전환 시켜준다. 하지만 캐시 정보는 지워 주지 않고, 다음번 앱 실행 때 캐시 정보를 사용할 수 있게 된다. 데모 앱에서 close 메서드는 appDelegate 클래스의 applicationWillTerminate: 메서드에서 사용 되었다. 이것은 열린 세션에 연류되어 있는 객체들을 지워주는데  좋은 실행 방식이다.

 

#세션 토큰 캐싱

Facebook SDK는 디폴트로 FBSession의 토큰 관련 데이터를 캐시 해준다. FBSessionTokenCachingStrategy 클래스는 캐시된 데이터를 관리해주는 클래스이다. 데이터는 NSDictionary이며, NSUserDefaults 아래에 특정한 키로 저장이 된다. 개발자가 서버 같은 곳에 저장하는 거와 같이 스스로 캐시 데이터를 관리해도 된다. 커스터마이징 하면서 사용하기 위해서는 FBSessionTokenCachingStrategy를 상속받아 다음과 같은 메서드를 오버라이드 하면 된다.

  • cacheTokenInformation:이나 cacheFBAccessTokenData: -
  • fetchTokenInformation이나 fetchFBAccessTokenData -
  • clearToken -

FBAccessTokenData의 토큰 데이터는 사용자의 access_token와 유통기간은 언제까지인지 혹은 토큰이 언제 마지막으로 refresh되었는지에 대한 내용이나 어떤 타입의 로그인 과정을 걸쳤는지에 대한 추가적인 정보를 가지고 있다.

Facebook SDK가 토큰 캐시 데이터를 관리하게 되면 NSUserDefaults의 ‘FBSessionTokenCachingStrategy’ 키를 통해 저장되어진다. 키를 변경 하고 싶으면 FBSessionTokenCacingStrategy의 initWithUserDefaultTokenInformationKeyName: 메서드를 사용하여 조정할 수있다. Facebook SDK에서 사용하고 싶은 키를 직접 입력하여 사용이 가능하다.

[WWDC 2013 - 3] Advances in Objective-C

안녕하세요. 바티입니다.

오늘은 WWDC 2013에서 발표한 Objective-C의 새로운 점들을 소개해드리고자 합니다. 본 포스팅은 WWDC 2013에서 발표한 자료를 기반으로 작성된 자료입니다.

Screen Shot 2013-09-04 at 2.51.31 PM

Road Map

  • Modules
  • Better productivity
  • ARC improvements

‘Advances in Objective-C’에서 발표는 내용을 큰 분류를 한다면 다음과 같이 크게 세가지로 나눌 수 있을 것이다.

 

#Modules

문제점

사실 개인적으로 이번 발표를 통해 지금까지 컴파일러가 Compile time 전에 전처리를 어떻게 하고 있었는지 알게 되었다. 사실 ‘매크로’와 같은 간단한 코드들만 한다고 생각했었다. 컴파일 전처리 때에 여러가지 일을 하겠지만 해당 주제와 관련있는 ‘import’ 부분을 더 자세히 살펴보도록 하자.

Simple Diagram shows how pre-compiler works

 

위 그림은 ‘MyApp.m’이라는 구현 파일에서 ‘iAd.h’(라이브러리)를 임포트 했다고 가정했을 때 컴파일 전처리를 어떻게 하는지 보여주는 다이어그램이다. 단순히 ‘.m’에 임포트 되어 있는 파일을 ‘복사/붙여넣기’ 식으로 컴파일 타임 전에 이루어진다고 한다. 그렇기 때문에 라이브러리 파일 하나를 임포트 했다고 해서 한 개만 ‘.m’에 붙여넣기가 되는 것이 아니라 임포트한 파일이 또 임포트 하고 있는 파일들을 복사하고 붙여넣는다고 한다. 그렇기 때문에 실질적으로 적은 량의 구현 파일이라 할찌라도 정작 컴파일 때에 붙어있는 파일의 크기는 우리가 생각하는 것 이상으로 어마어마하다는 뜻이다.

Screen Shot 2013-09-04 at 3.02.21 PM

위 그림의 파란 부분이 ‘.m’파일의 실제 구현 소스의 양이고 초록색과 보라색의 부분이 라이브러리를 임포트한 소스의 양이다. 파일 하나만 그런 것이 아니라 n 개의 파일이 존재한다면 엄청난 양이 될 것이며 컴파일 하는 시간이 왜 오래걸리는지 알게 될것이다.

하지만 지금까지 이런 방식으로도 개발을 잘 해오지 않았는가? 그렇다면 무엇이 문제일까? 세션 발표에서 소개한 문제점은 매우 ‘깨지기’ 쉽다고 한다. 예를 들어 다음 코드를 보자.

  1. #define readonly 0×01
  2. #import &lt;iAd/iAd.h&gt;
  3.  
  4. @implementation AppDelegate
  5. //…
  6. @end

‘readonly’ 키워드를 define을 통해 ’0×01′으로 대처하는 코드를 넣었다고 가정하자. 빌드를 하기 전까지는 아무런 문법적 에러가 발생하거나 발견되어지지 않는다. 하지만 컴파일 전처리 과정에서 ‘iAd’의 코드가 ‘.m’파일에 복사되면서 다음과 같은 일이 발생한다.

Screen Shot 2013-09-04 at 3.09.35 PM

 

@property 옵션으로 ‘readonly’가 ’0×01′로 대처되어 버려 에러가 나는 장면이다.

코드의 양도 늘어나 포퍼먼스적으로도 문제가 생기며, 컴파일 전에 발견하기 어려운 에러 발생률도 높다는 점이 지금까지 우리가 안고 온 약점이자 부족한 점이라고 할 수있다.

이것을 보안하기 위해서 Apple은 ‘Module’이라는 메커니즘을 가지고 나왔다.

해결책

Screen Shot 2013-09-04 at 3.13.30 PM

 

위 슬라이드와 같이 ‘Module’은 프래임 워크를 은닉하고 미리 따로 컴파일이 된다고 한다. 해당 세션에서 언급은 하지 않았지만, 다른 세션에서 언급하기로는 ‘Module’ 메커니즘을 사용하기 위해서 별도의 데이터베이스가 존재한다고 들었다. 컴파일 전처리 때에 기존처럼 임포트한 파일들의 내용을 ‘.m’ 구현 파일에 복사/붙여넣는 것이 아니고, 미리 컴파일 된 자원을 가지고 적용한다는 말이다.

Screen Shot 2013-09-04 at 3.16.58 PM

 

그렇기 때문에 위 슬라이드가 설명하는 것 처럼 로컬 매크로 정의는 프레임 워크 API에 영향을 주지 않는다고 한다.

뿐만 아니라 라이브러리를 임포트 할 때 전체 라이브러리를 꼭 임포트하지 않아도 된다고 한다. 부분적으로 선택적 임포트가 가능하다고 한다.

뿐만 아니라 라이브러리를 적용할 때는 항상 설정에 가서 라이브러리를 링크시켜 주어야 하는 번거러움이 있었다. 하지만 이젠 그런 수고도 하지 않아도 된다고 한다.

Autolinking

Autolinking

사용법

Screen Shot 2013-09-04 at 3.21.19 PM

 

사용법도 간단하다. 위 슬라이드를 보면 ‘#import’문을 ‘@import’으로 변경해주면 된다. 물론 빌드 세팅에서 설정을 해주어야하며 iOS 7이상과 OS X 10.9 SDK 이상을 적용해야 사용이 가능하다고 한다.

빌드 설정

빌드 설정

이렇게 적용을 했을 때 우리가 얻을 수 있는 효과는 빌드 타임과 인댁스 타임이 줄어든다는 것을 다음 슬라이드들을 통해 강조한다.

Screen Shot 2013-09-04 at 3.24.04 PM

Screen Shot 2013-09-04 at 3.24.46 PM

요약

  • 프래임워크 사용 용이
  • 소스 도구의 성능 향상
  • 기존 코드 바꿀 필요 없음

 

# Better Productivity

Apple에서 더 나은 생산성을 제공해주기 위해 다음과 같은 네 가지를 제공한다고 한다.

  • 코드 모던화를 위한 툴 제공
  • 향상된 SDK
  • 안정적인 블럭 반환 타입
  • 런타임

코드 모던화를 위한 툴 제공

Screen Shot 2013-09-04 at 3.33.57 PM

작년 WWDC 2012에서 발표한 새로운 문법을 지원하기 위해서 IDE의 Refactor 부분에 기능이 추가되었는데 이 기능들을 통해 ARC를 적용한 문법을 자동으로 변화 시켜주거나, 모던형식의 문법으로 변환 시켜주는 기능을 제공 해주고 있다. (모던 형식의 문법은 작년에 발표된 부분이기에 여기에서 다루지 않겠다.)

향상된 SDK

Screen Shot 2013-09-04 at 3.38.00 PM

 

발표한 세션에서는 ‘New features and you’부분의 두 가지를 조금도 조명하여 설명하고 있다. 새로운 기능이기 때문인 것으로 보인다.

‘instancetype’ 키워드

여러분들은 Xcode 4에서 다음과 같은 코드를 테스트 해본적이 있는가?

  1. NSDictionary *dic = [NSArray array];
  2. NSLog(@"%@", dic);

위 코드를 Xcode 4에서 할 때와 Xcode 5에서 할 때는 차이가 있다. Xcode 4에서는 경고 메세지를 보여주지 않는다는 점이다. 그 이유는 ‘NSArray’의 array 메세지는 +(id) array;와 같이 ‘id’로 반환 타입을 정의하고 있기 때문에 경고를 보여주지 않는다. 하지만 Xcode 5에서는 ‘id’ 대신 ‘instancetyupe’이라는 키워드를 사용하여 반환 타입을 정의하는데, 이 키워드는 다음과 같은 특징을 가지고 있다고 한다.

Screen Shot 2013-09-04 at 3.49.37 PM

 

 

Explicitly-Typed Enums

이 기능은 글로벌 Enum 변수를 구분해주는 방법이다. 기존에는 보통 Enum을 변수명은 다르게 만들어도 실제 값은 보통 1에서 10 사이의 숫자로 정의하는 것이 통상적이다. 그렇기 때문에 다른 Enum 타입이라도 할당을 하거나 사용을 할 때면 코드 작성 타임에서는 컴파일러가 다른 종류의 Enum인 것을 알아차리지 못했다. Enum을 정의하기 위해 기존 방식대로 한다면 다음과 같다.

  1. enum {ABC, JKL, XYZ};
  2. typedef NSUInteger MyEnum;

하지만 새로운 문법을 통해 코드 작성 타임에도 경고를 줄 수 있게 되었다.

  1. enum MyEnum : NSUInteger {ABC, JKL, XYZ};
  2. typedef MyEnum MyEnum;

Garbage Collection

가비치 컬랙션은 오직 ‘맥’에서만 작동을 한다. 하지만 ARC가 그의 역할을 대처를 한다. 그뿐 만이 아니라 OSX 10.8에서는 Deprecated될 것이다. 뿐만아니라 AVKit, Accounts, GameController, GameKit, MapKit, Social, SpriteKit, 등 새로운 프래임워크에서는 지원을 하지 않는다. 그러므로 되도록이면 ARC를 사용하기로 하자.

ARC의 업데이트와 발전

Automatic Reference Counting은 객체 포인터를 몇 번을 사용하고 있는지 파악한 후 메커니즘에 의해서 카운터를 해재해주는 메모리 관리 방법이라고 할 수 있다.

코코아 프레임워크는 의미적인 reference counting 방식으로 디자인 되어졌다. 결정적으로 객체의 해재에 대한 순서가 중요하다. 그리고 디버깅 작업에 매우 용이하다.

ARC는 뛰어난 코코아 코드를 작성할 수 있도록 도와준다. 메모리에 대한 염두를 뒤로하게 만들어 자신의 로직에 더 집중할 수 있도록 도와준다.

성능은 계속적으로 향상되어 가고 있으며, ‘__weak’ 레퍼런스는 약 두 배정도 iOS 7과 OSX 10.9에서 빠르다. 뿐만아니라 디버깅 때에 사용할 메모리가 예상이 가능하다.

ARC를 적용하기 위해서는 다음과 같은 사항을 지키면 된다.

  • ‘retain / release / autorelease’를 제거해준다.
  • 빈 dealloc 메서드를 제거한다.
  • NSAutoreleasePool를 @autoreleasepool로 변경한다.

 

마치며…

객체지향의 모습을 갖추기 위해서 열심히 노력하는 C 언어 진형에서는 가장 큰 고민 거리는 메모리 문제가 아닐까 싶다. 완전한 C라면 할당 받은 것은 해제를 시켜주어야하는 번거러움이 당연하지만, 객체지향적 프로그래밍에서는 그것이 쉽지 않기 때문에 가비치 컬랙션이니, Reference Counting 메커니즘이니 하는 것들이 나오는 것이다. 지금 버젼의 언어도 충분히 완벽에 가까운 객체지향적 언어가 되어 있는 모습인다. 이번 업데이트를 통해서 개발자에게 해당되는 ‘개발자 경험’에 최대한 멋진 경험을 주기위해 여러가지를 시도하고 적용한 모습이다. (예를 들어 ‘모듈’의 등장을 봐도 그렇다.)

‘개발자 경험’가 좋아진다면 개발 결과물이 좋아질 것이고, 개발 결과물이 좋아진다면 사용자에게도 좋은 경험을 제공할 것이 분명하다. 그렇기 때문에 ‘사용자 경험’을 중요시 하는 애플에서 끊임 없이 ‘개발자 경험’에 대한 연구를 하고 발전시키는 것이 아닌가 싶다.

[관찰] Quiet Time with Facebook SDK -Part 1-1. Authentication (Login)-

Screen Shot 2013-09-03 at 11.11.11 AM

 

안녕하세요. 바티 입니다.

Web 2.0이라는 단어가 유명해지기 시작하면서 주목 받던 주제 중 하나가 ‘공유’였던 것으로 기억합니다. ‘공유’라고 하면 개발자들은 먼저 떠오르는 단어들 중 하나가 ‘Open API’이거나 ‘SDK’가 아닐까 싶네요. 구글과 페이스북 같은 대기업에서 부터 소규모의 스타텁까지 SDK 및 Open API를 제공하고 사용하기 위해서 혈안이 되어 있는 모습입니다. 그런 의미로 블로그를 통해 메이져 회사들의  SDK를 완전 분해해 보고자 합니다.

앞으로 완전분해 해보기 위해서는 세가지 툴을 사용할 것입니다. ‘관찰’, ‘해석’ 그리고 ‘비교’. 긴 여정이 될 것 같은데 먼저 긴 여행도 첫 발이 중요한 만큼 첫 시리즈로는 온라인에서  많은 사용자들로 부터 사랑을 받고 있는 ‘페이스북’ SDK입니다. 페이스북에서 제공해주고 있는 SDK와 함께 예제 소스를 통해서 어떻게 사용하는지, 또는 그 속은 어떻게 되어 있는지 한번 살펴보고자 합니다.

부족하거나 잘못된 부분 혹은 궁금한 부분이 있으시면 자유롭게 댓글 달아주시면 함께 고민해보아요.

본 포스트에서는 Facebook SDK를 사용하여 로그인 하는 방법을 알 아볼 것입니다. Login Dialog의 종류의 특징, 상황에 따른 SDK가 컨트롤하는 Login Dialog, 그리고 제공 받을 정보에 대한 Permission에 대한 내용 및 몇 가지를 다룰 것이다.

 

Facebook Login on iOS

페이스북 로그인은 타 개발사들이 ‘접근자 토큰’ (Access token)을 제공 해줌으로써 페이스북의 서비스를 사용할 수 있게 해준다. 이 기능을 자신의 로그인과 인증 시스텐에 넣어서 사용할 수 있다. 예를 들어 현재 시장에 올라오는 앱들을 살펴보면 대부분 ‘Login with Facebook’라고 새겨진 파란 버튼을 쉽게 찾아 볼수 있다. 이 버튼들은 대부분 자신이 만든 페이스북 버튼이거나 FBLoginView를 갖다 붙여 놓은 것들이다.

페이스북에서 제공하는 'FBLoginView'

페이스북에서 제공하는 ‘FBLoginView’

 

Login Dialog

자신이 직접 버튼을 만들었던, 페이스북에서 제공해주는 인증과정을 위한 Login Dialog는 다음과 같이 크게 5 가지로 구분할 수 있다.

  1. 페이스북 앱 Native Login Dialog
  2. iOS 6 Native Login Dialog
  3. Facebook 앱 웹 Login Dialog
  4. 모바일 Safari Login Dialog
  5. 임베디드 WebView Login Dialog

각 Dialog는 사용자의 iOS 설정 혹은 디바이스에 페이스북이 설치되어 있는 여부에 따라 보여지는 것이 달라진다. 그럼 이제 부터 각각의 Login Dialog에 대해 페이스북 문서에서 알려주고 있는 장단점과 Dialog에 대해 알아보자. 페이스북 개발자 사이트

#1. 페이스북 앱 Native Login Dialog 

Facebook App Native Login Dialog

Facebook App Native Login Dialog

100 퍼센트 Native이며 ‘Facebook’ 앱에서 열려지는 창이다. 이 방법을 사용하기 위해서는 ‘.plist’ 파일에 ‘FacebookDisplayName’이라는 이름 아래에 앱 이름을 정의를 해주어야 한다. 정의된 키의 값은 페이스북 개발자 사이트에 등록한 이름과 동일해야만 한다.

- 장점: 100 퍼센트 Native

- 단점: iOS 6.0 이상용 페이스북 앱이 설치가 되어 있어야만 한다.

 

 

 

 

 

 

#2. 페이스북 앱 Native Login Dialog 

iOS 6 Native Login Dialog

iOS 6 Native Login Dialog

이 Login Dialog 창은 Apple이 iOS 6에서 새로 선보인 ‘Facebook 통합’에 해당하는 기능이다. 먼저 사람들은 iOS 설정에서 페이스북을 설정해야만 사용이 가능하다. 해당 방법은 디바이스 차원의 인증 방법이기 때문에 사용자가 가지고 있는 모든 iOS에 설정 해야만 사용이 가능하다.

- 장점: *100 퍼센트 Native

*로그인 과정에서 앱을 벗어나지 않아도 됨.

- 단점: *iOS 6 이상 사용자에 한에 사용가능

*멋진 비주얼이 없다. (단순 UIAlertView로 뜸)

*프라이버시 옵션 선택이 불가능함.

 

 

 

#3. 페이스북 앱 웹 Login Dialog 

Facebook App Web Login Dialog

Facebook App Web Login Dialog

해당 Login Dialog창은 ‘페이스북’ Native 앱에서 열리며, 페이스북 앱의 버젼이 맞지 않을 때 웹 베이스의 컴포넌트에서 띄어준다.

- 장점: 모든 Facebook 앱 버전과 호환 가능함

- 단점: 웹 베이스 뷰는 사용자들에게 최상의 경험을 줄수 없다.

 

 

 

 

 

 

 

#4. 모바일 사파리 Login Dialog 

Mobile Safari Login Dialog

Mobile Safari Login Dialog

 

해당 Login Dialog 창은 모바일의 사파리 브라우저에서 열린다. 다른 Login Dialog 창의 최종 대안이 된다.

- 장점: 사용자 디바스에 설치되어 있는 페이스북 버전에 의존하지 않는다.

- 단점: *웹 베이스 뷰는 사용자들에게 최상의 경험을 줄수 없다.

*모바일 버전 사파리 브라우저에서 로그인을 해야하는 불편함.

 

 

 

 

 

 

#5. 임베드 WebView Login Dialog 

Embedded WebView Login Dialog

Embedded WebView Login Dialog

해당 Login Dialog 창은 SDK를 사용하여 개발하는 앱의 임베디드 WebView에서 나타나게 된다. 해당  Login Dialog를 띄우기 위해서는 트리거할 수 있는 코드를 작정해야한다.

- 장점: 다른 앱 전환 없이 사용 가능함.

- 단점: *웹 기반 뷰는 사용자들에게 최상의 경험을 줄수 없다.

* 사용자는 Login Flow를 탈 때마다 페이스북 ID와 비밀번호를 입력해야하는 불편함이 있다.

 

 

 

 

 

 

위에서 살펴본 다양한 Login Dialog 창들은 SDK를 적용하는 개발자는 FBSession open* 으로 시작하는 메서드를 사용하여 다양한 사용자 디바이스 환경에 대응할 수 있다. 가장 먼저 SDK는 해당 디바이스의 iOS 6 이상 버전인 것을 확인 할 것이고, 만약 사용 중이라면 OS 레벨에서 Facebook 계정을 연동하였는지 확인 할 것이다. 만약 사용한다면 iOS 6 Native Login Dialog 창을 띄어 줄 것이다. 하지만 iOS   버전이 지원을 안하든지, OS 차원에서 페이스북과 연동을 하지 않은 디바이스에서는 다음 단계의 대안(fallback)을 살펴볼 것이다. 페이스북 앱이 설치되어 있는지 볼 것이고, 되어 있다면 페이스북 앱의 버전을 확인하여 Facebook App Native Login DialogFacebook App web Login Dialog를 보여 줄 것이다. 만약 페이스북 앱도 깔려 있지 않은 디바이스에서는 최후의 선택으로 ‘모바일 사파리 Login Dialog’ 창을 띄어서 인증을 할 것이다. 이 외에도 ‘임베디드 WebView Login Dialog’ 창을 통하여서 개발 중인 앱을 빠져 나가지 않고 해당 앱에서 웹뷰 창을 띄어 줄 수 있는 대안도 있다.

만약 개발 중인 앱의 ‘.plist’ 파일에 ‘FacebookDiskplayName’ 설정을 추가하지 않았다면 로그인 과정은 다음 과정을 차례대로 시도하며 내려갈 것이다.

  1. iOS 6 Login Dialog
  2. Facebook App Web Login Dialog
  3. Mobile Safari Login Dialog

만약 ‘iOS 6 Login Dialog’를 건너뛰고 싶다면, FBSession 클래스 인스턴스의 openWithBehavior:completionHandler: 메서드에 FBSessionLoginBehavior 파라미터를 ‘FBSessionLoginBehaviorUseSystemAccountIfPresent’ 파라미터를 제외하고 다른 파라미터를 사용하면 된다.

 

  1. // Initialize a session object
  2. FBSession *session = [[FBSession alloc] init];
  3. // Set the active session
  4. [FBSession setActiveSession:session];
  5. // Open the session
  6. [session openWithBehavior:FBSessionLoginBehaviorWithNoFallbackToWebView
  7. completionHandler:^(FBSession *session, FBSessionState status, NSError *error){
  8.  
  9. // Respond to session state changes,
  10.  
  11. // ex: updating the view
  12. }];

위 코드를 사용했을 때 SDK의 로그인 플로우는 다음과 같을 것이다.

  1. Facebook App Native Login Dialog
  2. Facebook App Web Login Dialog
  3. Mobile Safari Login Dialog

 

구현

위에서 설명한 Login Dialog를 통한 인증 과정을 걸치기 위해서 실제적으로 적용해야하는 구현을 설명하겠다. 크게 두가지 방법으로 구현할 수 있을 것이다. FBSession 인스턴스를 사용하는 방법과 FBLoginView를 사용하는 방식이 있다. 먼저 인스턴스를 초기화와 동시에 적용하는 방법을 알아보자.

 

#1. FBLoginView를 사용하는 방법

  1. FBLoginView *loginView = [[FBLoginView alloc] init];
  2. loginView.readPermissions = @[@"basic_info"];
  3. [modalViewController.view addSubview:loginView];

위 코드는 FBLoginView를 초기화 하고 원하는 Permission을 설정한다음에 뷰단에 붙이는 작업이다. 위 코드를 적용하게 되면은 아래와 같은 ‘Login with Facebook’ 버튼이 자동으로 생길 것이다.

페이스북에서 제공하는 'FBLoginView'

페이스북에서 제공하는 ‘FBLoginView’

 

 

 

버튼을 누르게 되면 사용자의 상황에 맞는 Login flow가 실행되어 질것이다. 사용자가 가장 쉽게 적용할 수 있는 방법이라고 생각되어 진다. 코드상으로 addSubview:를 사용하여 적용할 수도 있고, 아니면 xib 파일에서 커스텀 버튼을 적용하는 방식으로도 사용이 가능하다.

 

#2. FBSession을 사용한 로그인

사용자는 FBLoginView에 상응하는 UI를 만들어주어야 한다. 페이스북 개발자 사이트에 나와있는 Tutorial에서 제안하는 방법으로 커스텀 “Login” 버튼의 핸들러에 다음과 같은 메서드를 적용해보자. Static 메서드를 통한 호출 방식과 인스턴스를 만들어 호출하는 방식 둘중에 아무구나 사용해도 무관하다.

* Static 메서드

  1. [FBSession openActiveSessionWithReadPermissions:@[@"basic_info", @"email"]
  2. allowLoginUI:YES
  3. completionHandler: ^(FBSession *session, FBSessionState state, NSError *error)
  4. {
  5. [self sessionStateChanged:session state:state error:error];
  6. }];

 

* FBSession 인스턴스 방식

  1. FBSession *session = [[FBSession alloc] init];
  2.  
  3. [FBSession setActiveSession:session];
  4. [session openWithBehavior:FBSessionLoginBehaviorWithNoFallbackToWebView
  5. completionHandler:^(FBSession *session, FBSessionState status, NSError *error)
  6.  
  7. {
  8. // Respond to session state changes
  9. // ex: updating the view
  10. }];

 

방식은 static method와 거의 흡사하다.

 

#3. 객체 초기화 후 Permission 받기

Facebook SDK는 사용자의 정보를 초기화할 때 요청할 수도 있고, 초기화 후 요청을 할 수도 있다. 초기화 후에 추가로 요청할 경우에는 위 코드와 같은 방식으로 요청을 하면된다.

  1. [FBSession.activeSession requestNewReadPermissions:@[@"user_likes"]
  2. completionHandler:^(FBSession *session, NSError *error)
  3. {
  4. // Handle new permissions callback
  5. }];

글 쓰기 Permission을 받기 위해서는 requestNewPublishPersmissions:defaultAudience:completionHandler 메서드를 통해 요청할 수 있다.

  1. [FBSession.activeSession requestNewPublishPermissions:@[@"publish_actions"]
  2. defaultAudience:FBSessionDefaultAudienceFriends
  3. completionHandler:^(FBSession *session, NSError *error)
  4. {
  5. // Handle new permissions callback
  6. }];

 

#4. 로그인 상태 체크하기

  1. if ([FBSession.activeSession isOpen])
  2. {
  3. // Session is open
  4. }
  5. else
  6. {
  7. // Session is closed
  8. }

 

#5. Access 토큰 받기

서비스나 앱의 특성에 따라 달라질 것이지만, 어떤 경우에는 서버에 Access 토큰을 저장해야 하는 경우가 있다. 이럴땐 다음과 같은 방법으로 토큰을 얻을 수 있다.

  1. NSString *accessToken = [[FBSession.activeSession accessTokenData] accessToken];

 

#6. 로그 아웃하기

로그 아웃하는 방법은 다음과 같다. 다음 코드를 사용하면 세션도 닫아주고 동시에 저장되어 있는 캐시도 지워준다.

  1. [FBSession.activeSession closeAndClearTokenInformation];

하지만 여러가지 이유로 캐시를 꼭 안지워 줄 때는 다음과 같이 close 메서드를 사용한다.

  1. [FBSession.activeSession close];

 

마치며..

Facebook SDK의 인증 부분을 분석하기 위해서 먼저 ‘관찰’이라는 방법을 통해 어떤 일을 하는지 알아 보았다. 이번 포스팅에서는 로그인에 관한 내용을 페이스북 개발자 문서에 설명한 순서대로 설명해보았다.

사용자가 처해있는 환경에 따라 알맞은 Dialog를 보여주는 SDK의 위력은 대단하다고 생각한다. 만약 SDK가 커버해주지 못한다면 앱 개발자들이 자신의 코드에서 대응해야 할 것인데 수고를 덜어줄 뿐만이 아니라 위대한 빙산의 일각을 보는 느낌이라고나 할까? 경의로움까지 느낀다. FBSession는 여러 상태를 가지고 있다.

다음 포스팅에서는 FBSession 객체의 ‘Status’ (상태)에 대해서 알아보자. 어떤 상태들이 있는지 알아보게 될 것이고, 어떤 상황에서 어떤 상태로 변화 되는지 알아보게 될 것이다.