[Clean Code] 클린 코드 법칙

개발자 누구라면 깔끔한 코드를 원할 것이다. 나는 평소에 리팩토링, 클린 코드등에 괜히 관심히 많았다. (그렇다고 내 코드가 깨끗하다고는 못하겠다.) 처음으로 HOWTO 같은 종류가 아닌 개발 서적을 처음으로 산 책이 로버트 마틴의 ‘클린 코드’이다. 몇주 전 사내에 로버트 아저씨(실재로 백발의 할아버지이다.)가 같은 주제로 자신이 만든 동영상 시리즈 링크가 돌길래 하루에 한개씩 1.5배 속도로 시청하고 있다. 책에 나온 이야기를 재미있게 설명하고 있어서 귀에 속속 들어왔다. 한번 보고 스쳐 지나간다면 또 까먹을지 모른다는 두려움으로 노트에 핵심 포인트만 작성하기 시작했고, 노트도 나중되서 없어지만 안되겠다는 생각에 블로그에 정리를 한번 해보려고 한다. 이 글을 읽고 클린 코드에 관심이 생긴다면 책도 한번 사보길 권한다. 팀 단위로 일하는 소프트웨어 엔진이어라면 꼭 권하고 싶다. 링크

네이밍 (Naming)

  • 좋은 고드란? 사람들이 예상한 대로 작동하는 코드이다.
  • 좋은 네이밍은 타인과 소통할 수 있는 기본적이고 가장 중요한 도구이다.
    • 네이밍에서 의도가 나타나야 한다.
    • 해결하고자 하는 문제가 묘사되어야 한다.
    • 잘못된 정보는 절대 넣지 마라.
    • 클래스명, 변수명은 명사를 사용하여 작명하라.
    • 함수명은 동사로 시작해야 한다.
    • enum은 형용사로 시작한다.
    • 발음이 가능한 단어를 사용하자.
    • 변수의 타입을 나타내는 Prefix은 이제 그만 사용하자. 발달한 IDE가 해결해준다.
  • 이름이 사용되는 범위에 맞게 작명해야한다.
    • 변수는 범위가 광범위 할 수록 의도와 정확한 정보로 비교적 길게 작명하여 어디에서든지 이해가기 쉽게 만드는 것이 좋다.
    • 변수의 범위가 작을수록 간단하고 짧게 만드는 것이 가독성을 높이는 것에 좋다.
    • 클래스와 함수는 변수와 반대 되는 법칙을 따른다. 사용되어지는 범위가 넓을 수록 이름을 짧게 짓고, 범위가 좁을 수록 길게 지어서 정확히 무슨 일을 하는 묘사하자.

함수 (Function)

  • 함수의 크기가 작으면 작을 수록 좋다. 얼마나? (4줄~6줄)
  • 작명을 잘해야 한다.
  • 한가지 일만을 하자. 한가지 일만 하는지 알기 위해서 코드를 추출 (extract)이 안될 때 까지 추출한다.
  • 작은 크기의 함수들이 많아지면 함수 호출 오버해드나 가독성이 떨어진다고 생각한다면 그것은 오해이다.
    • 작은 크기의 좋은 작명이 된 함수가 많다는 것은 복잡한 길에 가고자 하는 길을 안내해줄 표시판이 많은 것 처럼 좋은 일이다.
    • 함수 호출 오버해드 (function call overhead)가 걱정되는가? 현대시대의 개발장비 성능은 옛 장비와 다르게 빨라졌기 때문에 나노 초도 걸리지 않으며, 여러 사람과 함께 용이 작업에 있어서 높은 가독성이 가져다 주는 혜택이 더 크게 보여진다.
  • 큰 함수의 구현을 클래스로 추출하여 크기를 줄여보자. 호출하는 곳에서는 새 클래스의 invoke()를 호출하고 중복 코드를 하나로 묶고, 더 이상 줄일 수 없을 때 까지 줄여 작은 단위의 함수로 구현을 분배하자.

함수 구조 (Function Structure)

  • 함수의 인자(Argument)의 갯수는 적을 수록 좋다. 1~2개가 적당하다. 3개 이상이 되면 오프젝트로 묶어서 전달하자.
  • output 인자는 절대 사용하지 말자. 전달된 인수의 필드나 프로퍼티를 변경하여 함수 밖에서의 사용하지 말자.
  • 함수 인자로 Boolean 타입의 인자는 사용하지 말자. Boolean 타입의 인자를 사용한다는 것은 함수 내에서 2가지 이상을 하겠다는 것을 공식적으로 선언한 것이기 때문이다. 함수는 한가지 일만 하는 것을 원칙으로 하자.
  • Nullable 인자는 지향하자. 물론 Open Source과 같이 불특정 인원이 어떤 것을 전달할지 모를 때면 사용하여 방어적 코드를 짜야겠지만, 팀내에서는 합의하에 절Nullable 인자를 사용하지 않고 공격적인 코딩 스타일을 고수하자.
  • Step Down 법칙: Public 변수나 메소드를 위에 넣고, private 메서드를 아래에 넣는 코드 컨밴션은 유행이 한참 지난 스타일이다. 잡지나 신문을 보면 제목이 나오고 자세한 내용은 그 후에 따라 나온다. 이와 같이 하면, 비록 Public 메서드들이 한눈에 안들어 올 것이다. 하지만 코드의 가독성을 위해서 Step Down 법칙을 추천한다.

Step Down Rule

 

  • Switch-case 문: 사용을 지향한다. Switch 문을 사용하게 되면 코드가 의존도가 높아진다. 대신하여 Polymorphism 방식을 사용하자.

No Switch statement

  • Command & Query: 함수의 특성을 광범위적으로 보면 크게 두가지 종류로 구분하여 사용하는 것을 추천한다.
    • Command : 반환 값은 없어야 하며 어떤 행동, 혹은 명령을 실행하는 함수라고 할 수 있다. 예를 들어 authenticator.login()라는 함수는 로그인을 하는 행동을 하는 함수이다. 꽤 자주 많은 곳에서 login과 같은 메서드에 User 오브젝트를 반환하는 경우를 볼 수 있다. 명령 성질의 함수에 반환을 하게 되면 Query 종류의 함수도 아니고 Command 종류의 함수도 아닌 두 가지 일을 하는 함수가 되어 버린다. 물론 Nil을 반환하여 로그인 실패를 체크하려는 의도도 있을지 모른다. 하지만 이런 박쥐 같은 함수는 타 개발자에게 혼란을 가중시킨다.
    • Query: 값을 반환하는 함수를 말한다.
  • Null를 반환하지 말자. 만약 Null이 될 것 같으면 Exception을 던지자.
  • try문은 함수의 가장 위에 사용하자. 여기에 언급되는 함수 구조의 룰은 전부 함수가 한가지 일만을 명확하게 하고 짧은 함수들을 전재를 한다.

 

