1장 오브젝티브-C에 익숙해지기
아이템 1 오브젝티브-C의 기원과 친숙해지라
메시징 구조가 함수 호출과 다른 가장 큰 부분은 런타임이 실행할 코드를 정한다는 것이다.
모든 오브젝티브-C 객체는 항상 스택이 아닌 힙 공간에 할당된다.
(오브젝티브-C 객체는 스택에 할당하는 것이 허용되지 않는다.)
(NSString *someString; vs NSString someString)
(사견 : 이건 클래스 클러스터 이야기인듯함)
(변수의 포인터 변수는 스택에, 인스턴스는 힙에 할당)
오브젝티브-C에서 *을 사용하지 않고 스택을 사용하는 것은 오브젝티브-C를 참조하지 않는다.
아이템 2 헤더에 헤더를 포함하는 것을 최소화하라
'포워드 클래스 선언'(전방 선언)을 이용해서 헤더파일간의 참조를 막는다.
헤더간 서로 참조를 하면 먼저 파싱되는 헤더가 파싱될 때 상대 헤더를 포함한다.
하지만 포함해야할 때도 있는데 프로토콜을 참조할 때이다.
컴파일러는, 프로토콜이 포워드 선언되어 프로토콜이 존재한다는 간단한 정보가 아니라 프로토콜이 선언한 모든 메서드를 볼 수 있어야 한다.
// EOCRectangle.h
#import "EOCShape.h"
#import "EOCDrawable.h"
@interface EOCRectangle : EOCShape <EOCDrawable>
@property (nonatomic, assign) float width;
@property (nonatomic, assign) float height;
@end
추가 포함을 피할 순 없지만 이로 인해 추가되는 헤더에 있는 다른 헤더들까지 추가되기 때문에 시간과 예외상황은 피할 수 없다.
따라서 신중해야한다.
헤더 파일에서 포함을 사용할 때, 포워드 선언이 가능하다면 그것을 사용한다. 프로퍼티, 인스턴스 변수, 프로토콜을 따르기 위해 포함하는 것이라면 클래스 확장 카테고리로 바꿀 수 있다. 이것을 사용해라. 그렇게 하면 컴파일 시간을 최소한으로 줄이고 유지 보수가 어려워지는 상호 종속을 줄일 수 있다. 또 퍼블릭 API로 공개하고 싶은 코드 부분만 노출할 수 있게 한다.
아이템 3 메서드보다는 같은 일을 하는 리터럴 문법을 사용하라
오브젝티브-C 1.0 이전엔 객체를 생성할 때 매우 장황했지만 1.0 이후엔 문자열 리터럴(string literal)이라 하여 매우 간단한 방법으로 NSString 객체를 만들 수 있게 되었다.
(현재 리터럴 문법은 NSNumber, NSArray, NSDictionary 인스턴스까지 확장해서 지원함)
리터럴 숫자
NSNumber *someNumber = [NSNumber numberWithInt:1];
이렇게 사용하던 것을 리터럴 숫자로 표현할 수 있다.
NSNumber *intNumber = @1;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.13159;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
또한 리터럴 문법은 표현식에도 동작한다.
int x = 5;
float y = 6.32f;
NSNumber *expressionNumber = @(x * y);
(사견 : 이건 그냥 x * y되고 나온 숫자에 리터럴 숫자로 적용된것 같은데... 그냥 팁이다. 위에 @YES, @'a'와 동일한 선상이다.)
리터럴 배열
일반적인 NSArray 선언 방법
NSArray *animals = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil];
리터럴을 사용한 선언 방법
NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];
일반적인 NSArray에서 값을 갖고오는 방법
NSString *dog = [animals objectAtIndex:1];
리터럴을 이용해서 값을 갖고오는 방법
NSString *dog = animals[1];
단, 리터럴 배열을 사용할 때 값들 중 nil이 포함되어 있으면 안된다. 리터럴 배열은 대괄호 안에 있는 모든 객체를 배열에 추가해주는 간편 문법(syntactic sugar)이기 때문이다. 만약 nil이 있으면 예외를 던진다.
(일반적인 NSArray로 동일한 것을 선언하면 중간에 nil을 terminal로 인식해서 종료시킨다.)
그래서 리터럴이 좀 더 안전하다. 예상보다 적은 값이 들어간 버그를 찾는 것보단 크래시(crash)를 던진 에러를 찾는게 쉽기 때문이다.
리터럴 사전
사전(dictionary)는 키-값 쌍을 추가할 수 있는 맵(map) 자료 구조를 제공한다.
아래는 다음과 같이 생성한다.
NSDictionary *personData =
[NSDictionary dictionaryWithObjectsAndKeys:
@"Matt", @"firstName",
@"Galloway", @"lastName",
[NSNumber numberWithInt:24], @"age",
nil];
길이도 길지만 인자의 순서가 <값>, <키> 순이다.
아래는 리터럴을 사용한 문법이다.
NSdictionary *personData =
@{@"firstName" : @"Matt",
@"lastName" : @"Galloway",
@"age" : @28};
간결하기도 하고 인자의 순서도 <키> : <값> 이다.
(주의할 점은 숫자 28의 정수는 저장하지 못하기 때문에 NSNumber로 꼭 저장을 해야한다.)
배열과 동일하게 중간에 nil이 존재하면 예외를 던진다.
접근하는 방법도 리터럴 문법을 사용할 수 있다.
NSString *lastName = [personData objectForKey:@"lastName"];
동일한 일의 리터럴 문법은
NSString *lastName = personData[@"lastName"];
가변 배열과 사전
[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"Galloway" forKey:@"lastName"];
첨자를 이용해서 설정하는 것은 아래와 같다.
mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";
제한
생성된 객체의 클래스는 반드시 Foundation 프레임워크의 클래스여야 한다는 것이다.
하지만 문자열은 예외다.
(자신의 커스텀 하위 클래스의 인스턴스를 생성할때 리터럴 문법을 사용할 순 없다.)
NSArray, NSDictionary, NSNumber는 클래스 클러스터이기 때문에 하위 클래스를 만들어 리터럴 문법을 사용할 순 있지만 추천하지 않는다.
문자열, 배열, 사전의 경우 불변 인자(variants)만 리터럴 문법에서 생성할 수 있다. 가변 인자가 필요하면 가변 복사본을 다음의 방법으로 얻을 수 있다.
NSMutableArray *mutable = [@[@1, @2, @3, @4, @5] mutableCopy];
아이템 4 전처리기 #define보다는 타입이 있는 상수를 사용하라
시간의 길이를 표현한다고 했을 때 C를 배웠으면 아래와 같이 사용할 것이다.
#define ANIMATION_DURATION 0.3
그런데 이 값은 'duration'의 의미는 시간과 관계가 있지만 타입에 대한 정보가 존재하지 않는다.
그래서 아래와 같이 사용하는게 더 좋은 표현이다
static const NSTimeInterval kAnimationDuration = 0.3;
이 자체로도 좋은 문서 역활을 한다.
팁, 내부적으로 변환 단위를 표현하는 상수의 일반적인 표기법은 k 소문자를 상수 맨 앞에 붙이는 것이다
클래스 외부로 노출되는 상수는 일반적으로 클래스 이름을 상수 앞에 붙여준다.
헤더파일에 전처리기 정의를 선언하는 것은 나쁜 선택이다.
(정적 상수(static const)는 헤더 파일에 있으면 안된다.)
헤더 파일에 선언된 상수는 해당 파일을 포함하는 모든 파일에서 사용될 수 있다.
또한 네임스페이스(namespace)라는 개념이 없기 때문에 kAnimationDuration이라는 전역변수 처럼 이름은 사용될 범위를 나타내는 접두어를 반드시 붙여야한다.
외부로 노출할 필요가 없는 상수는 구현 파일에 정의해야 한다.
변수를 static, const 둘 다 이용해 선언하는 것이 중요하다. const는 값을 바꾸려고 하면 에러를 띄우는 것이다
static이 내용이 긴데, static식별자는 변수가 정의된 번역 단위(translation unit)의 지역(local) 변수라는 것을 의미한다. 오브젝티브-C의 경우 클래스당(구현파일 m) 한 개의 번역 단위가 있다는 것을 의미한다. 따라서 static변수는 지역적으로 선언될 것이다. static을 붙이지 않으면 컴파일러는 그것을 위해 외부 심벌(external symbol)을 생성하는데 다른 번역 단위에 같은 이름의 변수를 선언하면 링커는 에러를 출력할 것이다.
사실 static과 const로 선언하면 컴파일러는 절대 심벌을 만들지 않고 전처리기때 처럼 모든 변수를 값으로 치환한다.
타입정보는 상수를 외부에 제공할 때 도움이 된다.
// 헤더 파일 내에
extern NSString *const EOCStringConstant;
// 구현 파일 내에
NSString *const EOCStringConstant = @"VALUE";
헤더 파일에 extern을 쓰면 전역 심벌 테이블에 등록을 해서 컴파일러가 그 상수의 정의를 볼 수 없더라도 사용할 수 있게 해준다.
팁, 상수 이름들은 연관된 클래스 이름을 접두어로 사용한다. 예를들면 아래와 같다.
UIApplicationDidEnterBackgroundNotification, UIApplicationWillEnterForegroundNotification
결론은 전처리기를 피하고 구현 파일에 static, const 전역으로 선언하고, 컴파일러가 볼 수 있는 상수를 사용한다.
아이템 5 열거형을 사용해서 상태, 옵션, 상태 코드를 정의하라
상태를 표현할 때 주로 아래와 같이 사용한다.
enum EOCConnectionState {
EOCConnectionStateDisconnected,
EOCConnectionStateConnectiong,
EOCConnectionStateConnected,
};
enum EOCConectionState state = EOCConnectionStateDisconnection;
위처럼 사용할 때 enum을 붙이지 않으려면 enum을 선언할 때 typedef를 붙여준다.
enum EOCConnectionState {
EOCConnectionStateDisconnected,
EOCConnectionStateConnectiong,
EOCConnectionStateConnected,
};
typedef enum EOCConnectionState EOCConnectionState;
EOCConectionState state = EOCConnectionStateDisconnection;
C++ 11 표준 출현은 열거형 타입의 값들을 저장할 때 쓰이는 기저 타입(underlying type)을 기술하는 능력이다. 이 것을 사용하면 열거형 타입을 포워드 선언할 수 있다.
다음 문법으로 타입을 지정할 수 있다.
enum EOCConnectionStateConnectionState : NSInteger { /* ... */ };
포워드 선언
enum EOCConnectionStateConnectionState : NSInteger;
enum은 비트 연산자로도 사용할 수 있다.
그래서 옵션이 켜져 있는지 꺼져있는지 OR 연산자로 사용 가능하다.
2장 객체, 메시징, 런타임
3장 인터페이스와 API설계
4장 프로토콜과 카테고리
5장 메모리 관리
6장 블록과 GCD
7장 시스템 프레임워크
'소프트웨어 > Objective-C' 카테고리의 다른 글
프로 오브젝티브-C 디자인 패턴 (0) | 2018.01.16 |
---|---|
Objective-C 기초 (outdate된 자료 일부 포함) (0) | 2018.01.02 |