본문 바로가기
프로그래밍 놀이터/iOS

[Effective Objective-C] #27 클래스 확장 카테고리를 이용해 상세한 구현을 숨기라.

by 돼지왕 왕돼지 2017. 9. 11.
반응형

 [Effective Objective-C] #27 클래스 확장 카테고리를 이용해 상세한 구현을 숨기라.


출처 : Effective Objective-C

.mm, class extension category, delegate, ovserver, Private, private instance variable, public 인터페이스, race condition, [Effective Objective-C] #27 클래스 확장 카테고리를 이용해 상세한 구현을 숨기라., 가독성, 경쟁 상태, 관찰자, 구현 구역, 동기화, 동적 메시징 시스템, 메서드 프로토타입, 문서화, 불변, 비파괴 abi, 상세 구현, 세터 접근자, 유일한 카테고리, 인스턴스 메서드, 인스턴스 변수, 읽기 쓰기 확장, 읽기 전용 프로퍼티, 점문법, 주의사항, 추가 인스턴스 변수, 카테고리 이름, 캡슐화, 클래스 확장 카테고리, 프로토콜


-

클래스가 외부로 공개한 메서드와 인스턴스 변수 이외의 메서드와 변수를 갖길 원할 때가 많이 있다.

일단 이런 인스턴스 변수와 메서드들을 외부로 공개하고 문서에 내부용(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 를 따른다고 퍼블릭 인터페이스에 선언하는 대신에 간단히 클래스 확장 카테고리 안에 선언하면 된다.




기억할 점


클래스에 인스턴스 변수를 추가하기 위해 클래스 확장 카테고리를 사용하라.


메인 인터페이스에 있는 읽기 전용 프로퍼티를 클래스 내부에서는 세터 접근자를 이용해 값을 설정할 수 잇도록 하고 싶으면 클래스 확장 카테고리에 그 프로퍼티를 읽기 쓰기로 재선언하라.


프라이빗 메서드를 위한 메서드 프로토타입은 확장 카테고리 내에 선언하라.


클래스가 따르는 프로토콜 정보를 내부에 숨기고 싶으면 클래스 확장 카테고리를 사용하라.




반응형

댓글