형식 (Form)

  • 모든 파일의 형식은 일관성이 중요하다. (인던테이션, 줄 바꿈, 주석 등)
  • 주석: 오래된 레가시 코드에는 업데이트를 하지 않은 주석이 많다. 주석은 Public API 문서에서는 불특정 다수에게 유용함을 주지만 코드 자체가 주석을 대신해야 좋은 코드이다.
  • 파일 크기는 최대한 작을 수록 좋다. 500 줄 이상이 되지 않게 조심하자.
  • 적절한 코드 가로 길이는 화면에 보이는 곳 까지이다. 줄 바꿈 없이 한 눈에 코드가 읽혀지는 것이 좋다.
  • 실제 구현에서 직접적으로 구현체 클래스 (concrete class)를 호출하기 보다는 추상 클래스를 호출함으로써 확장성 확보하자.

TDD (Test Driven Development)

  • 개발자는 현존하는 코드에 손을 대길 꺼려 한다. 그 이유는 잘 돌아가던 것이 깨질까 봐 그렇다. 하지만 코드가 깨지는 지 안깨 지는 보장 받을 수 있으면, 지저분한 코드를 더 깔끔한 코드로 바꾸려고 노력할 가능성이 높다. 그래서 테스트가 필요하다.
  • 그런데 왜 TDD인가? 이름에서 느낄 수 있는 것 처럼 Test code의 중요성은 아무리 강조해도 부족하다. 중요한 것을 먼저 짜는 것이 좋은 습관이다. Production code를 먼저 짜게 되면 Test case를 소흘하게 여길 것이다. 뿐만 아니라 좋은 설계를 갖게 될 것이다. 좋은 설계는 유연하고, 유지보수 가능해야하며, 확장 가능해야 한다. 그러기 위해서 코드가 깨지는 지 안깨지는 확신을 해야한다.
  • TDD의 세가지 법칙
    • 실패한 Test case를 패스하기 위해 만든 것 외에는 절대 Production code를 짜서는 안된다.
    • 실패할 만큼만 Test case를 짠다.
    • 실패한 Test case가 패스 할 만큼만 Production 코드를 짜라
  • 순서 (Red -> Green -> Refactor)
    • Red: 실패할 만큼만 Test case를 짠다.
    • Green: 실패한 Test case가 패스 할 만큼만 Production 코드를 짜라.
    • Refactor: Test case를 포함해서 코드를 정리하라.

설계 (Architecture)

  • 소프트웨어 개발에 있어 훌륭한 설계는 최대한 개발환경에 대한 결정을 안 할수 있는 설계이다. 개발을 시작하기 전에 우리는 Use case 보다 사용할 Tool과 Framework에 얽매이는 경우가 많다. 또한 이렇게 결정된 사항에 맞추어 설계가 진행되는 경우가 대부분이다. 하지만 이렇게 되면 각 tool이나 framework에 의존도가 높아져 유연한 설계를 할 수 없게 만든다.
  • 좋은 설계자란 tool과 framework와 같은 개발 환경에 대한 결정 사항들을 최대한 유연하게 하고, 미루고 미룰 수 있는 자이다. 예를 들어 데이터 저장을 어떤 방식으로 할지 결정을 나중에 해도 될 만큼 유연한 설계를 짜는 사람이 진정 좋은 설계자라고 할 수 있다. Sql를 사용할지 file system을 사용할지 웹 API를 사용할 지 등을 설계 초입에 안하고 설계할 수 있어야 한다.
  • 개발 환경 보단 Use case에 더 집중하도록 하자.
  • UI 작업을 Use case의 플러그인 정도로 생각을 하고 설계를 하자. 보통 UI 작업은 꽤 비싼 작업이다. UX/UI design/UI dev등 상당히 많은 노력과 시간이 필요한 부분이다. 하지만 Use case 입장에서 나중에 UI이가 Web page에서 Command line으로 바뀌거나 stand alone 어플리케이션으로 바뀐다 해도 큰 지장 없는 설계, 즉 UI 레이어를 Use case의 플로그인 정도로 생각하고 설계를 해야한다.
  • Use case 란?
    • 사용자와 시스템이 특정 목적을 달성하기 위한 설명서 이상, 이하도 아니다.
    • 유스케이스 주도적 개발 (Use case driven development)은 보다 더 나은 설계를 줄 것이다.
    • Use case와 전달 방식 (delivery mechanism)와 분리되어야 한다.

[Swift 서버 side 프래임워크] Vapor -시작하기-

sqgfetfy_400x400

 

 

A. Vapor 설치하기

1. 호환 가능한 환경 체크하기:

curl -sL check.vapor.sh | bash

2. 다운로드 받고 설치 하기

curl -sL toolbox.vapor.sh | sh

3. 설치 확인하기

vapor –help

 

 

B. Vapor 프로젝트 생성하고 실행하기

1. 새 프로젝트 생성하기

vapor new hello-vapor

2. Xcode에서 작업하기 (새로 생성된 폴더 안에 들어가서)

vapor xcode

3. 서버 시작하기

- Xcode에서 빌드 타겟을 변경한 후 Build & Run을 누른다.

- 브라우져에 가서 localhost:8080을 입력하여 서버가 작동하는지 확인한다.

 

C. Droplet 사용하기

1. Source/App/main.swift 파일 안에 있는 내용을 다 지운다. 그리고 다음을 입력한다.

  1. import Vapor
  2.  
  3. let drop = Droplet()
  4.  
  5. drop.run()

2. Get request에 응답 하고 싶다. 다음을 입력한다.

  1. import Vapor
  2.  
  3. let drop = Droplet()
  4.  
  5. drop.get { request in
  6.     return "Hello, Vapor!"
  7. }
  8.  
  9. drop.run()

(Drop의 get 메서드는 parameter 하나가 들어가고 표시 가능한 리소스를 반환해주는 클로져를 인자로 받는다.)

 

3. Get response에 JSON 타입을 반환하고 싶을 때 다음을 입력한다.

  1. drop.get { request in
  2.     return try JSON(node: [
  3.         "message" : "Hello JSON!"
  4.     ])
  5. }

