[Summary] Effective Objective-C 2.0

A. Objective-C와 친해지자

 

1. Objective-C의 근본을 이해하라

- c 언어는 function calling, objective-c는 dynamic binding을 사용하는 messaging structure이다. 가장 큰 차이는 런타임 때에 어떤 객체의 함수를 호출할지 결정된다는 점이 다르다.
- Runtime은 무거운 집을 다 들고 있다고 보면 된다. 데이터 구조와 객체에 해당하는 함수에 대한 정보들이 이에 해당되며 메모리 관리 함수들도 포함되어 있다.
- Runtime은 모든 코드를 서로 연결해주며 dynamic library를 링크해준다. 그러므로 라이브러리가 업데이트되면 재 컴파일 없이 성능적 이점을 누릴수 있게 된다.
- objective-c의 객체는 항상 stack 메모리 영역이 아닌, Heap 메모리 영역에 쓰인다. (이를 가리키는 포인터 변수는 스택에 저장되자만 말이다.) 스택에 저장된 변수 포인터들은 32 비트 컴퓨터에서는 4 바이트를, 64 비트 컴퓨터에서는 8 바이트를 할당한다.
- Heap 메모리는 레퍼런스 카운팅이라는 매카니즘으로 Objective-C가 추상화 하여 관리한다. malloc이나 free 같은 함수를 사용하지 않아도 된다.

 

2. 헤더파일에 헤더파일 importing 최소화하기

- 헤더 파일에 import는 컴파일 타임 때에 해당 파일의 내용이 복사가 되어 첨부가 되어 진다. 그렇기 때문에 헤더 파일에 import 하는 행위는 최소화 해야한다.
- 헤더 파일에 @class 예약어를 사용하여 존재여부 정도만 알리고, 클래스의 자세한 내용을 감춘다. Implementation 파일에서는 import를 함으로써 클래스의 사용을 위해서 자세한 내용을 보여줘야 한다.
- @class를 하용하여 forward declaration을 사용하면 상호 참조하는 경우를 피할 수 있게 해주는 효과를 얻을 수 있다. 상호 참조를 할 때 #import를 #include 대신 사용하면 끝 없이 루프를 돌면서 헤더파일을 크게 만들 것이며 클래스 둘 중 하나는 제대로 컴파일이 안될 것이다.
- 하지만 헤더 파일에 꼭 import를 써야할 경우도 있다.

  1. 상속받을 때
  2. protocol를 구현해야 할 때

- Class continuation에는 Delegate같은 protocol를 선언 함으로, 헤더파일를 간편하게 할 수 있다.
- 위의 것들를 염두하여 개발하면, 문제를 만들 수 있고, 유지보수를 어렵게 하는 상호참조를 피할 수 있으며, 컴파일 타임을 줄일 수 있다.
 

3. Equivalent 방식 보다 Literal 문법 선호하기

- Literal 문법을 사용하여 소스코드의 양을 줄이고 가독성을 높여라.
- 하지만 모든 면에서 좋다고 할 수는 없다. 다음 코드를 참조하라.

  1. NSArray *arrayA = [NSArray arrayWithObjects:obj1, obj2, obj3, nil];
  2. NSArray *arrayB = @[obj1, obj2, obj3];

만약 위 코드에서 obj2, obj3가 nil이라고 하자, 그러면 arrayA는 @[obj1]가 생성이 될 것이지만 arrayB는 exception을 토해 낼 것이다.
 

4. 전처리 매크로 #define보다 타입 상수 선호하기

