7.1.1 두 단계 초기화 패턴
초기화 과정
코코아 프레임워크의 객체들을 포함해서 NSObject에서 상속받은 모든 클래스의 인스턴스가 만들어지기까지는 두 단계에 걸쳐서 초기화가 이루어진다.
Pen *aPen = [[Pen alloc] init];
첫 번째 단계에서 Pen 클래스에 alloc 메시지를 보내서, 힙 공간에 객체 인스턴스 메모리 공간을 할당한다. 두 번째 단계에서는 객체 인스턴스에 init 메시지를 보내서 객체 인스턴스 속성이나 내부에 필요한 객체나 값을 초기화한다. …중략… 1단계에서 메모리 할당이 되지 않으면, 2단계 초기화 과정은 진행이 불가능하다.
…중략
두 단계 초기화를 한 단계로 줄여서 사용하기 위해 간편한 메서드를 클래스 메서드로 제공하기도 한다.
Pen *aPen = [Pen new];
이 코드는 내부적으로 alloc 메시지와 init 메시지를 보내서 초기화 단계가 마치 한 단계인 것처럼 사용할 수 있다. … 중략
지정 초기화 메서드(Designated Initializer)
초기화 메서드는 메서드 명칭이 init-으로 시작하는 조건만 만족하면, 인자 값에 따라 여러 개의 초기화 메서드를 만들어도 상관없다. … 중략…
코코아에서는 여러 초기화 메서드 중에서 기준이 되는 지정 초기화 메서드를 명시적으로 둘 것을 권장한다. NSString 클래스는 빈 문자열 객체를 만드는 -init 메서드가 지정 초기화 메서드고, 다른 메서드들은 초기 값을 넣을 수 있는 부가적인 보조 초기화 메서드다.
Designated Initializer: 클래스에 지정된 모든 property를 초기화하고 부모 클래스의 init을 호출하는 initializer
…중략
지정 초기화 메서드를 만들 때 지켜야 할 사항은 다음과 같다.
- 상속받은 서브 클래스에서 지정 초기화 메서드를 구현할 때는 반드시 부모의 지정 초기화 메서드를 호출해야 한다.
- 상속받은 서브 클래스에서 부모에 없는 새로운 보조 초기화 메서드는 자기의 지정 초기화 메서드를 호출해야 한다.
- 슈퍼 클래스의 지정 초기화 메서드에서 반환되는 객체는 self에 할당한다.
- 슈퍼 클래스의 지정 초기화 메서드에서 nil을 반환하면, 인스턴스 내부 변수를 사용하지 않고 nil을 그대로 반환한다.
7.1.2 MVC(Model-View-Controller) 패턴
…중략…오브젝티브-C에 가장 영향을 많이 준 언어는 스몰토크인데, 모델-뷰-컨트롤러 패턴도 스몰토크에서 이어진 흐름이라고 볼 수 있다. …중략… 컨트롤러가 사용자 입력을 확인하고, 모델 데이터를 변경해서 화면에 표시하기까지 흐름을 보여주고 있다.
MVC 패턴은 코코아 프레임워크에서 여러 다른 패턴과 함께 사용하는 기본 패턴이다. 모델과 컨트롤러 관계에서도 옵저버 패턴으로 구현하기도 하고, 컴포지트 패턴을 사용하기도 한다.
모델과 컨트롤러 관계에서도 NSNotificationCenter 클래스를 활용해서 옵저버패턴으로 구현하기도 한다. 컨트롤러가 해당 모델을 포함하는 경우에는 컴포지트 패턴을 사용하기도 한다.
모델 객체
모델 객체는 화면을 구성하거나 내부 처리를 위한 데이터를 추상화해서 타입을 지정한 자료구조로 표현하고, 데이터를 처리하는 로직을 정의한다. 앱에서 지속적으로 사용하는 데이터는 모델 객체 내부에 캡슐화되고, 파일이나 데이터베이스 같은 영구적인 구조로 저장하기도 한다.
MVC 패턴에서 데이터를 다루는 모델 객체는 화면을 구성하는 뷰 객체와 직접저그올 연결하지 않는다. 데이터에 접근하는 유일한 추상화 객체가 된다. 서버에서 받은 데이터 구조를 컨트롤러와 뷰에서 사용할 때 모델 객체가 기본 처리 단위가 된다.
뷰 객체
뷰 객체는 코코아 프레임워크에서 대체로 NSView나 UIView 클래스를 상속받아 앱 화면 자체를 그려서 표시하는 역할을 하고, 사용자 선택에 따라 입력을 받거나 피드백을 준다. 일반적으로 뷰 객체가 화면에 표시하는 정보는 모델 객체가 갖고 있는 데이터를 기반으로 한다. 하나의 뷰가 모델 하나와 매칭될 수도 있고, 하나의 뷰를 표현하기 위해서 여러 모델의 데이터가 필요하기도 하다. 반대로 하나의 모델의 데이터가 여러 뷰에서 사용자 입력에 따라 다르게 보여지기도 한다.
뷰 객체는 화면 구성을 위해서 모델 객체와 매우 밀접한 관계를 갖고 있지만, 이런 상호 관계를 끊고 느슨하게 결합하는 것이 MVC 패턴의 핵심 사항이다. 그 상호 관계를 유지하도록 도와주는 역할이 컨트롤러 객체의 역할이다. 따라서 컨트롤러가 연결해주는 모델 객체에 따라서 뷰 객체는 얼마든지 재사용이 가능하다.
컨트롤러 객체
컨트롤러 객체는 뷰 객체와 모델 객체 사이에서 사용자 입력과 데이터 변화에 대한 연결을 해주는 중재자 역할을 한다. 사용자 입력에 따른 새로운 데이터 변화를 확인하고, 관련 모델에 새로운 데이터를 업데이트해준다. 반대로 모델에서 데이터가 바뀌면 뷰에 전달해서 새로운 데이터로 화면에 표시한다.
뷰 객체가 표시하는 화면 구조가 복잡하거나 사용자 입력 방식이 다양할수록 데이터 모델도 상대적으로 덩치가 크고 컨트롤러 객체의 동작이 복잡해진다. MVC 패턴으로 구현할 때 가장 큰 고민거리는 컨트롤러 객체를 구현하는 코드가 복잡하고 길어진다는 것이다. …중략… 따라서 가볍고 재사용성이 높은 컨트롤러 객체를 만들기 위해서 다양한 MVC 변형 패턴들을 함께 사용한다. 코코아에서는 최근에 알려진 변형 패턴을 도입하지 않았다.
7.1.3 메시지 셀렉터 패턴
다른 객체가 코코아 객체에게 메시지를 보내면 코코아 런타임은 해당 객체 메서드 중에서 메시지를 처리할 메서드를 찾아서 메서드의 함수 포인터를 호출한다. 런타임이 메시지에 해당하는 객체 메서드에서 찾는 과정을 다이내믹 디스패치라고 부른다. …중략… 런타임에서 메시지를 처리하기 이전에, 메서드를 선택하거나 메서드 바인드를 지연시키기 위해 사용하는 메시지 셀렉터와 관련된 패턴에 대해 알아보자.
셀렉터(SEL)와 구현 포인터(IMP)
코코아 객체의 메서드를 찾기 위해서는 셀렉터와 구현 포인터를 사용한다. 셀렉터는 말 그대로 메시지를 받을 객체의 메서드 중에서 적합한 메서드를 고르는 역할을 한다. 셀렉터가 없다면 C++ 처럼 컴파일 과정에서 객체 메서드에 대한 함수 포인터를 찾아서 고정된 메모리 주소를 바인드했다가 호출해야만 한다. 코코아 객체에게 메시지를 보낼 때는 고정된 메모리 주소를 사용해서 컴파일 시점에 미리 바인드할 필요가 없다. 대신 메시지에 대한 셀렉터를 넘겨주면 실행 중에 메서드를 찾아 늦게 바인드한다.
SEL theSelector = @selector(drawSomething);
이처럼 셀렉터를 선언할 떄는 SEL 타입을 사용하고, 변수는 @selector() 예약어를 사용해서 설정한다. 결과정으로 theSelector는 drawSomething이라는 이름을 가진 메서드를 골라서 쓸 수 있는 상태가 된다. 해당 객체에 메서드가 없다면 동적으로 바인드되지 않아 에러가 날 수 있다. 참고로 런타임 API 중 method_getName() 함수를 사용하면 @selector()와 동일하게 셀렉터를 찾을 수 있다.
struct objc_method
{
SEL method_name;
char* method_types;
IMP method_imp;
};
typedef objc_method Method;
런타임에서 객체 메서드를 저장하는 구조체는 위와 같다. method_name 항목은 메서드 이름과 파라미터 키워드를 포함하는 메서드 시그니처를 SEL 타입으로 저장한다. method_types항목은 파라미터들에 대한 타입을 문자열 타입으로 저장한다. 마지막으로 method_imp 항목은 IMP 타입으로 메서드 구현 포인터를 저장한다. IMP 타입은 C언어에서 함수 포인터처럼 실제 메서드의 구현 부분의 메모리 주소를 포인터로 저장한다.
런타임 API 중에 class_getClassMethod() 함수 또는 class_getInstanceMethod() 함수에 클래스 타입과 SEL 타입을 넘기면 위와 같은 Method 구조체를 얻을 수 있다. 만약 구현 포인터를 얻으려면 class_getMethodImplementation() 함수를 직접 호출하거나, 해당 객체에 -methodForSelector: 메시지를 보내도 동일하게 동작한다. 상속 관계에 따라서 일부 메서드에 대한 셀렉터는 동일하지 않을 수도 있다.
셀렉터 실행(perform selector)과 지연 실행(delayed perform)
아래 세 줄은 결과적으로 동일하게 동작하는 코드다.
[myPen drawSomething];
[myPen performSelector:@selector(drawSomething)];
[myPen performSelector:theSelector];
첫 번째는 직접 메시지를 보내는 방식이고, 두 번째는 NSObject에 준비된 -performSelector:를 활용하여, drawSomething 메서드 셀렉터를 찾아서 셀렉터를 실행하는 방식이다. 세 번째는 theSelector = @selector(drawSomething) 방식으로 미리 찾았던 셀렉터를 실행하는 방식이다.
…중략…
메시지를 보내면서 일부러 전달하는 시점을 지연시키는 방법도 셀렉터 실행으로 가능하다. NSObject 클래스에 준비된 -performSelector:(SEL)aSelectorwithObject:(id)anArgument afterDelay:(NSTImeInterval)delay 메서드를 사용하면 된다. 메시지는 마지막 인자 값 delay(초단위)만큼 지연돼서 객체에 전달된다. 내부적으로는 해당 스레드에 이벤트를 감시하는 런루프에 전달되고, 런루프에서 지연 시간 동안 기다린 후에 해당 객체로 메시지를 전달하는 방식이다. 이 ㄷ때 런루프는 지연 시간에 대한 정확한 전달 시점을 보장해주지는 못 한다. 따라서 매우 정확한 메시지 지연을 위해서는 pthread 계열 API를 사용하거나 다른 방식으로 구현해야 한다. 이벤트가 아주 많이 쌓이는 경우가 아니라면, 일반적으로 셀렉터 지연 실행 방식은 잘 동작한다.
최근에는 셀렉터 실행 방식보다는 애플이 만든 저수준 병렬 처리 라이브러리 GCD 방식을 사용하도록 권장하고 있다. ARC 환경에서 동적으로 셀렉터를 찾아서 실행하는 방식으로 메서드를 실행할 경우, 메서드 내부에서 만들어진 객체가 사라지지 않는 경우가 발생할 수 있다. 지연 실행의 경우도 dispatch_after() 같은 GCD 방식으로 대체하는 것이 좋다.
타깃과 액션
셀렉터 패턴을 자주 활용하는 경우는 타깃과 액션을 사용할 경우다. 사용자 인터페이스(일반적으로 뷰) 객체의 특정한 이벤트를 받을 객체를 타깃으로 지정하고, 이벤트를 받아 처리할 메서드를 액션으로 지정한다. 타깃과 액션에 대한 연결은 실행 중에도 얼마든지 가능하다. 이렇게 사용자 인터페이스 객체와 이벤트 처리 로직을 느슨하게 연결하면, 코드와 분리해서 뷰 객체에 대한 재사용성을 높일 수 있다. 또는 여러 사용자 인터페이스 객체에 대한 처리 코드가 중복되지 않도록 하나의 타깃과 액션으로 묶어서 처리도 가능하다. 이렇게 객체 이벤트를 처리하는 액션을 지정할 때도 셀렉터를 사용한다. 앞에서 설명했던 것처럼, 셀렉터는 실행 중에 문자열로 지정하는 방식도 가능하기 때문에 특정 객체의 이벤트 처리를 실행 중에 타깃과 액션으로 지정해서 연결할 수도 있다.
7.2 객체 사이 결합성을 줄여주는 패턴
7.2.1 싱글턴 패턴
디자인 패턴에서의 싱글턴은 ‘인스턴스가 딱 하나만 있는 객체’를 의미한다. 앱 내부에서 공통적으로 사용하는 객체 인스턴스가 딱 하나만 있기 때문에 다른 객체들이 서로 공유해서 사용할 수 있다. 코코아 프레임워크에서는 ‘공유 인스턴스(shared instance)’라는 용어를 사용하기도 한다. 예를 들어 NSFileManager나 UIApplication 클래스의 객체는 싱글턴으로 공유 인스턴스를 사용한다.
UIApplication 공유 인스턴스
UIApplication 클래스는 iOS앱 프로젝트에서 자주 사용하는 싱글턴 객체 중에 하나다. 모든 iOS 앱은 main.m 파일의 main() 함수 내부에 있는 UIApplicationMain() 함수를 통해 UIApplication 싱글턴 객체를 생성한다. 이렇게 만들어진 UIApplication 객체는 앱의 이벤트 전달과 액션 처리를 위한 무한 루프(RunLoop)를 가지고 있다. 앱 수준에서 중요한 이벤트와 생명주기 관리는, UIApplicationMain()의 네 번째 인자 값으로 넘어가는 UIApplicationDelegate객체에서 처리한다.
UIApplication 객체의 또 하나 중요한 기능은 UIWindow 객체들을 관리한다는 것이다. 초기 iOS는 앱에서 여러 화면을 윈도 단위로 처리하지 않고, 뷰 단위로 처리하도록 했었다. AppleTV와 화면 연동이 가능해면서, UIScreen 객체와 함께 멀티 스크린의 멀티 윈도 앱 개발이 가능해졌다. UIApplication은 UIWindow의 목록을 갖고 있어서 현재 표시는 키윈도를 찾을 수 있도록 해준다.
싱글턴 구현 방법
싱글턴 패턴을 구현하는 방식은 다양하다. 앱 시작 시점에 미리 만들어놓고 곧바로 공유 인스턴스를 참조해서 쓰는 방법이 있고, 공유 인스턴스에 처음 접근하는 뒤늦은 시점에 객체 인스턴스를 생성하는 방법도 있다. 또는 앱 생명주기에 맞춰서 한 번만 실행되는 부분에서 객체인스턴스를 미리 만들어두는 일반적인 방법도 있다. 최근에는 인스턴스를 참조할 때마다 GCD 기반 API인 dispatch_once() 함수를 사용해서 딱 한 번만 실행하도록 작성하는 방식을 많이 쓰고 권장하고 있다.
NSFileManager 공유 인스턴스
초기 OS X 파운데이션에서 NSFileManager 클래스는 +defaultManager 메시지를 보내고 난 이후에 공유 인스턴스를 통해서만 파일 시스템에 접근할 수 있도록 구현했었다. 하지만 이렇게 접근하는 공유 인스턴스는 멀티 스레드 환경에서 안정적이지 않다. …중략… 각 스레드 별로 NSFileManager 객체 인스턴스를 생성해서 쓰도록 권장하고 있다. …중략…
생각거리
NSFileManager의 경우처럼 싱글턴 객체가 모든 경우에 효과적이지는 않다. 객체 인스턴스가 하나만 존재하기 때문에 멀티 스레드 환경이나 참조하는 객체가 너무 많은 경우에는 싱글턴 객체 접근하는 것 자체가 과부하가 될 수도 있다. 다른 객체와 과도한 결합성을 줄이는 데는 도움이 되지만, 싱글턴 객체가 병목 구간이 되거나 취약 지점이 될 위험이 있다.
7.2.2 옵저버 패턴
코코아 객체 간 관계 개선을 위해서 싱글턴과 함께 가장 많이 사용하는 패턴이 바로 옵저버 패턴이다. 대부분 객체 상태 또는 내부 값이 바뀌면 그 값을 참조하는 의존적인 객체에 알려주고 새로운 값으로 변경해야만 한다. 이런 경우 관련된 객체들 사이 결합성이 높아진다. 옵저버 패턴은 특정 객체의 상태가 바뀌었을 때, 해당 객체와 관련이 있는 다수 객체에 한꺼번에 알려주기 위해서 자주 사용하는 패턴이다. 발행/구독 패턴이라고도 한다. 특정 객체의 상태 변화에 대해 미리 구독할 객체들을 등록해놓고, 상태 변화가 발생하면 통보하는 방식으로 동작한다. 옵저버의 메서드를 지정하기 위해서 셀렉터 패턴도 함께 사용한다.
통보(notification)
코코아 프레임워크에서는 옵저버 패턴을 상태 변화가 있는 객체가 n개의 대상 객체에 메시지를 한꺼번에 보내는 방식으로 구현했다. 코코아의 모든 객체는 옵저버가 될 수 있으며, 자기 자신이나 다른 객체를 통보 센터 (notification center)에 옵저버로 등록할 수 있다. 모든 객체는 등록되어 있는 옵저버들에게 통보 센터를 통해서 통보를 전달할 수 있다. 통보 센터에서는 보내려는 통보를 확인하고 통보를 전송할 조건을 확인해서 조건에 부합하는 옵저버 객체에 메시지를 보낸다.
생각거리
통보를 보내는 -postNotification 동작은 기본적으로 동기방식으로 동작한다. 따라서 옵저버 개수가 엄청 많거나 옵저버에서 처리하는 동작이 느리면 통보를 보내는 객체가 멈춰서 기다리게 된다. 이런 현상을 피하고 싶다면 NSNotificationCenter 대신에 NSNotificationQueue에 통보를 넣어서 비동기 방식으로 동작하도록 해야한다. 마지막으로 NSNotificationCenter 당연히 앱 내부에서만 통보를 보낼 수 있다. …중략…
7.2.3 응답 체인 패턴
특정한 메시지에 응답할 객체를 바인드하거나 지정해서 결합성을 높이지 않고, 응답할 가능성이 있는 객체들을 체인 형태로 확인하고 더 이상 응답할 객체가 없을 때까지 반복해서 확인하는 방식이다. 체인에 등록된 객체들은 해당 메시지를 처리하거나 혹은 처리하지 않고 다음 객체로 전달할 수 있다.
…중략…
응답 객체(Responder Object)
코코아에서는 뷰나 윈도를 포함해서 이벤트를 받아서 응답하는 모든 객체의 최상위 클래스는 NSResponder다. 따라서 화면을 구성하는 모든 클래스는 응답이 가능한 객체다. 화면을 구성하는 뷰 객체들뿐만 아니라 NSViewControler 클래스나 NSApplication 클래스도 마찬가지로 NSResponder를 상속하고 있다. 코코아 터치에서는 UIResponder라는 동일한 수준의 클래스가 존재하고, UIViewController와 UIApplication 클래스도 UIResponder를 상속한다.
처음 응답 객체(First Responder)
Xcode에서 인터페이스 객체를 작업할 때, 스토리보드 화면이나 Xib에서 Placeholders 항목에 ‘1’이 그려진 사각형 모양을 한 처음 응답 객체(First Responder) 항목이다. 처음 응답 객체는 응답 체인의 처음 항목을 지칭하는 프락시 객체다. 특정 메시지나 이벤트를 처리할 객체를 고정적으로 바인드하고 싶지 않다면, 이벤트 처리 대상을 처음 응답 객체로 지정한다. 그러면 응답 체인(Responder Chain)을 따라 응답 객체에게 해당 이벤트를 순서대로 보낼 수 있다. 중략…
만약 응답 체인의 객체들을 모두 확인했는데 처리한 객체가 없다면 해당 이벤트는 무시한다. 특정 객체를 지정했다가 응답할 메서드가 없는 경우 크래시가 발생하는 것과는 대조적이다. …중략… Hit-Test가 가능한 최상위 뷰가 처음 응답 객체가 되고, 그 뷰를 포함하는 상위 뷰를 따라가서 최상위 뷰를 소유하는 뷰 컨트롤러 객체도 확인한다. 뷰 컨트롤러 다음에는 해당 뷰 컨트롤러를 포함하는 윈도 객체와 마지막으로는 애플리케이션 객체까지 확인한다. 이런 과정을 거치기 위해서 뷰 컨트롤러와 윈도, 애플리케이션 클래스까지 응답 객체를 상속받아서 구현한 것이다.
응답 체인 탐색
일반적으로 응답 체인은 뷰 계층을 따라서 포커스된 화면 객체들 중에서 특정 뷰에서부터 윈도까지 이어진다. …중략… 원한다면 -setNextResponder: (스위프트에서는 next프로퍼티) 메서드를 사용해서 다음 응답 객체를 지정할 수도 있다. 그리고 멀티 윈도 환경에서는 활성화된 키 윈도 객체 다음에는 메인 윈도로 넘어가서 다시 메인 윈도의 처음 응답 객체부터 응답 체인을 탐색하기도 한다.
…중략…
7.3 객체 내부의 복잡성을 감춰주는 패턴
객체 중심 디자인에서 객체를 설계할 때, 객체 설계 범위는 객체마다 갖는 고유한 역할과 책임을 기준으로 정한다. 객체 역할과 책임에 대한 내부 구현 방식은 최대한 감추면서, 객체를 마치 하드웨어 부속처럼 부품화하면 손쉽게 재사용이 가능해진다. …중략…
7.3.1 팩토리 추상화 패턴
팩토리 추상화는 세부 클래스를 지정하지 않고 관련된 클래스 패밀리를 생성할 수 있는 인터페이스를 제공한다. 클래스 클러스터 라고도 부른다. 객체를 생성하는 시점에 사용하는 특정한 클래스가 내부의 여러 하위 클래스를 추상화하며, 생성하는 데이터에 따라 적합한 내부 클래스로 객체 인스턴스를 생성해주기 때문에 그렇게 부른다. 파운데이션 프레임워크의 상당수 클래스는 클러스터 클래스로 구현했다. …중략… 클러스터 클래스는 객체 생성 시점에 적합한 내부 객체 인스턴스를 만들어주는 공장 역할을 담당하면서, 복잡한 파운데이션 내부 객체들을 감추고 필요한 인터페이스에만 집중할 수 있도록 도와준다.
…중략…
7.3.2 파사드 패턴
파사드 패턴은 객체 생성을 하나의 팩토리 클래스에서 추상화했던 클래스 클러스터와 비슷하다. 내부 객체들의 복잡한 관계를 감추고 하나의 클래스가 인터페이스를 모두 담당하는 패턴을 파사드 패턴이라고 한다. …중략…
코코아의 대표적인 파사드 클래스는 이미지 관련 클래스인 NSImage와 UIImage 클래스다. 여러 이미지 포맷 처리, 압축 데이터 처리, 그리는 방식, 헤더 데이터 구조 등 이미지와 관련된 내부 구조를 자세히 모르더라도 이미지 클래스 인터페이스 하나로 거의 모든 동작을 실행한다. 벡터 방식의 PDF 파일이든 TIFF 파일이든 PNG 파일이든 상관없이 동일한 인터페이스로 디바이스 화면에 맞춰서 동작한다. 내부적으로 다양한 형식의 이미지를 다루기 때문에 인터페이스 동작이 모든 데이터에서 의도대로 동작하지 않을 수도 있다. 예를 들어 벡터 방식 이미지는 비트맵 이미지일 경우와 달리 특정 픽셀의 색상을 구하는 동작이 작동하지 않는다.
파사드 패턴으로 구현한 또 다른 예는 코어 데이터에서 사용하는 영구 저장 객체 (Persisten Storage Coordinator) 클래스에서 찾을 수 있다. 이 객체는 하위에 어떤 저장소를 선택해서 쓰더라도, 동일한 인터페이스로 여러 영구 저장 구조에 접근할 수 있도록 지원한다. 내부 데이터가 Plist 형식의 XML이든지 데이터베이스 SQLite든지 상관없이 동일하게 처리할 수 있다.
7.3.3 번들 패턴
번들 구조는 실행 파일과 프로그램에서 사용하는 인터페이스, 문자열, 이미지, 음악 같은 리소스를 디렉터리에 구조적으로 묶어놓은 것을 말한다. 실행 파일은 하드웨어나 CPU에 따라 다른 아키텍처를 포함하는 복수 실행 파일을 포함할 수 있다. …중략…
Info.plist 파일
번들 구조에서 앱에 대한 기본적인 정보들은 Info.plist 파일에 저장한다. 주의할 점은 반드시 Info의 I를 대문자로 해야 한다는 것이다. 키 값은 코어 파운데이션 CFBundle에 선언되어 있어서 상수앞에 CF접두어가 붙어있다.
…중략…
리소스 지역화
번들 내부에서 리소스 파일을 찾을 때는 다음과 같은 규칙을 따른다.
- 지역화하지 않는 글로벌 리소스는 Resources 디렉터리에 포함한다.
- 사용자 지역 설정에 맞춰서 지역별 지역화 리소스를 찾는다.
- 사용자 언어 설정에 맞춰서 언어별 지역화 리소스를 찾는다.
- Info.plist에 기본 언어로 지정한 개발 언어 리소스를 찾는다.
글로벌 리소스 파일들은 리소스 디렉터리 바로 아래에 있어야 한다. 리소스 지역화 디렉터리는 [언어]_[지역].lproj 형식으로 만들어지며, 각 지역화에 맞는 리소스 파일들을 동일한 이름으로 지역화 디렉터리에 넣어야 한다.
iOS 앱 번들
iOS 앱은 맥 앱보다는 기본적인 번들 구조가 단순하다. iOS 앱은 실행 파일과 리소스 전체가 하나의 디렉터리로 구성된다. 물론 앱 스토어에서 배포하는 앱(IPA) 파일을 풀어보면 인증서와 구매 정보를 포함하기 때문에 내용은 좀 더 많아진다. iOS용 앱의 리소스는 개발 단계에서 사용하는 스토리보드나 Xib, 이미지 파일과 100% 동일하지 않다. 앱 배포 단계에서 인터페이스 파일도 컴파일해서 .storyboardc 파일로 바꾸고 일부 이미지 파일도 데이터 인코딩을 변경해서 파일 크기를 조정한다.
iOS 앱 번들에는 앱 바이너리 실행 파일과 Info.plist, 앱 아이콘과 런치 이미지 등을 포함한다. 이렇게 번들 구조는 OS X와 iOS에서 앱 뿐만 아니라 프레임워크와 플러그인까지 다양한 곳에서 활용한다. …중략…
7.3.4 프락시 패턴
프락시 객체는 다른 객체를 감싸거나 대신해서 접근하는 기능을 제공한다. NSProxy 클래스는 코코아에서 유일하게 NSObject에서 상속받지 않고 NSObject 프로토콜만 구현한, 숨겨진 최상위 객체다. 단지 다른 객체를 대신하는 동작을 위해 필요한 인터페이스만 선언한 추상화 클래스다. NSProxy 인터페이스를 상속받아 만든 프락시 객체는 원하는 메시지를 다른 객체로 전달하는 역할을 한다. 코코아에 이미 구현되어 있는 프락시 클래스는 다른 앱이나 다른 쓰레드에 있는 객체를 프락시로 연결해주는 NSDistanceObject 클래스가 있다. …중략…
Uploaded by N2T