4. 하위 경로 URL의 Get response 만들기

  1. drop.get("hello") { request in
  2.     return try JSON(node: [
  3.         "message" : "Hello, again!"
  4.     ])
  5. }
  6.  
  7. drop.get("hello", "there") { request in
  8.     return try JSON(node: [
  9.         "message" : "Hello, there!"
  10.     ])
  11. }

5. Get request에서 String Querey 받기

  1. drop.get("beers", Int.self) { request, beers in
  2.     return try JSON(node: [
  3.         "message" : "Take one down, pass it around, \(beers - 1) bottles of beer on the wall..."
  4.     ])
  5. }

6. Post request 하기

  1. drop.post("post"){ request in
  2.     guard let name = request.data["name"]?.string else{
  3.         throw Abort.badRequest
  4.     }
  5.     return try JSON(node: ["message" : "Hello, \(name)!" ])
  6. }

D. Heroku에 배포하기

1. 프로젝트를 Git에 commit하기

2. Heroku 가입하기

3. https://devcenter.heroku.com/articles/heroku-cli 에 가서 관련 도구 다운 받아 설치하기

4. 설치 확인하기

heroku –version

5. Command line에서 Heroku 접속하기

heroku login

6. Heroku에 배포하기

vapor heroku init

 

[iOS] 런타임에 언어 바꾸기

iOS 프로젝트에서 Localizable.string 파일을 만들어 앱의 다양한 언어를 지원할 수 있다. 기본적으로 유저가 사용하고 있는 OS 세팅에 적용되어 있는 언어를 앱은 받아드리고 보여주게 될 것이다. 하지만 필자 같이 영문 OS 버전을 사용하면서 특정 앱에 한에서 한국어를 사용하고 싶을 때가 있다. 하지만 CocoaTouch에 선언되어 있는 NSLocalizedString 매크로를 통해서 앱의 언어를 바꾸려면 앱을 메모리에서 한번 지우고 다시 앱을 열어야지만 새 언어를 적용할 수 있다는 특징이 있다. 꽨 번거로운 일이다. 그래서 필자는 런타임에 언어를 바꿀수 있는 방법을 찾아보다가 괜찮은 방법을 찾았기에 소개하고자 한다.

본 글은 stackoverflow에서 찾은 솔루션이다.

링크

기본적인 아이디어는 애플에서 적용되어 있는 NSLocalizedString 매크로를 재정의 하여 사용하는 것이다.

  1. NSBundle의 카테고리를 만든다.
  2. AppDelegate에 language 코드를 알려주는 인스턴스 메서드를 만든다.
  3. NSLocalizedString 매크로를 사용하는 파일에 카테코리 파일을 임포트 한다.

NSBundle의 카테고리 만들기

  1. //
  2. //  NSBundle+RunTimeLanguage.h
  3. //  Copyright © 2016 bartysways. All rights reserved.
  4. //
  5.  
  6. @import Foundation;
  7.  
  8. #undef NSLocalizedString
  9. #define NSLocalizedString(key, comment) [[NSBundle mainBundle] runTimeLocalizedStringForKey:(key) value:@"" table:nil]
  10.  
  11. @interface NSBundle (RunTimeLanguage)
  12.  
  13. - (NSString *)runTimeLocalizedStringForKey:(NSString *)key
  14.                                      value:(NSString *)value
  15.                                      table:(NSString *)tableName;
  16.  
  17. @end
  18.  
  19. //
  20. //  NSBundle+RunTimeLanguage.m
  21. //  Copyright © 2016 bartysways. All rights reserved.
  22. //
  23.  
  24. #import "NSBundle+RunTimeLanguage.h"
  25. #import "AppDelegate.h"
  26.  
  27. @implementation NSBundle (RunTimeLanguage)
  28.  
  29. - (NSString *)runTimeLocalizedStringForKey:(NSString *)key
  30.                                      value:(NSString *)value
  31.                                      table:(NSString *)tableName
  32. {
  33.     AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
  34.     NSString *path = [[NSBundle mainBundle] pathForResource:appDelegate.languageCode
  35.                                                      ofType:@"lproj"];
  36.     NSBundle *languageBundle = [NSBundle bundleWithPath:path];
  37.     NSString *localizedString= [languageBundle localizedStringForKey:key value:key table:nil];
  38.  
  39.     return localizedString;
  40. }
  41.  
  42. @end

AppDelegate에 인스턴스 메서드 만들기

  1. //
  2. //  AppDelegate.h
  3. //  Copyright © 2015 bartysways. All rights reserved.
  4. //
  5.  
  6. @import UIKit;
  7.  
  8. @interface AppDelegate : UIResponder
  9. <
  10. UIApplicationDelegate
  11. >
  12.  
  13. @property(nonatomic, strong) UIWindow *window;
  14. @property(nonatomic, strong, readonly) NSString *languageCode;
  15.  
  16. @end
  17.  
  18. //
  19. //  AppDelegate.m
  20. //  Copyright © 2015 bartysways. All rights reserved.
  21. //
  22.  
  23. @import UserNotifications;
  24.  
  25. #import "AppDelegate.h"
  26.  
  27. @implementation AppDelegate
  28.  
  29. - (NSString *)languageCode
  30. {
  31.     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  32.     NSString *languageCode = ((NSArray *)[userDefaults objectForKey:@"AppleLanguages"]).firstObject;
  33.     return languageCode != nil ? languageCode : @"en";
  34. }
  35. @end

카테코리 파일을 임포트하기

  1. //
  2. //  HeaderCollectionReusableView.m
  3. //  Copyright © 2016 bartysways. All rights reserved.
  4. //
  5.  
  6. #import "HeaderCollectionReusableView.h"
  7. #import "NSBundle+RunTimeLanguage.h"
  8.  
  9. @implementation HeaderCollectionReusableView
  10. @end

VIPER 패턴의 각 요소의 역할

2014-06-07-viper-wireframe-76305b6d

