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

[Effective Objective-C] #5 열거형을 사용해 상태, 옵션, 상태 코드를 정의하라

by 돼지왕 왕돼지 2017. 7. 26.
반응형

 [Effective Objective-C] #5 열거형을 사용해 상태, 옵션, 상태 코드를 정의하라


출처 : Effective Objective-C

2의 제곱, and, backward compatibility, C++, c++ 11, compiler 경고, default 구문, define, effective objective-c, enum, enum type, foundation 프레임워크, implicit cast, NS_ENUM, NS_OPTIONS, Objective-C, or, or 연산, state machine, Switch, typedef, underlying type, [Effective Objective-C] #5 열거형을 사용해 상태, 가독성, 값 지정, 기저 타입, 매크로, 비트 연산, 상태, 상태 머신, 상태 코드, 상태 코드를 정의하라, 시스템 프레임워크, 암묵적 캐스팅, 열거형, 열거형 타입, 옵션, 유일한 값, 전처리기, 조합, 컴파일, 컴파일러, 포워드 선언, 프레임워크, 하위 호환, 헬퍼


-

enum 은 시스템 프레임워크 전반에 걸쳐 광범위하게 사용되지만 개발자들은 잘 활용하지 않는다.

enum 은 예를 들어 에러 상태 코드들이나 조합할 수 있는 옵션들에 사용될 수 있는 상수들을 정의하는 데 매우 유용하게 쓰일 수 있다.


enum EOCConnectionState{

     EOCConnectionStateDisconnected,

     EOCConnectionStateConnected,

};



-

enum 을 사용하면 코드가 읽기 좋아진다.

컴파일러는 열거형의 각 멤버에 유일한 값을 부여한다.

0으로 시작해 1씩 증가시킨 값을 각 멤버에 부여한다.

이런 열거형 내부의 타입은 컴파일러에 따라 다르다.



-

위의 방법은 사용할 때 다음과 같이 enum 을 붙여 사용해야 해서

enum EOCConnectionState state = EOCConnectionStateDisconnected;


다음과 같이 typedef 를 추가하여 사용한다.

typedef enum EOCConnectionState EOCConnectionState;



-

C++ 11 부터 underlying type(기저 타입)을 기술할 수도 있다.


enum EOCConnectionState : NSInteger{

     ...

};


기저 타입 기술의 장점은 열거형 타입을 포워드 선언 할 수 있다는 것이다.

컴파일러가 underlying type 이 최종적으로 차지하는 크기를 알 수 없기 떄문에 타입이 사용될 때 컴파일러는 변수를 할당하기 위해 필요한 공간의 크기를 알 수 없다.


enum 의 포워드 선언은 다음과 같이 한다.


enum EOCConnectionState :NSInteger;



-

enum 의 값을 직접 정의하는 것도 가능하다.


enum EOCConnectionState{

     EOCConnectionStateDisconnected = 1,

     EOCConnectionStateConnected,

};



-

열거형이 정확하게 정의되었으면 비트 연산인 OR 연산자를 이용해 조합될 수 있다.

그리고 AND 비트 연산자를 이용해 특정 옵션값이 켜져 있는지 확인할 수 있다.



-

Foundation 프레임워크에는 열거형 타입을 정의하는 데 도움이 되는 여러 헬퍼가 있다.

또 이 헬퍼들을 이용하면 열거형 타입이 값을 저장할 때 사용하는 내부 타입을 정의할 수 있다.

이러한 헬퍼들은 하위 호환( backward compatibility)을 지원한다.

지정한 컴파일러가 새로운 표준을 지원하면 해당 문법이 그대로 사용되고, 지원되지 않으면 예전 문법으로 돌려준다.

헬퍼는 전처리기 #define 매크로 형태로 제공된다.


NS_ENUM 과 NS_OPTIONS 가 그것이다.


typedef NS_ENUM(NSUInteger, EOCConectionState){

     EOCConnectionStateDisconnected,

     EOCConnectionStateConnected,

};


typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection){

     EOCPermittedDirectionUp = 1 << 0,

     EOCPermittedDirectionDown = 1 << 1,

     EOCPermittedDirectionLeft = 1 << 2,

     EOCPermittedDirectionRight = 1 << 3,

};



-

매크로는 첫 번째로 컴파일러가 새로운 열거형을 지원하는지 여부를 검사한다.

그래서 기능이 없으면 열거형을 예전 방법으로 정의한다.


기능이 있으면 위 예제의 NS_ENUM 은 다음과 같이 converting 된다고 볼 수 있다.


typedef enum EOCConnectionState : NSUInteger EOCConnectionState;

enum EOCConnectionState : NSUInteger {

     EOCConnectionStateDisconnected,

     EOCConnectionStateConnected,

};



-

NS_OPTIONS 매크로는 C++ 로 컴파일을 하든 하지 않든 여러 가지 방법으로 정의된다.

C++ 가 아니면 그것은 NS_ENUM 과 똑같이 확장되지만, C++ 면 약간 다르게 확장된다.

C++ 컴파일러는 열거형 값 두 개를 OR 비트 연산자로 조합하면 다르게 동작하기 때문이다.

OR 연산으로 조합했을 때 C++ 는 조합 결과가 열거형이 나타내고 있는 타입인 NSUInteger 이기를 기대한다.

또 열거형 타입은 암묵적 캐스팅(implicit cast)을 허용하지 않는다.


EOCPermittedDirection permittedDirections = EOCPermittedDirectionLeft | EOCPermittedDirectionUp;


위 코드를 수행하면, 컴파일러가 C++ 모드였다면 다음과 같은 에러가 나타난다.

error: cannot initialize a variable of type ‘EOCPermittedDirection’ with an rvalue of type ‘int'


OR 연산의 결과에 대해 EOCPermittedDirection 으로 명시적인 캐스팅이 요구된다.

그래서 C++ 을 위한 NS_OPTIONS 열거형은 이런 에러가 발생하지 않게 조금 다른 방법으로 정의된다.

이런 이유 때문에 OR 연산으로 조합할 수 있는 열거형이 필요하면 항상 NS_OPTIONS 를 사용해야 한다.

필요 없다면 NS_ENUM 을 사용하라.



-

enum을 활용하는 마지막 방법은 switch 문장에서 이용하는 것이다.

switch 문에 default 구문을 넣고 싶을 것이다.

그러나 상태 머신(state machine) 을 정의하는 enum을 switch 문에 사용할 때 default 문은 사용하지 않는 게 최선이다.

그 이유는 나중에 열거형에 새로운 상태를 추가했을 때 컴파일러는 새롭게 추가된 상태가 switch 문에서 다루어지지 않는다는,

도움이 되는 경고를 보내주기 때문이다.

default 구문이 새로운 값을 처리하면 컴파일러는 경고를 보내지 않는다.



-

기억할 점

     enum 을 사용하여 상태 머신의 상태, 메서드 인자로 쓰이는 옵션, 에러 상태 코드에 사용되는 값에 읽기 좋은 이름을 주라.

    

     enum 타입이 여러 가지 옵션이 동시에 사용될 수 있는 메서드 옵션을 정의한다면, 열거형의 값을 2의 제곱 크기로 선언하여 옵션들이 OR 연산을 할 수 있게 하라.

    

     명시적 타입으로 enum 을 선언하기 위해 NS_ENUM 과 NS_OPTIONS 매크로를 사용하라.

     매크로를 사용하면 컴파일러가 선택한 타입이 아닌 직접 선택한 타입이 사용되는 것을 보장한다.


     enum 타입을 switch 문에서 사용할 때 default 문은 구현하지 말라.

     그렇게 하면 enum에 신규 값을 추가할 때 도움이 된다.

     컴파일러는 switch 문이 enum의 모든 값을 다루지 않았을 때 경고를 주기 때문이다.




반응형

댓글