- 전처리 매크로인 #define은 전처리 때에 정의된 내용을 replace한다. 하지만 변수 타입이 의무화 되지 않기 때문에 각 가지 문제를 가지고 올 수 있다.
- 매크로 #define 대신에 타입형을 표시된 상수를 사용하라.
- 만약 글로벌 scope으로 상수를 선언해야 할 상황이 온다면 클래스 이름을 prefix으로 붙여서 사용해야 한다.
- 로컬 scope에서는 ‘k’를 prefix로 붙여서 네이밍을 하고, 글로벌 scope에서는 클래스 이름을 붙이는 코드 컨벤션을 지켜라.
- static과 const를 함께 사용하는 것이 중요하다. (static은 .m 파일을 오브젝트 파일로 컴파일할 때 로컬 변수의 영역을 할당하겠다는 뜻이다. const는 값을 한번 지정하고 변경하고자 할때 exception을 발생시킨다.) 만약 static을 붙이지 않으면 컴파일러는 external 지시어를 붙여 글로벌 scope으로 만들 것이다.
- NSNotification의 string 상수를 사용하고자 할때 쓰인다:

  1. //.h file
  2. extern NSString * EOCLoginManagerDidLoginNotification;
  3.  
  4. //.m file
  5. NSString *const EOCLoginManagerDidLoginNotification = @"EOCLoginManagerDidLoginNotification";

 

5. 상태, 옵션, status 코드는 enum을 사용하라

- NS_Enum, NS_Option 매크로를 사용하여 enum을 생성하자.

- switch 문을 사용하여 개발할 때 enum에 정의된 것을 빼놓고 빌드하면 경고 메세지가 뜬다.

  1. //Example of 'NS_Enum'
  2. typedef NS_ENUM(NSUInteger, EOCConnectionState){
  3.     EOCConnectionStateDisconnected,
  4.     EOCConnectionStateConnecting,
  5.     EOCConnectionStateConnected,
  6. };
  7. // is Equal To
  8. typedef enum EOCConnectionState:NSUInteger EOCConnectionState;
  9. enum EOCConnectionState:NSUInteger{
  10.     EOCConnectionStateDisconnected,
  11.     EOCConnectionStateConnecting,
  12.     EOCConnectionStateConnected,
  13. };
  14.  
  15. //Example of 'NS_Option'
  16. typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection){
  17.     EOCPermittedDirectionUp = 1 << 0,
  18.     EOCPermittedDirectionDown = 1 << 1,
  19.     EOCPermittedDirectionLeft = 1 << 2,
  20.     EOCPermittedDirectionRight = 1 << 3,
  21. };
  22. // is Equal To
  23. typedef enum EOCPermittedDirection:int EOCPermittedDirection;
  24. enum EOCPermittedDirection:int{
  25.     EOCPermittedDirectionUp = 1 << 0,
  26.     EOCPermittedDirectionDown = 1 << 1,
  27.     EOCPermittedDirectionLeft = 1 << 2,
  28.     EOCPermittedDirectionRight = 1 << 3,
  29. };

 
 

B. Object, 메세징, 그리고 런타임

 

6. Property 이해하기

- @property 지시어를 사용하면 클래스의 인스턴트 변수의 접근자를 자동으로 만들어주게 된다. (Auto-synthesis)
- Property는 object의 데이터를 은닉하는 기능을 제공한다.
- Dot syntax를 하용할 수 있게 해준다.
- 인스턴트 변수를 자동으로 생성해준다. 생성시 ‘_’를 prefix하여 만들어준다.
- 속성:

  • Atomicity: 기본값은 locking을 사용하는 atomic이다. 만약 nonatomic을 사용하면 locking을 사용하지 않는다.
  • Read/Write: [readwrite/readonly]
  • Memory-Management Semantics: [assign/strong/weak/unsafe_unretained/copy]
  • Method Names: [getter=/setter=]

- 생성자 함수 안에서 자신의 접근자 함수를 사용해서는 안된다. 서브 클래스가 접근자 함수를 변동하여 사용 할 수 있기 때문이다.
 

7. 내부에서 인스턴스 변수는 접근자 함수말고 직접 접근하라