MVC, MVP, MVVM과 같은 패턴들이 난무하는 세상에 VIPER라는 또 다른 개념의 패턴이 나오게 된 배경은 방대하게 커지는 View Controller를 막기 위한 수단이 아닐까 생각해본다. 기본적으로 모바일 어플리케이션에서 View Controller가 커지는 이유는 간단하다. 사용자의 이벤트 액션이 빈번하게 일어나면서 보다 느낌 좋은(?!) UI, UX를 제공하기 위함이 아니겠는가? 나는 MVC, MVP, MVVM을 실제 프로젝트에 적용해가면서 경험한 느낌 중 VIPER가 가장 깔끔한 인상을 받았다. 물론 하나라도 제대로 사용한다면 격게 될 경험일지도 모른다. 개인적인 경험상 그렇다는 것이다. 그렇다면 VIPER 패턴이 무엇인지 요소 하나하나 살펴보자.

 

Interactor

비즈니스 로직이 들어가는 클래스 종류이다. 각 클래스는 하나의 Use case에 해당하는 비즈니스 로직이 들어가야한다. NSObject를 상속받아 설계하면, iOS 프로젝트와 macOS 프로젝트에서 공유해서 함께 사용할 수 있다. 어떠한 UI와도 연관이 없어야 하며 독립적으로 존재해야 한다. Data Store에서 전달 받은 Entity는 절대 그대로 Presenter 클래스에 전달되어서는 안된다. Presenter에 전달 될 모델 역할의 클래스는 Behavior가 없는 간단한 데이터로 구성되어 있는 모델이어야 한다. 그리고 네트워킹을 사용하는 앱이라면 Interactor에서 DataManager나 DataStore와 같은 직접적으로 네트워킹 구현이 있는 클래스를 사용하여, 네트워크를 초기화하고 시작해야할 것이다. Interactor는 여러 Remote 자원에서 받은 데이터를 종합해서 정보를 Presenter에게 넘져주는 역할을 해야 할 것이다.

  • Data Store로 부터 데이터를 받아온다.
  • Entity를 조작 혹은 조합한다.
  • 조작된 Entity를 다시 Data store에 넣어 주거나, Presenter에게 화면에 뿌려주기 좋은 모양의 모델을 넘겨준다.

Entity

모델 클래스이다. Interactor 클래스에서 다루어지는 객체로써, 절대 그대로 Presenter 레이어로 넘겨서는 안된다. 그래고 만약 Core Data를 사용하고 있다면, ManagedObject로 부터 분리해서 생성되어야 한다.

Presenter

크게 두가지 역할이 있다. UI를 그려주고, Interactor 클래스에게 데이터를 요구하는 역할이다. 뿐만 아니라 VIPER의 약자중 Router에 해당하는 개념의 일부분을 담당한다. Wireframe 클래스는 다음 화면이 어딘지 안다면, Presenter는 언제 다음 화면이 나와야하는지 알고 있는 클래스이다. Presenter는 View 레이어 즉 UIViewController와 같은 클래스를 상속 받은 클래스에게 알맞는 데이터를 할당해주던가 원하는 UI 모습을 만들수 있는 클래스이다. Presenter는 View의 UILabel 이 존재하는지 UIButton이 존재하는지 알아서는 안된다. Presenter는 그려질 컨텐츠와 언제 그것이 View에 전달되어 화면에 뿌려져야하는 지만 알고 있으면 된다.

View

View는 수동적이여야 한다. Presenter가 보여줄 컨텐츠를 줄 때까지 기다려야 한다. 절대 Presenter에게 데이터를 달라고 요구해서는 안된다.  InterfaceProtocol 같은 것을 사용하여 Presenter가 View에 컨텐츠를 넣어 줄수 있게 해야한다. 비록 컨텐츠는 Presenter가 가지고 있지만 화면에 어떤 모습으로 그려져야하는지는 View가 알고 있다. View와 View Controller가 커지는 이유는 이벤트를 받고 그것을 처리하기 가장 편한 곳이기 때문이다. 하지만 View 레이어를 가볍게 만들기 위해서는 View에 관심을 가지고 있는 객체들에게 사용자가 특정 액션을 했을 때 그것을 알려주면 된다. 예를 ViewController가 이벤트 핸들러라는 이름으로 Presenter 객체를 프로퍼티로 가지고 있다고 가정하면, ‘취소’ 버튼 같은 특정 UI의 이벤트가 발생했을 때 Presentor의 메서드를 호출 하면된다. 이때 리액티브 관련 프래임워크의 힘을 빌리면 조금더 깔끔하게 코드를 짤 수 있다.

Routing

VIPER 패턴의 Routing은 Xcode의 스토리보드와 같은 역할을 하는 레이어이다. 구제적으로는 Wireframe 클래스와 Presenter 클래스가 그 역할을 한다. 먼저 Wireframe 클래스는 UIWindow, UINavigationController, UIViewController 같은 것을 소유하고 있으며, 담당 역할은 View나 ViewController를 생성하고 window에 설치하는 것이다. Presenter는 View와 인터랙션을 하고 있기 때문에 언제 새로운 창을 만들어야 하고 언제 화면전환이 이루어지는 지를 알고 있다. 반면 Wireframe은 어디로 가야하는 지를 알고 있다. 이 두 클래스를 통해서 Routing의 레이어를 완성 할 수 있다.

etc.

Data store

Data store는 Entity를 Interator에게 주는 역할을 한다. Entity를 받은 Interactor는 모델 데이터를 조작하여 알맞은 모양으로 Presenter에게 던진다.

Data Manager

Data Manager는 Data store와 비슷한 역할도 하지만, 추가로 fetch Request를 만든다든가, Query를 만들거나 하는 일을 한다. 이렇게 하므로써 Interactor는 Application 레벨의 비즈니스 로직에 더 집중 할 수 있게 해준다.

 

추가 2016.11.04

Module 간의 통신

위에서 간단히 설명한 VIPER의 구조는 iOS 프로젝트로 따지자면, 앱 기능의 의미를 갖는 ViewController를 중심으로 모듈화하여서 사용할 수 있을 것이다. 하지만 중요한 것은 모듈과 모듈 사이에 연결되는 고리를 고민해야 할 때가 온다. 예를 들어 Setting 모듈에서 적용된 작업을 List 모듈 같은 것에 적용하고 싶다면 Notification과 같은 옵져버 패턴을 사용하여 데이터 전달 혹은 메서드 호출을 할 수 있을 것이다. 하지만 더 VIPER 패턴스러운 방법은 Presenter에다가 delegate을 두고 사용하는 것이 깔끔하다. 예를 들어 Setting 모듈의 Presenter에 delegate을 선언하고, List 모듈의 presenter가 이 것을 구연하게 되면 Notification과 같은 API에 의존하지 않고 데이터 전달 및 메서드 호출이 가능해 질 것이다.

