[Effective Objective-C] #27 클래스 확장 카테고리를 이용해 상세한 구현을 숨기라.
출처 : Effective Objective-C
-
클래스가 외부로 공개한 메서드와 인스턴스 변수 이외의 메서드와 변수를 갖길 원할 때가 많이 있다.
일단 이런 인스턴스 변수와 메서드들을 외부로 공개하고 문서에 내부용(private)이기 때문에 사용하면 안 된다고 명시할 수 있다.
무엇보다도 오브젝티브-C 의 어떠한 메서드나 인스턴스 변수도 실제로는 프라이빗이 아니다.
이는 동적 메시징 시스템이 동작하는 방식 때문이다.
그렇다 하더라도 꼭 공개할 필요가 있는 것만 공개하는 것이 좋은 사용 방법이다.
-
클래스 확장 카테고리는 일반적인 카테고리와는 다르게 확장(continuation)이라는 클래스의 구현 파일에 정의해야 한다.
중요한 점은 클래스 확장 카테고리가 추가 인스턴스 변수를 선언할 수 있는 유일한 카테고리라는 것이다.
또 이 카테고리는 특정 구현을 가지지 않는다.
그것 내부에 정의된 모든 메서드는 클래스의 메인 구현 파일에 있어야 한다.
-
다른 카테고리와는 다르게 클래스 확장 카테고리는 이름이 없다.
EOCPerson 이라는 이름의 클래스를 위한 클래스 확장 카테고리는 다음과 같다.
@interface EOCPerson ()
...
@end
-
클래스 확장 카테고리는 메서드와 인스턴스 변수를 같은 곳에 함께 정의할 수 있다.
이는 비파괴( nonfragile ) ABI 덕분에 가능하다.
객체의 크기를 몰라도 객체를 사용할 수 있다는 것을 뜻한다.
그렇기 때문에 인스턴스 변수를 퍼블릭 인터페이스에 정의할 필요가 없다.
클래스 사용자가 클래스의 배치(layout)을 알 필요가 없기 때문이다.
이러한 이유로 클래스에 인스턴스 변수를 추가하는 것은 클래스의 구현 파일뿐 아니라 클래스 확장 카테고리에서도 가능하다.
이렇게 하기 위해 간단히 오른쪽에 중괄호를 추가하고 그 안에 인스턴스 변수를 선언하면 된다.
@interface EOCPerson(){
NSString *_anInstsanceVariable;
}
// method declare
@end
@implementation EOCPerson {
int _anotherInstanceVariable;
}
// method impl.
@end
인스턴스 변수는 퍼블릭 인터페이스에 정의할 수 있다.
그러나 그것들을 클래스 확장 카테고리 또는 구현 부분에 숨기면 오직 내부에서만 알려진다는 이점이 생긴다.
퍼블릭 인터페이스에서 인스턴스 변수를 private 으로 표시하더라도 자세한 구현은 여전히 노출된다.
-
interface 에 private instance variable 을 추가하는 것은 다음과 같이 한다.
@interface EOCClass : NSObject {
@private
EOCSuperSecretClass *_secretInstance;
}
@end
-
구현 구역에 인스턴스 변수를 추가해도 된다.
문법적으로는 클래스 확장 카테고리에 추가하는 것과 동일하다.
취향대로 사용하면 되지만 카테고리에 추가하는 것이 선호된다.
모든 데이터 정의를 한곳에 모아둘 수 있기 때문이다.
또 클래스 확장 카테고리에 프로퍼티가 정의되어 있을 수 있다.
그래서 여기에 추가 인스턴스 변수를 선언하는 것이 좋다.
이 인스턴스 변수는 실제로는 프라이빗이 아니다.
런타임이 사용하는 메서드는 항상 찾아볼 수 있기 때문이다.
그러나 의도로 보나 목적으로 보나 이것은 내부용이다.
또한 퍼블릭 헤더에 선언하지 않았기 때문에 이 코드를 라이브러리의 일부분으로 두면 훨씬 찾기 어려울 것이다.
-
클래스 확장 카테고리 오브젝티브-C++ 코드에서 특별히 더 유용하다.
오브젝티브-C++ 은 오브젝티브-C 와 C++ 코드를 모두 이용해 작성할 수 있기 때문이다.
#import <Foundation/Foundation.h>
#include “SimeCppClass.h"
@interface EOCClass : NSObject {
@private
SomeCppClass _cppClass;
}
위의 header 는 .mm 확장자를 가져야 한다.
.mm 확장자는 이 파일을 오브젝티브-C++ 로 컴파일할 것이라고 컴파일러에 알린다.
이 확장자를 붙이지 않으면 SomeCppClass.h 를 포함할 수 없다.
그러나 C++ 클래스인 SomeCpp 는 전체를 포함(import)해야 한다.
_cppClass 인스턴스 변수의 크기 정보 같은 컴파일러가 변수 정의를 해석(resolve)할 때 필요한 정보를 얻기 위해서다.
이 클래스를 사용하기 위해 EOCClass.h 를 포함하는 모든 파일은 오브젝티브-C++ 로 컴파일되어야 한다.
이는 쉽게 제어에서 벗어나 결국엔 전체 앱이 오브젝티브-C++ 로 컴파일될 것이다.
지극히 정상이지만 이렇게 되는 것은 아주 좋지 않은 것으로 보인다.
특히 코드가 다른 앱이 사용할 수 있는 라이브러리로 묶여 배포될 때에는 서드 파티 개발자가 이 라이브러리를 사용하기 위해 자신의 모든 파일을 .mm 확장자로 변경하는 것은 아주 좋지 않다.
-
// impl
#import “EOCClass.h"
#include “SomeCppClass.h
@interface EOCClass(){
SomeCppClass _cppClass;
}
@end
@implementation EOCClass
@end
이제 EOCClass 헤더는 C++ 로부터 자유롭다.
그 헤더의 사용자는 그 클래스 내부에 무엇이 있는지 알 필요가 없다.
-
클래스 확장 카테고리의 또 다른 좋은 사용 예는 public 인터페이스에 정의된 읽기 전용 프로퍼티를 클래스 내부에서는 읽기 쓰기로 변경(expand)할 때다.
보통은 직접 인스턴스 변수에 접근하기보다는 세터 메서드를 통해 설정하길 원할 것이다.
다른 객체가 듣고 있는(listening) 키-값 관찰 알림을 발생시킬 수 있기 때문이다.
클래스 확장 카테고리 또는 다른 카테고리에 있는 프로퍼티와 클래스 인터페이스에 있는 프로퍼티는 반드시 속성이 같아야 한다.
읽기 전용을 읽기 쓰기로 변경하는 것은 예외다.
// header
@interface EOCPerons : NSObject
@property (nonatomic, copy, readonly) NSString* firstName;
@property (nonatomic, copy, readonly) NSString* lastName;
...
@end
// impl
@interface EOCPerson()
@property (nonatomic, copy, readwirte) NSString* firstName;
@property (nonatomic, copy, readwirte) NSString* lastName;
@end
이제 EOCPerson 구현은 setFirstName: 과 setLastName: 을 호출하는 것 같이 세터 접근자를 사용할 수 있을 뿐 아니라 점문법으로도 프로퍼티를 사용할 수 있다.
이렇게 하면 객체가 공개적으로는 불변 상태를 유지하지만 내부적으로는 필요에 따라 데이터를 조작할 수 있다.
그렇기 때문에 클래스 내부에 캡슐화된 데이터를 외부에서는 변경할 수 없지만 인스턴스 내부에서는 조작 가능하다.
주의해야 할 점이 있는데 이 방법은 객체의 관찰자(observer)가 객체의 프로퍼티를 읽는 동시에 내부에서 그 프로퍼티를 쓰려고 할 때 발생하는 경쟁 상태(race condition) 문제가 있다.
동기화를 잘 사용하면 이 문제를 완화시킬 수 있다.
-
클래스 확장 카테고리의 또 다른 좋은 사용 예는 오직 클래스의 구현 내에서만 내부적으로 사용할 프라이빗 메서드들을 선언하는 것이다.
클래스 구현부에서만 사용하는 메서드를 클래스 구현부 내에 문서화 할 수 있기 때문이다.
@interface EOCPerson()
- (void)p_privateMethod;
@end
-
최신 컴파일러를 사용한다면 메서드를 사용하기 전에 선언할 필요는 없지만 클래스 확장 카테고리 내에 위와 같이 메서드를 선언하는 것은 여전히 좋은 생각이다.
그렇게 하면 어떤 메서드가 있는지 한곳에 문서화할 수 있기 때문이다.
나는 클래스를 구현하기 전에 다음과 같이 메서드 프로토 타입을 작성하는 것을 좋아한다.
그런 다음 메서드를 구현한다.
이는 클래스의 가독성을 개선하는 아주 좋은 방법이다.
-
마지막으로 클래스 확장 카테고리는 객체가 따르는(conform) 프로토콜을 내부적으로만 알려야 할 때 이 내용을 명시하기 아주 좋은 장소다.
가끔 퍼블릭 인터페이스를 통해 특정 카테고리를 따르고 있다는 정보가 공개되는 것을 원하지 않을 때가 있다.
Delegate 를 따른다고 퍼블릭 인터페이스에 선언하는 대신에 간단히 클래스 확장 카테고리 안에 선언하면 된다.
기억할 점
클래스에 인스턴스 변수를 추가하기 위해 클래스 확장 카테고리를 사용하라.
메인 인터페이스에 있는 읽기 전용 프로퍼티를 클래스 내부에서는 세터 접근자를 이용해 값을 설정할 수 잇도록 하고 싶으면 클래스 확장 카테고리에 그 프로퍼티를 읽기 쓰기로 재선언하라.
프라이빗 메서드를 위한 메서드 프로토타입은 확장 카테고리 내에 선언하라.
클래스가 따르는 프로토콜 정보를 내부에 숨기고 싶으면 클래스 확장 카테고리를 사용하라.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[iOS] Swift vs. Objective-C (0) | 2017.09.13 |
---|---|
[Effective Objective-C] #28 프로토콜을 이용해 익명 객체를 제공하라. (0) | 2017.09.12 |
[Effective Objective-C] #26 카테고리에는 프로퍼티를 사용하지 말라. (0) | 2017.09.10 |
[Effective Objective-C] #25 서드 파티 클래스에는 반드시 카테고리 이름을 접두어로 붙여라 (0) | 2017.09.09 |
[Effective Objective-C] 목차와 요약을 통해 한 눈에 알아보는 Effective Objective-C #17 ~ #24 (0) | 2017.09.08 |
댓글