[Objective-C] 카테고리
출처 : OS X 구조를 이해하면서 배우는 Objective-C Chap 10.
Notice : 정리자(돼지왕 왕돼지)가 remind 하고 싶은 내용이나 모르는 내용 기반으로 정리하는 것이기 때문에 구체적인 내용은 책을 사서 보시기를 권장드립니다.
10.1. 카테고리 선언과 정의
* 10.1.1. 카테고리란
-
카테고리는 클래스와 마찬가지로 인터페이스로 선언하고, 구현 부분에 그 정의를 기술한다.
단, 인스턴스 변수를 선언할 수는 없으며, 카테고리는 메서드만 포함한다.
메서드는 인스턴스 메서드와 클래스 메서드 양쪽 다 가능하다.
-
카테고리 구현 부분에도 인스턴스 변수를 선언할 수 없다.
메서드 정의에서는 그 클래스의 다른 메서드를 호출하거나 인스턴스 변수에 자유롭게 접근할 수 있다.
또한 일반 클래스 구현 부분과 마찬가지로 인터페이스에 나오지 않는 지역 메서드 정의나 C 함수도 작성할 수 있다.
* 10.1.2. 카테고리와 파일 작성
-
메인 인터페이스를 포함한 헤더 파일에 다른 인터페이스도 모두 포함시키는 방법이 전형적인 사용법이다.
-
카테고리의 인터페이스와 구현 부분을 포함한 파일명은 알기 쉽게 적당히 정해도 충분하지만, 보통은 “클래스명+카테고리명.m” 처럼 정하는 것이 표준이다.
-
카테고리의 인터페이스는 다음 원칙을 지켜야 한다.
카테고리 인터페이스는 메인 인터페이스를 참조해야만 한다.
카테고리 구현 부분은 대응하는 인터페이스를 참조해야만 한다.
어떤 카테고리 메서드를 사용할 때 호출하는 쪽에서는 그 메서드를 포함한 인터페이스를 참조해야만 한다.
* 10.1.3. 서브 모듈로서의 카테고리
* 10.1.4. 메서드 전방 선언
-
인터페이스에 선언되어 있지 않은 지역 메서드는 프로토타입 선언을 하지 않은 C 함수와 마찬가지로 그 정의보다 앞에 있는 코드에서 사용할 수 없다.
카테고리로 이 문제를 해결할 수 있다.
지역 카테고리를 선언해 그 안에서 메서드를 전방 선언하는 것이다.
* 10.1.5. 비공개 메서드
-
서브 클래스가 추가한 메서드가 슈퍼 클래스의 비공개 메서드를 알지 못한 채 덮어쓸 가능성이 있다.
이 문제를 예방하기 위해 애플 사 가이드 라인에는 비공개 메서드명에 고유 접두어를 붙이도록 추천한다.
* 10.1.6. 클래스 확장
-
클래스 확장 선언은 카테고리와 비슷하지만 카테고리명을 지정하지 않고 괄호만 사용한다.
-
클래스 확장에는 인스턴스 변수를 만들 수도 있다.
여기에 작성한 인스턴스 변수는 클래스 본체 인터페이스의 인스턴스 변수 작성에 추가된다.
-
클래스 확장으로 선언한 메서드는 클래스 자체의 구현 부분에 작성해야만 한다.
-
클래스 확장으로 선언한 인스턴스 변수는 클래스 본체 및 클래스 확장의 선언을 임포트한 카테고리에서만 참조할 수 있다.
클래스 본체의 구현 부분에서도 인스턴스 변수를 선언할 수 있지만 여기서 선언한 인스턴스 변수는 그 파일 안에서만 유효하다.
이에 비해 클래스 확장에서 선언한 인스턴스 변수는 여러 카테고리에서 참조할 수 있다.
* 10.1.7. 카테고리와 선언 프로퍼티
-
프로퍼티 선언을 카테고리 인터페이스에 포함할 수 있다.
하지만 카테고리의 구현 부분에 @synthesize 를 포함할 수는 없다.
카테고리 선언에 프로퍼티를 포함할 때 구현 부분에는 접근자 메서드를 정의해야 한다.
-
클래스 확장에서도 프로퍼티 선언을 포함할 수 있다.
이 프로퍼티는 클래스 본체의 구현 부분에 @synthesize 나 접근자 메서드를 기술할 수 있다.
클래스 확장으로 선언된 인스턴스 변수에 대응하는 선언 프로퍼티를 작성하는 것도 가능하다.
10.2. 기존 클래스에 카테고리 추가
* 10.2.1. 새로운 메서드 추가
-
카테고리로 메서드를 추가하는 것은 편리하지만 남용하면 안 된다.
편리하다고 기존 클래스에 계속 추가하는 건 좋은 프로그래밍 방식이 아니다.
* 10.2.2. 메서드 추가 예제
cf) 개수가 변하는 인수를 받는 메서드 정의
-
개수가 변하는 인수에는 다음과 같은 제약이 있다.
인수 목록이 개수가 변하는 인수로만 이루어져서는 안 된다.
개수가 변하는 인수는 인수 목록 마지막에 나와야 한다.
개수가 변하는 인수형은 프로그램이 책임지고 관리해야 한다.
-
개수가 변하는 인수를 사용한 함수의 정의는 인수가 전혀 없는 상태의 함수를 호출할 수 없다.
인수 목록 중간이 정해지지 않은 함수도 정의할 수 없다.
인수 목록에는 다른 형을 섞어 사용할 수 있지만 이것이 제대로 동작하는지는 프로그램 쪽에서 책임져야 한다.
-
개수가 변하는 인수를 가진 메서드와 함수 정의는 헤더 파일로 stdarg.h 가 필요하다.
인수 선언에서 변경 가능한 인수는 점 세 개 ( … ) 로 표현한다.
개수가 변하는 인수를 사용하려면 va_list 형의 변수가 필요하다.
-
va_start( var_name, 개수가 변하는 인수의 매개변수명 );
...
f = va_arg ( var_name, 형식명 ); // nil 이 나올 때 까지 인수 갯수만큼 반복.
...
va_end( pvar );
-
va_arg 는 실제로 개수가 변하는 인수값을 추출.
이 식을 실행할 때마다 인수의 다음 값이 추출된다.
va_arg() 의 두번째 인수에는 추출하고 싶은 인수형을 적는다.
이 형은 항상 같지 않아도 된다.
하지만 여기서 지정한 형과 실인수가 되는 식의 형이 다르다면 동작을 보증할 수 없다.
-
va_end() 는 인수값 추출을 끝낸다.
va_start() 와 va_end() 는 반드시 함께 사용해야만 한다.
-
개수가 변하는 인수의 마지막에 NULL 이나 nil 을 둠으로써 인수의 끝이라는 걸 뜻하는 경우는 자주 있는데,
이런 함수를 사용할 떄 NULL 을 붙이는 걸 잊으면 오동작할 수 있다.
함수 프로토타입 선언 끝에 NS_REQUIRES_NIL_TERMINATION 매크로를 기술해두면 컴파일할 때 이런 실수를 경고할 수 있다.
* 10.2.3. 기존 메서드 덮어쓰기
-
기존 메서드와 같은 이름의 메서드가 새로운 카테고리로 정의되면 새로 정의된 메서드가 유효해진다.
이렇게 하면 상속을 사용하지 않고 메서드를 덮어쓰는 트릭이 가능하다.
하지만 기존 메서드를 부주의하게 덮어쓰면 생각지도 않은 문제가 발생할 수도 있다.
-
여러 카테고리에서 같은 메서드를 정의하면 어떤 카테고리의 메서드를 실행할지 알 수 없어 원하는 정의의 메서드를 사용한다고 보증할 수 없다.
-
카테고리를 사용해서 기존 메서드를 바꾸는 방법은 추천하지 않는다.
기존 메서드를 덮어쓴다고 컴파일러나 링커가 경고를 해주지는 않으므로 실수로 덮어쓰지 않도록 메서드명을 잘 관리하자.
10.3. 연상 참조
* 10.3.1. 연상 참조 개요
-
카테고리를 만들어 클래스에 메서드를 추가할 수 있지만 인스턴스 변수는 추가할 수 없다.
하지만 런타임 시스템 기능을 사용하면서는 어떤 객체에 대해 다른 객체 참조를 추가할 수 있게 되었다.
이 기능을 연상 참조( associative references ) 라고 한다.
이 기능과 카테고리를 조합하는 걸로 상속 기능을 사용하지 않고 클래스 정의를 더 유연하게 확장할 수 있다.
* 10.3.2. 객체 연결과 참조
-
void objc_setAssociatedObject(id object, void* key, id value, objc_AssociationPolicy policy)
첫번째 인수 object 에 대해 key 로 지정한 어드레스를 키로 세 번째 인수 value 를 참조할 수 있도록 연결한다.
네번째 인수 policy 는 연결 방법을 지정한다.
value 에 nil 을 넘기면 key 로 지정한 연결을 풀 수 있다.
-
id objc_getAssociatedObject( id object, void* key)
첫번째 인수 object 에 대해 key 어드레스에 따라 연결한 객체를 돌려준다.
연결이 없으면 nil 을 돌려준다.
-
static char rKey; // static 변수로 값은 지정하지 않고 주소만 할당한다.
...
objc_setAssociatedObject( obj, &rKey, r, OBJC_ASSOCATION_RETAIN );
...
id x = objc_getAssociatedObject( obj, &rKey );
...
-
key 로 지정한 어드레스는 참조 객체를 특정하기 위해서만 사용하므로
위의 예제에서 변수 rKey 가 저장 장소로 사용되며 값이 바뀌는 일은 없다.
* 10.3.3. 객체 저장 방법
-
objc_setAssociatedObject() 의 네번째 인수 policy 에는 참조 객체가 어떻게 저장될지 지정한다.
다음과 같은 종류가 있으며, 보통 OBJC_ASSOCIATION_RETAIN 을 지정한다.
OBJC_ASSOCIATION_ASSIGN
OBJC_ASSOCIATION_RETAIN_NONATOMIC
OBJC_ASSOCIATION_RETAIN
OBJC_ASSOCIATION_COPY_NONATOMIC
OBJC_ASSOCIATION_COPY
-
OBJC_ASSOCIATION_ASSIGN 이외의 네 가지 지정은 retain 인가, copy 인가, 아토믹인가, 비아토믹인가의 조합이다.
* 10.3.4. 연결 끊기
-
객체에 연결된 참조를 모두 해제하는 런타임 함수도 있다.
void objc_removeAssociatedObjects(id object)
이 함수를 사용하면 다른 코드에서 어떤 연결을 했더라도 알아차리지 못한 채 해제될 위험이 있다.
기존 코드 어딘가에서 연상 참조가 사용되고 있을지도 모르고, 앞으로 다른 카테고리가 추가되어 새로운 연상 참조가 사용될지도 모른다.
이런 연결까지 해제되어버릴 가능성이 있다.
보통은 objc_setAssociatedObject() 값에 nil 을 넘기는 걸로 따로따로 해제하는 게 좋다.
* 10.3.5. 카테고리를 사용한 예제
-
연상 참조를 카테고리와 조합하면 프로그래밍 폭이 무척 넓어지게 된다.
다만, 지나치면 이해하기 힘든 프로그램이 되어 버리므로 서브 클래스를 만드는 등, 가능한 또 다른 방법이 있는지 검토해 보는 것이 좋다.
또한 인스턴스 변수가 아니므로 객체 복사(복제)나 아카이브 작성을 할 때는 주의해야 한다.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Objective-C] 프로토콜 (0) | 2018.01.04 |
---|---|
[Objective-C] 추상 클래스와 클래스 클러스터 (0) | 2018.01.03 |
[Objective-C] Foundation 프레임워크의 중요 클래스 - NSDictionary, NSValue, NSNumber, NSURL (0) | 2018.01.01 |
[Objective-C] Foundation 프레임워크의 중요 클래스 - NSData, NSArray, NSSet (0) | 2017.12.31 |
[Objective-C] Foundation 프레임워크의 중요 클래스 - NSString, NSMutableString (0) | 2017.12.30 |
댓글