[Xcode] LLDB 유용한 명령어 모음

PO

  • 객체의 description 메서드 호출
  • 오브젝트를 반환 해주는 expression 명령어와 함께 사용 할 수 있다.

P

  • Primitive 값을 출력할 수 있다.
  • Primitive 값을 반환 해주는 expression 명령어와 함께 사용 할 수 있다.

br

  1. 브레이크 포인트 리스트 보기: br list
  2. 브레이크 포인트 삭제 하기: br delete 1
  3. 특정 브레이크 포인트 활성화 시키기: br e 1
  4. 특정 브레이트 포인트 비활성화 시키기: br di 1
  5. 동일한 메서드명을 가지고 있는 곳에 브레이트 포인트 설정하기: br set -n viewDidLoad
  6. 특정 브레이크 포인트에 조건 넣기: br mod -c “totalValue > 1000″ 3
  7. 특정 브레이크 포인트에 command 심기: br com add 2

b

  1. 브레이크 포인트 새로 만들기: b MyViewController.m:30

continue

  • 실행 재생하기

n

  • 커서 한줄 실행 시키기 (step over)
  • 단추키 F6

s

  • 커서 메서드 안에 들어가기 (step in)
  • 단추키 F7

finish

  • 커서 메소드 빠져나오기 (step out)
  • 단추키 F8

 expr -part1-

  • 런타임으로 Objective-c 코드를 실행 시켜준다.
  • [예] expr self.view.hidden = YES
  • [예] expr (void) NSLog(@”hello world”)
  • [예] expr (BOOL) [self.myArray containObject @"carKeys"]
  • [예] expr — (CGRect)[self.view frame]
  • [예] expr [self prepareForSegue:@"mySegue" sender:nil]

expr -part2-

  • 런타임에서 변수를 생성 및 조작 할 수 있다.
  • 정의하기: expr int $meaningOfLife = 42
  • 사용하기: expr 100 + $meaningOfLife
  • 예재: JSON 스트링은 오브젝트로 바꾸기
  1. expr NSString *$json = [self fetchRemoteData];
  2. expr NSData *data = [$json dataUsingEncoding:4];
  3. expr NSDictionary *$parsedDic = [NSJSONSerialization JSONObjectWithData:$data option:0 error:NULL];
  4. po parsedDic

bt

  • back trace: 브레이크 포인트를 통해 멈춰진 곳이 어떤 경로를 통해 불렸는지 보여주고, 각 단계를 왔다 갔다 할 수 있는 기능
  • 모든 thread의 back-trace를 보고 싶을 때: bt all

thread

  • Thread 정보에 대해서 알려준다. 하지만 한번에 한 Thread에서 작업 할 수 있다.
  • 불린 값으로 메소드 반환하기: thread return YES (또는) NO
  • back-trace: thread backtrace
  • 다 보기: thread all
  • 목록 보기: thread list
  • 이동하기: thread select 24
  • 브레이크 포인트 처럼 작동하기: thread until 100

frame

  • 로컬 변수 살펴보기: frame variable self
  • 정보 보기: frame info
  • 직접 이동하기: frame select 2
  • 상대 이동하기: frame -r -1

watchpoint

  • 특정한 변수 값이 변할 때 마다 실행 멈추고 값 보기
  • 리스트 보기: watchpoint list
  • 삭제: watchpoint delete 1
  • 설정하기: watchpoint set variable _x
  • watchpoint set expression — my_point
  • 조건넣기: watchpoint modify — -c “_x < 0″ 1
  • 조건 없애기: watchpoint modify -c “” 1

script

  • LLDB에는 파이썬 언어 해석기가 포함되어 있다. 그리고 브레이크 포인트에서 스크립트를 실행 시킬수 있다.
  • 실시간 파이썬 실행하기: script
  • 브레이크 포인트에 파이썬 심기: br command add -s python  1
  • 브레이크 포인트에 command 삽입하기: breakpoint command add -f my.breakpoint_func

command

  • 현존하는 스크립트를 심을 수 있다.
  • 임포트하기: command script import ~/my_script.py
  • 추가하기: command script add -f my_script.python_function cmd_name
  • 히스토리 보기: command history
  • 디버깅 스크립트 임포트하기: command import ~/my_lldb.txt

Core Data 개론

Apple이 UI와 독립적으로 Model layer에 툴로 코어 데이터를 제공하고 있다는 것은 iOS 혹은 MacOSX 어플리케이션 개발자라면 다 알 것이다. 근데 많은 사람들이 코어 데이터를 O/RM (Object-relational mapping)으로 혹은 SQL Wrapper 정도로 오해하기도 한다. 물론 SQL를 기본으로 사용하고 있기는 하지만 더 높은 차원의 추상화되어 있는 개념으로 생각해야한다. 만약 O/RM이나 SQL Wrapper를 기대하고 코어 데이터를 생각한다면 다른 것을 사용해야 할 것이다. 코어 데이터를 코코아 어플리케이션 정보 저장에 사용되는 코코아용 데이터 베이스 API로 생각하는 것도 오해 중 하나라고 할 수 있다. 디스크에 있는 저장소뿐만 아니라 흔히 데이터 오브젝트라고 불리는 메모리상의 모든 객체를 포함한다. 자바나 C#, 혹은 다른 객체 지향 언어로 작업하면 데이터 객체를 만드는 데 많은 시간이 소요되며, 작업이 자주 반복되는 특징이 있다. 코어 데이터는 대부분의 반복되는 코드를 제거해 어플리케이션의 비즈니스 로직이나 컨트롤러 계층에 집중할 수 있게 해준다.

그렇다면 코어 데이터는 무엇인가?

코어 데이터는 모델 레이어 기술이다. 간단히 말하면 코어 데이터는 디스크에 저장될 수 있는 객체 그래프이다. 코어 데이터는 몇 가지 컴포넌트들로 구성이 되어 있다. 대부분의 사례에서 사용될 수 있으며, 세업도 비교적 쉬운 편이다. 각각의 컴포넌트들은 상호 간에 묶여 있다. 각각의 컴포넌트의 개념을 설명하기 전에 아래 그림을 먼저 잘 봐두기 바란다.

출처 https://www.objc.io/issues/4-core-data/core-data-overview/

출처 https://www.objc.io/issues/4-core-data/core-data-overview/