- 기본적으로는 클래스 내부에서 인스턴스 변수를 가지고 올 때는 직접 가지고 오고, 값을 set할 때는 접근자 함수를 사용하라. 값을 가지고 올 때 접근자 함수를 사용하지 않는 것은 method dispatch를 걸치지 않고 가지고 올 수 있기 때문에 빠르기 때문이다. 그리고 값을 set할 때는 접근자 함수를 사용하면 디버깅할 때에 이점이 있으며 메모리 관리 및 KVO 매커니즘을 사용할 수 있다는 장점이 있다.
- 내부에서 직접 접근시

  • 빠르다
  • 메모리 관리 옵션과 무관하게 값을 할당할 수 있다.
  • KVO를 사용할 수 없다.

- 내부에서 접근자 함수를 사용시:

  • messaging dispatch가 일어나기 때문에 비용이 크다.
  • 메모리관리 가능하다
  • 디버깅에 용이하다.
  • KVO를 사용할 수 있다.

- 하지만, 예외도 있다. 생성자 함수에서는 접근자 함수를  사용하지 않는 것은 서브 클래스에서 setter를 덮어 쓸 수 있기 때문이다.
 

8. 객체의 Equality를 이해하라

- 커스텀 클래스를 만들 때 isEqual 함수를 만들어 사용하라. deep comparison과 shallow comparison이 있다.
 

9. 클러스터 패턴을 사용하여 구현을 숨기라

  1. //—- Base-class —-
  2. typedef NS_ENUM(NSUInteger, EOCEmployeeType){
  3.     EOCEmployeeTypeDeveloper,
  4.     EOCEmployeeTypeDesigner,
  5.     EOCEmployeeTypeFinance,
  6. }
  7.  
  8. @interface EOCEmployee: NSObject
  9. @property(copy) NSString *name;
  10. @property NSUInteger salary;
  11.  
  12. + (EOCEmployee *)employeeWithType:(EOCEmployeeType)type;
  13. - (void)doADayWork;
  14. @end
  15.  
  16. @implementation EOCEmployee
  17. + (EOCEmployee *)employeeWithType:(EOCEmployeeType)type{
  18.     switch(type){
  19.         case EOCEmployeeTypeDeveloper:
  20.         return  [EOCEmployeeDeveloper new];
  21.         break;
  22.         case EOCEmployeeTypeDesigner:
  23.         return [EOCEmployeeDesigner new];
  24.         break;
  25.         case EOCEmployeeTypeFinance:
  26.         return [EOCEmployeeFinance new];
  27.         break;
  28.     }
  29. }
  30.  
  31. - (void)doADayWork{
  32. }
  33. @end
  34.  
  35. //—- Sub-class —-
  36. @interface EOCEmployeeDeveloper: EOCEmployee
  37. @end
  38.  
  39. @implementation EOCEmployeeDeveloper
  40. - (void)doADayWork{
  41.     [self writeCode];
  42. }
  43. @end

 

10. Associated Object를 사용하여 클래스에 데이터 붙이기

- void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)를 통해서 객체에 데이터를 할당할 수 있다.
- void objc_getAssociatedObject(id object, void *key)를 통해서 담겨져 있는 데이터를 불러 올 수 있다.
- NSDictionary와 비슷한 방법으로 key를 넣어 데이터를 저장 붙일 수 있는데, 다른 점이 있다면 key는 set에 사용했던 key 포인터를 써야한다.
- objc_associatedObject는 다음과 같은 Policy를 사용하여 메모리 관리를 하게 된다.

  • OBJC_ASSOCIATION_ASSIGN: assign
  • OBJC_ASSOCIATION_RETAIN_NONATOMIC: nonatomic, retain
  • OBJC_ASSOCIATION_COPY_NONATOMIC: nonatomic, copy
  • OBJC_ASSOCIATION_RETAIN: retain
  • OBJC_ASSOCIATION_COPY: copy