위 그림은 코어 데이터 스택을 나타낸 것이다. 여기에서 스택이라 하면 여러 층의 레이어를 갖추고 수직적으로 데이터를 전달하는 구조를 말한다. 수직적이라는 말은 상위 레이어에서 시작해 하위 레이어로 전달이 진행된다는 의미이다. 데이터는 바로 하위 레이어나 중간에 위치한 레이어로 전달되지 않기 때문에 상위 레이어부터 차례로 접근해야 한다.

위 그림을 참고로 설명하자면 크게 세가지 레이어로 나눌 수 있다.

  • Managed Object Context 레이어
  • Persistent Store Coordinator 레이어
  • Persistent Store 레이어

상위 두 레이어는 메모리에서 데이터를 관리한다고 보면 되고, Persistent Store 레이어로 내려가면 Disk 메모리 영역으로 생각해두면 쉽다.

코어 데이터 메인 컴포넌트

먼저 쉬운 설명을 위해서 상위 개념으로 부터 실제 파일 시스템 영역 차례로 기술해보겠다.

NSManagedObject

전에 코어 데이터를 사용해 보았다면 가장 친근한 클래스 명이라 생각 되어진다. 코어 데이터를 사용하는 개발자가 가장 많이 접하게 되는 객체이기 때문이다. NSManagedObject는 Managed object model의 Entity를 서브 클래싱 받아서 소스 코드에서 사용되어 지는 Value Object인데, 이 객체는 위 그림 과 같이 특정 Managed Object Context 안에서 관리되어 진다고 할 수 있다.

NSManagedObjectContext

Managed object context는 자신이 가지고 있는 Managed object들의 변동 여부를 추적하는 것이 주된 역할이다. 그래서 -save라는 메서드를 호출하면, 변동되어진 Managed object에 한에서만 로컬 데이터 베이스에 변경 요청을 한다. 기본 적으로 코어 데이터 템플릿은 하나의 Managed object context를 사용하고 있지만, 더 복잡한 상황에서는 하나 이상의 Context를 각기 다른 thread에서 사용할 수 있다. Managed object context는 PersistentStoreCoordinator와 통신을 하며 로컬 데이터 베이스에 데이터를 불러오고나, 저장 혹은 삭제를 요청한다.

NSPersistentStoreCoordinator

Persistent Store Coordinator는 스택의 중간에 중개자의 역할을 NSManagedObjectContext와 NSPersistentStore 객체 사이에서 한다. 자체적으로 Cache 영역이 존재하기 때문에 로컬 데이터 베이스에 자료를 요청하기 전에 cache 된 것이 있다면 디스크 메모리까지 접근하지 않고 cache 영역에서 데이터를 활용한다.

NSPersistentStore

Persistent Store은 실제 SQLite 데이터 베이스와 Coordinator 사이에서 데이터 베이스 쿼리 코드를 전환해주고 실 디비와 통신하는 Wrapper의 역할을 한다. 각 데이터 베이스 별로 존재하며 새로운 데이터 베이스를 사용하고자 한다면 NSPersistentStore를 상속 받아 상용해야 한다.

실제 데이터 가지고 오기

사용자가 로컬 데이터 베이스에 있는 데이터를 가지고 오기 위해서 여러가지 과정을 걸치게 된다.

  1. NSManagedObjectContext: 가장 context 안에 자기고 있는 데이터가 있는지 조사한다. 만약 없다면 한단계 아래 레이터에 데이터를 요청한다. 해당 레이어는 임시 메모리에 해당된다.
  2. NSPersistentStoreCoordinator: 위에서 잠깐 언급 했지만 NSPersistentStore를 통해 데이터를 요청하기 전에 자신이 가지고 있는 cache를 먼저 확안하는 작업을 걸친다.
  3. NSPersistentStore: 이 레이어는 직접 디비 쿼리로 데이터 베이스에 자료를 요청하는 작업을 하게 되므로 이 안계 부터는 Disk 메모리를 사용한다고 보면 된다.

위 과정은 NSFetchRequest를 사용하여 가지고 오는 경우가 아닌 objectIdentifier를 가지고 오는 경우를 말한다. NSFetchRequest를 사용하여 데이터를 요청하게 되면 항상 NSPersistentStore 레이어까지 내려가 디스크 메모리에 접근한다고 봐야한다.

XCode가 제공하는 기본 템플릿 말고 독립적으로 코드 사용하기

Xcode 프로젝트를 새로 생성할 때에 ‘코어 데이터 사용하기’라는 옵션을 체크하고 생성하게 되면 .xcdatamodeld라는 확장자를 가진 XML 파일과 함께 AppDelegate 파일에 코어 데이터 관련되 기본 템플릿이 추가되어 프로젝트가 생성이 될 것이다. 아마 많은 사용자들은 제공 해주는 코드를 그대로 사용 할 것으로 생각되어 진다. 하지만 위에서 배운 개념들을 생각하며 AppDelegate에서 분리된 PersistentStack이라는 파일을 통해 코어 데이터를 사용해보는 코드는 다음과 같다. (아래 방법으로 코어 데이터를 세팅 할 때에는 새로운 프로젝트를 생성할 때에 ‘코어 데이터 사용하기’ 체크를 푼다음에 직접 .xcdatamodelId 파일을 추가하며 아래 클래스를 사용해야 한다.)

 

  1. // 헤더 파일
  2. #import <Foundation/Foundation.h>
  3. @import CoreData;
  4.  
  5. #pragma mark – Interface
  6.  
  7. @interface SLKPersistentStack : NSObject
  8. @property(nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext;
  9. + (instancetype)sharedInstance;
  10. @end
  11.  
  12. // 구현 파일
  13. #import "SLKPersistentStack.h"
  14.  
  15. #pragma mark – Interface extension
  16.  
  17. @interface SLKPersistentStack ()
  18.  
  19. @property(nonatomic, strong, readwrite) NSManagedObjectContext *managedObjectContext;
  20. @property(nonatomic, strong) NSURL *modelURL;
  21. @property(nonatomic, strong) NSURL *storeURL;
  22.  
  23. @end
  24.  
  25. #pragma mark – Implementation
  26.  
  27. @implementation SLKPersistentStack
  28.  
  29. #pragma mark [Getters]
  30.  
  31. - (NSManagedObjectModel *)managedObjectModel{
  32.     return [[NSManagedObjectModel alloc] initWithContentsOfURL:self.modelURL];
  33. }
  34.  
  35. - (NSURL*)storeURL{
  36.     NSURL* documentsDirectory = nil;
  37.     documentsDirectory = [[NSFileManager defaultManager]
  38.                           URLForDirectory:NSDocumentDirectory
  39.                           inDomain:NSUserDomainMask
  40.                           appropriateForURL:nil
  41.                           create:YES
  42.                           error:NULL];
  43.  
  44.     return [documentsDirectory URLByAppendingPathComponent:@"db.sqlite"];
  45. }
  46.  
  47. - (NSURL*)modelURL{
  48.     return [[NSBundle mainBundle] URLForResource:@"SLKCodingExercise"
  49.                                    withExtension:@"momd"];
  50. }
  51.  
  52. #pragma mark [Constructors]
  53.  
  54. + (instancetype)sharedInstance{
  55.     static id sharedInstance = nil;
  56.     static dispatch_once_t onceToken;
  57.  
  58.     dispatch_once(&onceToken, ^{
  59.         sharedInstance = [[[self class] alloc] init];
  60.     });
  61.  
  62.     return sharedInstance;
  63. }
  64.  
  65. - (instancetype)init{
  66.     if (self = [super init]) {
  67.         [self p_setupManagedObjectContext];
  68.     }
  69.  
  70.     return self;
  71. }
  72.  
  73. - (void)p_setupManagedObjectContext{
  74.     self.managedObjectContext = [[NSManagedObjectContext alloc]
  75.                                  initWithConcurrencyType:NSMainQueueConcurrencyType];
  76.     self.managedObjectContext.persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
  77.                                                             initWithManagedObjectModel:[self managedObjectModel]];
  78.  
  79.     NSError *error;
  80.     [self.managedObjectContext.persistentStoreCoordinator
  81.                                 addPersistentStoreWithType:NSSQLiteStoreType
  82.                                 configuration:nil
  83.                                 URL:self.storeURL
  84.                                 options:nil
  85.                                 error:&error];
  86.     if (error) {
  87.         NSLog(@"error: %@", error);
  88.     }
  89.  
  90.     self.managedObjectContext.undoManager = [[NSUndoManager alloc] init];
  91. }
  92.  
  93. @end

 