- objc_associatedObject는 최후의 수단으로 사용을 해야한다. 버그가 생기면 찾기 힘든 포인트가 되기 때문이다.

  1. //Objc_association의 사용 예제
  2. // Test.m
  3.  
  4. #import <objc/runtime.h>
  5.  
  6. static void *TESTMyAlertViewKey = "TESTMyAlertViewKey";
  7.  
  8. - (void)askUserAQuestion{
  9.     UIAlertView *alert = [[UIAlertView alloc]
  10.                             initWithTitle: @"Question"
  11.                             message: @"What do you want to do?"
  12.                             delegate: self
  13.                             cancelButtonTitle:@"Cancel"
  14.                             otherButtonTitles:@"Continue", nil];
  15.  
  16.     void (^block)(NSInteger) = ^(NSInteger buttonIndex){
  17.         if(buttonIndex == 0){
  18.             [self doCancel];
  19.         }else{
  20.             [self doContinue];
  21.         }
  22.     };
  23.  
  24.     objc_setAssociatedObject(alert, TESTMyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
  25.     [self show];
  26. }
  27.  
  28. - (void)alertView:(UIAlertView *)alertView
  29.         clickedButtonAtIndex:(NSInteger)buttonIndex{
  30.  
  31.     void (^block) (NSInteger) = objc_getAssociatedObject(alertView, TESTMyAlertViewKey);
  32.  
  33.     block(buttonIndex)
  34. }

 

11. objc_msgSend의 역할 이해하기

- 메세지는 다음과 같은 것을 포함 하고 있다.

  • 이름
  • selector
  • 인수(arguments)
  • 반환타입(없을 수도 있다)

- c로 만들어진 objc_msgSend 함수는 해당 객체의 메서드 테이블에 가서 해당 메서드가 있는지 확인하고 있다면, 그리로 껑충 뛰어서 코드를 진행하게 된다. 만약 찾지 못했다면 상속관계도를 따라 올라가 메서드가 있는지 확인하게 된다. 만약 그래도 없다면 message forwarding이라는 메커니즘이 끼어들게 된다.
- 함수 호출 한번에 여러 단계가 있는 것 처럼 보인다. 하지만 각 클래스 마다 캐시를 사용하여 한번 호출 된 것은 저장하였다가 사용하기 때문에 같은 객체의 같은 이름의 함수를 사용하고자 한다면 처음보다 빠르게 실행할 수 있다.

  1. id returnValue = objc_msgSend(someObject,
  2.                               @selector(messageName:),
  3.                               parameter);

 

12. Message Forwarding 이해하기

- 메세징 디스패치는 런타임에서 이루어진다. 그렇기 때문에 때로는 존재하지 않는 메서드 메세징을 시도하는 실수를 범하기도 한다. 이를 핸들링하기 위해 message forwarding 매커니즘이 존재한다.
- 핸들링하는 세가지 스탭이 있다.

  1. Dynamic method resolution: resolveInstanceMethod: 사용
  2. Replacement Receiver: – (id)forwardingTargetForSelector:(SEL)selector 사용
  3. Full Forwarding 메카니즘: – (id)forwardInvocation:(NSInvocation *)invocation 사용

- 위 핸들링을 시도해도 정의되어 있는 조치가 없으면 예외처리를 던지게 된다.

 

13. Method Swizzling을 통해 메서드 구현을 바꿔치기 하자

- 클래스의 implementation은 ‘IMP’라고 하는 함수 포인터로 저장되어 있다. 오프젝티브 씨에서는 Dynamic messaging system에서 해당 함수 포인터를 참조하여 메세지를 보내는 방식을 취하고 있다.
- 클래스마다 메서드 명들이 기술되어 있는 테이블를 조작할 수 있는 몇몇 함수들이 존재한다. 추가, 기존 implementation의 함수 포인터 가지고 오기, 바뀌치기등 몇몇 조작이 가능한데, ‘void method_exchangeImplementations(Method m1, Method m2)’가 그중의 일부이다.
- Method Swizzling은 디버깅를 위한 의도로 사용되는 것이 가장 적합하며, 가장 유용하게 사용될 수 있다.

  1. @interface NSString (EOCMyAdditions)
  2. - (NSString *)eoc_myLowercaseString;
  3. @end
  4.  
  5. @implementation NSString (EOCMyAdditions)
  6. - (NSString *)eoc_myLowercaseString{
  7.     NSString *lowercase = [self eoc_myLowercaseString];
  8.     NSLog(@"%@", lowercase);
  9.     return lowercase;
  10. }
  11. @end
  12.  
  13. Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString));
  14. Method swappedMethod = class_getInstanceMethod([NSString class], @selector(eoc_myLowercaseString));
  15.  
  16. method_exchangeImplementations(originalMethod, swappedMethod);