위 클래스는 싱글톤 패턴을 사용하여 만들어 보았다. 그 이유는 프로젝트 소스 코드 여러 군데에서 NSManagedObjectContext를 필요로 할 것이기 때문이다. 하지만 하나이상의 Context를 사용하게 된다면 다른 방법을 생각해봐야 할 것이다. Managed Object Model은 위에서 언급한 .xcdatamodelId 파일에 해당되는 객체이다.

마치며…

해당 글은 코어 데이터의 기본적이 구조와 개념에 대한 내용만을 다루어 보았다. 멀티 스레드 환경에서의 코어 데이터 적용에 대한 부분과 쉽게 읽어지는 코드를 위해서는 꼭 필요한 과정일 것이다. iOS 환경이나 OS X 환경에서 기본적으로 로컬 디비를 사용하겠다고 한다면 코어 데이터를 꼭 한번 고려할텐데 좀 더 기본에 충실하게 무엇이 무엇인지는 알고 사용하자는 의미에서 글을 써보았다. 앞으로 여유가 된다면 코어 데이터의 성능관련 부분과 멀테 스레드 환경에서의 코어 데이터 사용하기 부분을 올려보고자 한다. 혹시 미흡하거나 잘못 된 부분이 있다면 댓글을 남겨 주시길.

참조
  1. https://www.objc.io/issues/4-core-data/core-data-overview/
  2. 코어 데이터 맥 OS X과 아이폰 개발자를 위한 -에이콘 출판사-

클라이언트 개발자로서 Data serialization에 대한 생각

이 글은 기술에 대한 깊이 있는 이야기를 하고자 한 것이 아니라 모바일 개발자로써 깊이 없이 개발해왔던 지난 날들을 생각하며 몇자 남기는 정도의 흔적이라고 생각해주면 좋겠다.

데이터 시리얼라이즈는 이기종 간에 통신을 위해 사용되거나, 객체를 저장할 때 사용한다. 특별히 iOS 클라이언트 개발자는 객체의 인스턴트를 저장할 때 사용할 것이다.

오늘날 iOS 프로젝트에서 보편적으로 JSON 데이터 포멧으로 http 프로토콜을 사용하여 통신을 하고 있을 것이다. http 프로토콜은 스트링 프로토콜로써 사용하기 비교적 쉬운 방면에 뛰어난 성능을 요구하는 데이터 통신에서는 무거운 성능 이슈로 비추하고 있는 것이 통념적이다. 보다 빠르고 쉽게 이기종 간의 환경에서 데이터를 주고 받을 때 데이터 시리얼라이즈를 사용하여 통신을 하는데 이에 대해서 간략하게 적어본다.

데이터 통신은 기본적으로 패킷이라는 단위로 송수신을 한다. 대부분의 클라이언트 개발자는 패킷 단위의 개념까지 고려해 가면서 통신 코드를 짜고 있지 않을 것으로 여겨진다. 적어도 나는 그랬다. 아무 개념과 고민 없이 서버에서 제공하는 API를 호출 할 때면 서버팀과 약속한 데이터 포멧에 맞추어 JSON 스트링을 만들었고 이것을 NSURLRequest 같은 객체에 실어서 보내기만 했기 때문에, 패킷이 어떻게 나눠져서 전달되게 되는지 혹은 그 패킷의 모습에는 관심이 없었다. 이 말은 현재 개발환경에서 클라이언트 개발자로써 데이터 통신부분에 대하여 큰 고민은 거의 하지 않아되 되는 정도의 진입장벽인 것이다.

하지만 서버 개발자들은 고민을 할 것으로 여겨진다. 회사는 비용절감의 측면에서 더욱 낮은 코스트로 같은 기능을 구현하기 원할 것이고, 이를 실현하기 위해 여러가지 것을 고민하고 있을 것이다. 데이터 시리얼라이즈는 그런 측면에서 중요한 부분을 차지한다. 효율적이고 넒리 사용되기 쉬운 데이터 시리얼라이즈를 위해서 여러 회사에서 자신들이 만든 프로토콜을 대중이 사용해주길 바라고 있다. ProtocolBuffer는 구글에서, Thrift는 페이스북에서 시작하여 지금은 오픈소스로 발전하여 많은 프로젝트에서 각광을 받고 있다.

객체를 데이터 시리얼라이즈를 하게 되면 바이트 배열에 원하는 데이터 스키마에 맞주어 결과물을 만들게 된다. 수신하는 쪽이나 송신하는 쪽에서 스키마에 대한 내용을 알고 있다면, decode하는 과정이 JSON과 같은 텍스트 기반의 인풋을 스트링 파싱하는 것보다 성능이 뛰어나다. 뿐만 아니라 패킷 사이즈가 몇배 이상으로 작아져 보다 뛰어난 성능을 가지고 올 수 있는 장점 때문에 데이터 시리얼라이즈에 대한 성능개선이 주목을 받고 있는 것이다.

분명 각자의 프로젝트 도메인에 가장 알맞는 데이터 타입과 통신 프로토콜을 사용하는 것이 정답일 것이다. 하지만 어떤 옵션이 있는지 알아둬어야 더 깊이 있는 개발자가 될 수 있다고 생각되어지며, 만약 자신의 프로젝트에서 HTTP 기반으로 JSON을 사용하고 있다면 왜 그 방식을 선택하게 되었는지 why에 대한 고민을 했을 때 비로써 멋진 개발자로 거듭날 수 있지 않을까 생각해본다.

[Question] isBalancedTree?

문제: 인자로 들어오는 Binary Tree는 Balanced인가 아닌가를 구분하는 함수를 만들어라.

문제를 풀기 앞서 몇 가지 개념 정리를 해야 할 것 같다. Binary 트리라 불리는 이진트리 데이터 구조는 무엇인가? 이진 트리는 0개 혹은 1개, 2개의 자식노드를 같는 트리를 말한다. 그럼 해당 데이터 구조가 균형잡히다, 라는 것은 무슨 뜻일까? Balanced하다 하는 것은 자신의 자식노드의 깊이의 차이가 1보다 많아서는 안될 때 균평 잡힌 이진트리이다. 라고 말한다.

Screen Shot 2016-01-07 at 11.35.58 AM

위 예제는 균형잡힌 이진트리이다. 왜냐하면 모든 노드의 자식노드들의 깊이 차이가 1을 넘어 서는 것이 없기 때문이다.

Screen Shot 2016-01-07 at 11.36.09 AM

위 예제는 균형잡힌 이진트리가 아니이다. 왜냐하면 1의 자식들의 깊이의 차이가 1보다 큰 2이기 때문이다.

그렇다면 균형잡힌 이진트리를 찾기 위해서 어떻게해야 할 것인가? 고민해보자.

주먹구구식으로 먼저 생각을 해보자. 모든 노드의 자식의 차이를 구하고 그의 차이가 1 보다 큰 것이 나오면 false를 그 이외의 것들은 True를 반환하면 된다.

아래 답을 보기 전에 먼저 손수 풀어보자. 효율적인 함수를 짜기 위해서는 고민을 좀 해봐야 할 것이다. 왜냐하면 노드의 깊이도 구해야 할 것이고 Bool 값도 반환하고 싶을 것이기 때문이다.

이 문제의 팁은 -1를 통해 깊이 계산과 동시에 불균형를 표시하는 것이다.

  1. - (BOOL)isBalanced:(Node *)rootNode{
  2.     if([self getNodeDepth:rootNode] == -1){
  3.         return NO;
  4.     }
  5.     return YES;
  6. }
  7.  
  8. - (NSInteger)getNodeDepth:(Node *)node{
  9.     if(node == nil){
  10.         return 0;
  11.     }
  12.  
  13.     NSInteger leftDepth = [self getNodeDepth:node.left];
  14.     if(leftDepth == -1){
  15.         return -1;
  16.     }
  17.  
  18.     NSInteger rightDepth = [self getNodeDepth:node.right];
  19.     if(rightDepth == -1){
  20.         return -1;
  21.     }
  22.  
  23.     NSInteger depthDiff = Abs(leftDepth – rightDepth);
  24.     if(depthDiff > 1){
  25.         return -1;
  26.     }
  27.  
  28.     return Max(leftDepth, rightDepth) + 1;
  29. }

[Recursive Questions] Print all combination of Parenthesis

Combination 문제는 Permutation 문제의 응용문제라고 할 수 있다. 재귀함수 호출에 의한 문제 풀이를 한번 알아보자.

문제: 괄호의 수를 의미하는 integer 값을 입력을 받아 가능한 괄호의 조합을 프린트해주는 함수를 짜보자. 예를 들어 3을 입력으로 넣었다면, ((())), (())(), ()(()), (()()), ()()() 문자열이 프린트 되어야 한다.

#Walk through
0. 함수의 인자 값으로 다음과 같이 받을 것이다. (leftRemain, rightRemain, str)
1. [Base condition] 만약 rightRemain의 수가 0이라면, str을 프린트한다.
2. 만약 leftRemain가 0보다 큰지 확인한다.
2-1-a. 크다면, leftRemain을 하나 줄인 다음 str에 “(“를 추가해서 재귀 함수를 호출하자.
2-1-b. 만약, leftRemain가 rightRemain 보자 작은지 확인하자.
2-1-b-i. 작다면, rightRemain을 하나 줄인 다음 str에 “)”를 추가해서 재귀 함수를 호출하자.
2-2. 작다면, rightRemain을 하나 줄인 다음 str에 “)”를 추가해서 재귀 함수를 호출하자.

  1. - (void)printAllCombinationOfParenthesisWrapper:(NSInteger)numberOfPar{
  2.     [self printAllCombinationWithLeftRemain:numberOfPar
  3.                                     rightRemain:numberOfPar
  4.                                          string: @""];
  5. }
  6.  
  7. - (void)printAllCombinationWithLeftRemain:(NSInteger)leftRemain
  8.                               rightRemain:(NSInteger)rightRemain
  9.                                    string:(NSString *)str{
  10.     if (rightRemain == 0){
  11.         NSLog(@"%@", str);
  12.         return;
  13.     }
  14.  
  15.     if(leftRemain != 0){
  16.         [self printAllCombinationWithLeftRemain:leftRemain-1
  17.                                     rightRemain:rightRemain
  18.                                          string: [str appendString:@"("]];
  19.         if(leftRemain < rightRemain){
  20.             [self printAllCombinationWithLeftRemain:leftRemain
  21.                                         rightRemain:rightRemain-1
  22.                                              string: [str appendString:@")"]];
  23.         }
  24.     }
  25.     else{
  26.         [self printAllCombinationWithLeftRemain:leftRemain
  27.                                     rightRemain:rightRemain-1
  28.                                          string: [str appendString:@")"]];
  29.     }
  30. }

[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한다는 것을 잊지 마라