- 위 코드를 살펴 보면 재귀 호출을 하는 것 처럼 보이지만 사실은 메서드 스위즐링을 하고 나서 이 함수의 IMP는 기존의 lowercaseString를 가리키고 있다는 것을 기억하자.
 

14. Object 이해하기 (Introspection 메서드를 최대한 사용하자)

- 오브젝트의 타입은 컴파일 타임이 아닌 런타임에 결정이 된다.
- id 타입을 사용하여 컴파일 타임에 dynamic programming을 할 수 있는데, id는 컴파일 타임때 어떤 메서드들를 호출 가능하게 구현이 되어있다. 하지만 특정 메서드 호출의 시도는 런타임으로 가지고 간다.

  1. NSString *stringType = @"Some string";
  2. id genericType = @"Some string";

- 위 예제 코드에서 genericType는 의미상 NSString 포인터이다. 하지만 다는 점이 있다면 컴파일 타임에 NSString에 기술되어 있는 메서드가 있는지 없는지 알고 있다 없다의 정도의 차이이다.
- NSObject의 프로토콜에는 introspection이라고 불리는 런타임 때에 오브젝트 타입을 확인할 수 있는 메서드를 제공하고 있다.
  1. NSMutableDictionary *dict = [NSMutableDictionary new];
  2. [dict isMemberOfClass:[NSDictionary class]];//NO
  3. [dict isMemberOfClass:[NSMutableDictionary class]];//YES
  4. [dict isKindOfClass:[NSDictionary class]];//YES
  5. [dict isKindOfClass:[NSArray class]];//NO

 
 

C. API 디자인와 인터페이스

 

15. 클래스 이름 앞에 자신만의 Prefix를 붙이자

- Objective-C에서는 name space 지원을 하지 않는다.
- Open Source 라이브러리를 사용하면, dynamic loader를 통해 적재되기 때문에 개발시 이름 충돌을 모를 수 있다.
- Apple에서 두 글자 Prefix를 예약했기 때문에 세글자 prefix를 사용해야 한다.
 

16. Designated 생성자를 만들어라

- 다양한 모든 생성자는 designated 생성자를 통해 작동해야 한다.
- 부모의 designated 생성자는 꼭 사용에 맞게 override를 해서 사용해야 한다.

  1. @interface EOCRectangle: NSObject <NSCoding>
  2. @property (nonatomic, assign, readonly) float width;
  3. @property (nonatomic, assign, readonly) float height;
  4.  
  5. - (id)initWithWidth:(float)width andHeight:(float)height;
  6. @end
  7.  
  8. @implementation EOCRectangle
  9. - (id)initWithWidth:(float)width andHeight:(float)height{
  10.     if((self = [super init])){
  11.         _width = width;
  12.         _height = height;
  13.     }
  14.  
  15.     return self;
  16. }
  17.  
  18. - (id)init{
  19.     return [self initWithWidth:5.0f andHeight:10.0f];
  20. }
  21.  
  22. - (id)initWithCoder:(NSCoder *)decoder{
  23.     if((self = [super init])){
  24.         _width = [decoder decodeFloatForKey:@"width"];
  25.         _height = [decoder decodeFloatForKey:@"height"];
  26.     }
  27.     return self;
  28. }
  29. @end
  30.  
  31. //====================================================================
  32.  
  33. @interface EOCSquare : EOCRectangle
  34. - (id)initWithDimension:(float)dimension;
  35. @end
  36.  
  37. @implementation EOCSquare
  38. - (id)initWithDimension:(float)dimension{
  39.     return [super initWithWidth:dimension andHeight:dimension];
  40. }
  41.  
  42. - (id)iniWithWidth:(float)width andHeight:(float)height{
  43.     float dimension = MAX(width, height);
  44.     return [self initWithDimension:dimension];
  45. }
  46.  
  47. - (id)initWithCoder:(NSCoder *)decoder{
  48.     if((self = [super initWithCoder:decoder])){
  49.  
  50.     }
  51.     return self;
  52. }
  53. @end

 

17. description 메서드를 작성하라

- LLVM 디버깅 명령어 중 po(print object)를 사용할 때 description 메서드를 호출하기 때문에 커스텀 클래스는 description 메서드를 작성하는 것이 좋다.
 

18. Immutable Object를 선호하라

- @property의 readonly라 하여도 KVO를 통해서 변경이 가능하다. eg. [obj setValue:@"abc" forKey:@"identifier"];
- mutable 변수를 @property로 사용할 때 직접 값을 변경하게 말고, add, remove 함수를 사용해서 간접적으로 접근하도록 하라.
 

19. 명확하고 일관적인 네이밍을 사용하라

- 긴 이름의 함수명을 짓는 것을 꺼리지 마라.
- 하지만 쓸데 없이 긴 이름은 피하라.
- 네이밍 Tip (메서드)

  1. 초기화하는 팩토리 메서드의 이름은 객체 타입을 시작으로 함수명을 지어라.
  2. parameter는 명사로 지어라.
  3. 뭔가 실행하는 함수는 <동사 + 명사>, 이런식으로 이름을 지어라.
  4. 줄임말은 피하라.
  5. boolean 타입의 반환 타입의 메서드 명은 is나 has로 시작하는 이름을 지어라.
  6. get으로 시작하는 메서드 이름은 Accessor 메서드에게 양보하라.

 

20. private 메서드의 이름에 prefix를 붙여라

- public 메서드와 private 메서드를 나누어 표히하면 디버깅이 쉬어진다.
- 리팩토리시 메서드 이름이나 signature를 바꿀 때 더 쉽게 구별이 가능하다.
- 싱글 언더스코어 “_” 로 시작하는 prefix은 Apple에서 예약을 했다. 피하라.
 

21. Objective-C의 Error 처리 방식을 이해하라.

- ARC는 예외처리 안전 모드가 기본값이 아니다.
- 메모리에 할당된 resource가 @try로 인해서 release 타이밍을 놓치게 될 수도 있다.
- exception은 치명적인 에러에만 사용하라. (fatal error)
- 일반적인 에러 처리 방법

  1. delegate으로 error를 전달하라
  2. out 파라미터로 메서드에 전달하라. eg. – (BOOL)doSomething:(NSError **)error

 

22. NSCopying 프로토콜 이해하기

- -(id)copyWithZone:(NSZone *)zone을 사용하여 객체를 복사하라.
- 최근 방식은 zone은 하나만 사용되어 지고 있다.
- copy 메서드를 오버라이드 하지마라. 대신 copyWithZone:을 구현하라.
- deep copy / shallow copy를 구분하라.
 
 

D. 프로토컬과 카테고리

 

23. Object 내부간의 통신은 delegate이나 Data source 프로토콜을 이용하라

- delegate 패턴은 데이터를 비즈니스 로직으로 부터 분리해준다.
- delegate은 @property의 weak으로 선언하여서 non-owning 관계를 유지하는 것이 중요하다.
 

24. Category를 사용하여 방대한 클래스를 나누어라

 

25. 항상 서드 파티 라이브러리의 Category는 prefix을 붙여 사용하라

- Category는 마지막으로 추가된 것이 최종으로 override하게 된다. 그러므로 prefix을 하용하여 혼란을 방지해야한다. 그래서 메서드 이름 앞에 prefix를 붙이고 category 이름도 다르게 하는 것이 좋다.
 

26. Category에서는 @property를 피하라

- 모든 property는 메인 클래스에서 선언하여 사용하라.
- category는 functionality 확장으로만 봐야지, 데이터 확장은 피하는 것이 좋다.
- 때로는 readonly property는 category에 넣어도 좋다.
 

27. Class-Continuation을 통해 구현을 감추어라

 

28. Protocol을 통해 무명의 객체를 제공하라

- eg. @property id<BTBCompletionHandler> delegate;
 
 

E. 메모리 관리

 

29. Reference Counting 이해하기

 

30. ARC를 사용하라

 

31. Reference를 release하고 Observation을 해지하는 것은 dealloc에서만 하라

- dealloc에서 NSNotification, KVO등을 해지하라.
- OS 자원인 file description이나 DB관련 객체도 dealloc에서 해재하라.
- dealloc에서 메서드 호출을 피하라.
 

32. Exception 안전 코드와 함께 메모리 관리하기

- (수동 레퍼런스 카운트 관리) try문 안에 retain하는 객체는 코드 진행중 exception 발생으로 release 할 타이밍을 놓칠 경우가 생길 가능성이 있다. Finally 구문으로 객체 해제를 해야한다.
- ARC에서 기본 값으로 finally 구문에 retain 된 객체를 해지하지 않는다. -fobj-arc-exception 컴파일러 플래그로 컴파일 타임에 release코드를 포함시켜 주자.
 

 33. Retain cycle을 피하기 위해 Weak reference를 사용하라

- 만약 own하지 않는 것은 retain하지 말라.
 

34. Autorelease Pool Block 사용하기

- 반드시 꼭 자신만의 auto release pool를 만들어서 사용해야 하는 것은 아니다.
- @autoreleasepool 문법을 사용하여 Loop 구문에 들어 가있는 tmp 변수들을 정리해주자.
 

35. Zombie 기능을 사용하여 메모리 관리를 하자

- Zombie 객체를 알아보는 디버깅 툴을 사용하여 retain되어 release되지 않는 코드를 알아보고 최적해보자.
 

36. retainCount 메서드 사용을 피하자

 
 

F. 블럭과 GCD 이해하기

 

37. Block 이해하기

- 블록은 다른 값과 맞찬가지로 자신만의 타입을 가지고 있다. Primitive 타입 변수나 Reference 타입 변수과 같이 변수와 연계할 수 있고 다른 변수 처럼 사용될 수 있다.
- 문법: return_type (^block-Name) parameters
- 블럭 밖에서 선언된 변수를 블럭 안에서 수정하고자 한다면 ‘__block’수식어를 통해 표시해야 한다.
- 블럭도 reference 카운팅을 하여 memory 관리에 속하게 된다.
- 블럭 안에서 instance 변수를 ‘__block’ 수식어 없이 사용가능 한데, self와 함께 capture된다.
 

38. 자주 사용하는 블럭 타입을 typedefine을 사용하여 선언하자

- #typedefine은 다른 타입의 alias를 제공한다.

eg. typedef int(^EOBlockName)(BOOL flag, in val) //선언

EOBlockName block = ^(BOOL flag, int val){….} // 사용
 

39. 코드 분리를 줄이기 위해 핸드러 block을 사용하라

- delegate 패턴으로 처리하여 코드가 여러 곳으로 분리되는 것은 직관적이지 못하다. 그리고 또한 delegate 함수를 여러 경우에 사용할 경우 구분해줘야 하는 코드가 추가적으로 구현되어 있어야 한다. 이럴때 블럭을 사용하여 직관적이고 표현력을 높히는 방법을 사용하는 것이 좋다.
- 블럭 처리의 두 가지 종류:

  1. startWithCompletion:failure:
  2. startWithCompletion: (하나의 블럭안에 ^(data, error))

 

40. Block이 객체를 소유하고 있어 생기는 retain cycle를 피하라

 

41. dispatch queue를 사용하여 스레드 lock을 실행하라

- GCD 출연 전 통상적으로 사용하던 방식

  1. @synchronized(self)를 통해 critical section 구현하기
  2. [NSLock() lock] / [NSLock() unlock] 메서드 직접 사용하기

- concurrent queue와 barrier_block이 추천하는 조합이다.
 

42. performSelector보다 GCD를 선호하라

 

43. GCD와 Operation Queue를 구분하여 사용하라

- GCD 강점:

  1. GCD는 C library이다.
  2. synchronization (critical section)을 구현할 경우 좋다.
  3. dispatch_once를 통해 코드 한번만 실행하기
  4. lightweight한 작업만 사용할 것을 추천한다.

- NSOperation 장점:

  1. cancelling operations
  2. operation dependencies
  3. KVO를 적용된 Operation property를 사용할 때: NSOperation은 각 단계에 있어 flag를 제공한다. 이때 KVO를 사용해 flag의 변화에 귀를 기울이자.
  4. 우선순위: GCD에서는 Queue 자체에 우선순위를 줄 수 있지만, 각 Task에 우선순위를 줄 수 없다.
  5. 재사용성 용이

 

44. Dispatch_Group을 사용하라

 

45. Thread safe한 단한번의 실행을 위해 dispatch_Once를 사용하라

- token은 반드시 static이나 global scope의 변수여야 한다.

46. dispatch_get_current_queue의 사용을 피하라

- 현재 deprecated 되었다. 디버깅을 위해서만 사용하라.
- dispatch queue는 내부적으로 hierarchy 구조로 설계되어 있으므로 단순히 해당 메서드를 사용하여 현재 사용중인 큐를 알아내는 것은 무의미 하다.
 
 

G. 시스템 프래임워크

 

47. System 프래임워크와 친해져라

- toll-free bridging은 CoreFoundation와 Foundation 프래임워크를 이어주는 역할을 한다.
 

48. Loop 대신 enumeration을 선호하라

- 통상적으로 사용하는 loop 방식은 extra 변수를 할당해야 하며 처리해야한다. 대신 enumeration 메서드를 사용하면 보다 쉽고 메모리 관리에 용이한 방법을 사용할 수 있다.
- enumerateObjectsUsingBlock:
- enumerateKeyAndObjectsUsingBlock:
 

49. Collection 타입의 자원의 메모리 관리를 위해 Toll-Free Bridging을 사용하라

- Toll-free bridging 은 foundation에서 정의한 objective-C 클래스와 CoreFoundation에 정의한 C 데이터 구조를 형볌환해주는 중요한 역할을 해준다.
 

50. NSDictionary 대신 캐시 역할을 해주는 NSCache를 사용하라

- NSCache는 memory가 꽉차면 자동으로 저장되어 있는 데이터를 release해준다. (사용한지 오래된 데이터 순서로)
- NSCache는 copy하지 않고 retain한다.
- Thread safe하다.
- NSCache 대신 NSPurgeableData를 사용해도 된다.
 

51. initialize 함수와 load 함수 사용을 선호하라

- load 메서드 사용은 디버깅 목적으로만 사용하는 것이 좋다.
- initialize는 클래스가 사용되기 전 단 한번 불려진다.
 

52. NSTimer는 자신의 타겟을 retain한다는 것을 잊지 마라

 

Leave a Comment.