Object C 버전 정보
ISA에 대해서
Objective-C 런타임 클래스 정보 objc.h에 관련 클래스가 나와있다.
런타임 라이브러리의 내부에는 다음과 같은 Objective-C의 클래스 (objc_class)와 클래스로부터 생선된 객체 (objc_object)를 표현하기 위한 C의 구조체 코드가 있다.
typedef struct objc_class *Class;
typedef struct objc_object {
Class isa;
} *id;
모든 objc_object 들은 isa라고 정의된 클래스 변수를 갖고 있고 Objective-C 런타임은 이 isa 포인터를 이용하여 해당 객체가 어떤 클래스인지, 그리고 이 객체가 명령 메시지를 받았을 때 셀렉터에 응답을 하는지를 확인한다.
interface의 구조
@interface 클래스명 : 슈퍼 클래스명
{
인스턴스 변수의 선언;
...
}
메소드의 선언;
...
@end
super의 특징
주의할 것은 super는 self와 달리 특정 객체를 표현하는 것이 아니라는 것입니다. 그래서 변수에 대입하거나 메소드의 리턴값으로 사용하지 못합니다. 슈퍼 클래스의 메소드를 호출하는 용도 외에는 사용하지 못하게 됩니다.
initializer 정의 팁
- (id) init
{
self = [super init]; /* 슈퍼 클래스의 이니셜라이저를 먼저 호출한다. */
if (self != nil) { /* 슈퍼 클래스엣 인스턴스가 반환된 경우*/
... /*이 부분에 서브 클래스의 고유한 초기화 코드를 쓴다*/
}
return self;
}
NSObject에서 init구현
// Replaced by CF (throws an NSException)
+ (id)init {
return (id)self;
}
self에 값 대입하기
이 부분은 초기화를 할 때 주로 만나게 된다.
self에 객체를 대입하는 것은 self는 메시지의 리시버를 의미하므로 객체를 self에 대입하면 원래 그 객체가 리시버였던 것 처럼 동작을 계속합니다.
self object란 어떻게 구성되어 있는 것인가?
이 질문을 이해하기 위해선 이 링크를 읽어보자 (link)
So why assign the value returned from [super init] to self?
Looking at a typical initializer method:
- (id)initWithString:(NSString *)aString
{
self = [super init];
if (self)
{
instanceString = [aString retain];
}
return self;
}
Why do we assign [super init]
to self here?
The textbook reason is because [super init]
is permitted to do one of three things:
- Return its own receiver (the
self
pointer doesn't change) with inherited instance valuesinitialized. - Return a different object with inherited instance values initialized.
- Return
nil
, indicating failure.
이걸 보면 self = [super init]; 이 이해가 된다.(링크)
결과는 '메모리 주소가 같아서 상관 없다.'이다.
함수로 구현한 메시지
프로그램의 동작을 조금이라도 빠르게 하고 싶거나 C언어의 루틴에 함수의 포인터가 전달해야 할 경우 등 메소드에 대응하는 함수를 직접 다뤄야 하는 경우 아래처럼 사용하면 된다.
단, 메소드를 함수로 호출할 경우 객체지향의 동적 결합이라는 장점은 잃게 된다.
(effective objective-c 2.0 : 동적인 함수를 한 번 호출하게 되면 캐쉬되는데 그 이후엔 정적인 함수와 속도차이가 많이 나지 않는다.)
객체의 메소드에 대응하는 함수 포인터를 얻기 위해선 아래 함수 2가지 중 하나를 사용한다.
- (IMP) methodForSelector:(SEL)aSelector
+ (IMP) instanceMethodForSelector:(SEL)aSelector
IMP의 정의 : typedef id (*IMP) (id, SEL, ...);
ex. 함수로의 포인터를 얻고, 함수를 호출하여 메소드 호출하는 등 처리
IMP funcp;
funcp = [foo methodForSelector:@selector(setBox:title:)];
xyz = (*funcp) (foo, @selector(setBox:title:), param1, param2);
함수 선언 종류
메소드형이 '+'인 경우, 자바의 static 메소드와 같다. 클래스를 생성하지 않고도 쓸 수 있는 메소드이며 해당 클래스에서 같은 값을 사용할 경우 사용한다.
메소드형이 '-'인 경우, 인스턴스 (public) 메소드.
(reference : link)
만약 private function을 사용하고자 한다면 category를 활용해서 사용한다.
특히 class continuation이라는 카테고리 기능을 사용하면 된다.(링크)
인스턴스 변수의 가시성
인스턴스 변수가 외부로 노출되는 범위를 가시성(visibility)라고 합니다.
가시성에는 외부에서 직접 접근하지 않는 것이 권장되지만 무조건 접근해야한다면 허용하는 방법이 있습니다.
또한 거꾸로 서브 클래스에서 슈퍼 클래스의 인스턴스 변수를 참조하지 못하도록 제약을 거는 방법도 있습니다.
@private
: 선언한 클래스 안에서만 접근 가능. 서브 클래스에서 접근 불가, -> 연산자를 통해 접근
@protected
: 선언한 클래스나 서브 클래스 안에서 접근 가능, ->연산자로 접근 가능
@public
: 어디서든 구조체의 멤버처럼 접근할 수 있습니다.
가시성은 인스턴스 변수를 선언할 때 사용(default는 protected)
@interface TableOfColors : HashTable
{
id delete; // protected
@public
BOOL empty; // public
@private
id cache; // private
@protected
int entries; // protected
}
@end
NSObject 클래스
1) 루트 클래스의 역활
Objective-C는 객체가 가지는 동적인 특징을 구현하기 위해 실행 시에 런타임 시스템의 도움을 받는데 이런 시스템은 운영체제와 같은 것으로 객체의 생성, 해제를 수반하는 메모리 영역의 송신된 메시지에 대응하는 메소드 탐색등을 해줍니다.
이런한 기본적인 런타임 시스템의 기능들은 루트 클래스인 NSObject에서 메소드의 형태로 제공됩니다. (NSObject은 모든 클래스를 상속받아 다양한 기능들을 내포하고 있음) 즉, 루트 클래스는 런타임 시스템에 대한 인터페이스 역활을 하는 것.
2) 클래스와 인스턴스
NSObject는 인스턴스 변수를 하나만 가집니다. 이것은 isa라는 Class 타입의 변수인테 모든 인스턴스 객체는 이 isa에 의해 자신이 속한 클래스 객체를 참고하고 있습니다. 이 변수는 인스턴스와 클래스이 관계를 결정하는 중요한 변수이므로 서브 클래스에서 변경을 하면 안됩니다. 하지만! 인스턴스가 속한 클래스를 확인하기 위해서는 isa를 참조하면 안되고, 인스턴스 메소드인 class를 사용해야 합니다.(특히 키 값 )
NSObject.mm source file : link
(stack overflow link)
메세지에 대해서
메세지는 중앙 전송 함수를 통해 객체에게 메세지를 호출하는 것이다.
메시지 셀렉터라는 것은 함수가 함수명으로 식별되는 것처럼 메시지는 키워드를 메시지명으로 사용하여 다른 메시지와 구분합니다. 예를 들어 인수가 있는 copy라는 메시지와 인수가 없는 copy라는 메시지의 구분은 copy:와 copy로 됩니다.
(copy:는 인수가 있는 것, copy는 인수가 없는 것)
프로토콜에 대해서
Objective-C의 프로토콜은 메소드 선언에 불과하다. 그래서 이것을 메소드도, 인스턴스 변수도 아무것도 구현하지 않은 선언만 있는 추상 클래스라고 생각하면 된다.
선언 방법은 아래와 같다.
@protocol [protocol name]
method declare;
...
@end
한 예로 NSLocking 프로토콜은 아래와 같이 선언되어 있다.
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
프로토콜의 적용은 아래와 같이 슈퍼 클래스 이름 앞에 < >로 둘러싼 프로토콜명이 붙는다
@interface 클래스명 : 슈퍼 클래스명 <프로토콜명>
{
인스턴스 변수 선언;
...
}
메소드 선언;
...
@end
아래는 실제 NSLocking 프로토콜을 적용한 클래스 NSLock의 인터페이스는 아래와 같습니다.
@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@end
하나의 클래스에 여러개의 프로토콜을 적용하는 경우는 아래와 같이 표현한다.
@interface A : NSObject <S, T>
...
@end
단, 셀렉터가 같아도 인수나 리턴값의 타입이 다른 경우, 즉 시그네처가 다른 메소드가 프로토콜 간에 중복된 경우는 문제가 발생할 수 있다.
하나의 클래스 내에서는 같은 셀렉터를 가지는 다른 메소드를 선언할 수 없기 때문입니다.
프로토콜의 상속도 가능합니다.
방법은 아래와 같습니다.
@protocol 프로토콜명1 <프로토콜명2>
메소드 선언;
@end
다른 경우와 똑같이 프로토콜을 여러개 상속 받을때는 ',' 으로 구분하여 사용합니다.
추가적으로 객체가 어떤 프로토콜을 준수한다는 것을 타입으로 선언할 수 있는데 이 선언으로 얻는 것은 정적인 타입 검사입니다. 물론 동적으로 타입이 검사되진 않습니다. 방법은 아래와 같습니다.
id <S> obj;
- (void)addElement:(id <msg>) elem;
id 타입이 아닌 구체적인 클래스명과 카테고리를 조합해서 타입으로 사용하는 것도 가능합니다.
- (void)setAlternativeView:(NSView <Clickable> *)aView;
위 예제는 단순히 NSView 타입의 인스턴스 뿐만 아니라 카테고리 혹은 상속을 사용해서 프로토콜 Clickable을 따르는 객체여야한다는 것을 명시함
protocol도 전방선언(forward declaration)도 가능하다.
ex) @protocol Test;
protocol의 적합성 검사에 2개의 함수가 사용된다.
+ (BOOL)conformsToProtocol:(Protocol *)aProtocol
그 클래스가 aProtocol로 지정된 프로토콜을 다르고 있다면 YES를 리턴한다.
- (BOOL)conformsToProtocol:(Protocol *)aProtocol
리시버의 클래스가 aProtocol에서 지정한 프로토콜을 따르고 있다면 YES를 리턴한다.
예제는 아래와 같습니다.
if ([obj conformsToProtocol:@protocol(NSLocking)] )
@optional, @required
선택과 필수 구현의 컴파일러 지시자입니다.
예제는 아래와 같습니다.
@protocol Alram
- (void)setCurrentTime:(NSDate *)date;
- (BOOL)alarm;
- (void)setAlarm:(BOOL)flag;
@optional
- (BOOL)snooze;
- (void)setSnooze:(BOOL)flag;
@required
- (void)setTimerAtHour:(int)h minute:(int)m;
@end
비공식 프로토콜
- 비공식 프로토콜은 NSObject 클래스의 카테고리로 선언한다.
- 비공식 프로토콜에서 선언된 메소드의 구현은 필수는 아니다
- 컴파일 시에 비공식 프로토콜을 따르는지 확인할 방법은 없다.
- 런타임시 비공식 프로토콜을 따르는지 알 수 있는 방법이 없기 대문에 각 메소드를 구현했는지는 따로 확인할 수 밖에 없다. (각 메소드에 대해 respondsToSelector:를 사용해봐야 한다)
보통 Cocoa 환경에서 델리게이트(delegate)를 구현할 때 메소드 집합을 선언하는 경우에 많이 사용된다.
프로토콜과 카테고리의 차이
얼핏 비슷해보지만 차이가 크다.
프로토콜 - 구현되어 있지 않은 명세만 담고 있다. (like interface)
카테고리 - 구현되어 있다. (like class)
프로토콜 - 확장이 가능하다. (상속 받아서 변수, 함수를 추가한 새로운 프로토콜을 만들 수 있다.)
카테고리 - 함수만 추가가 가능하다. 그래서 private형으로 사용하기 위해서 카테고리를 활용해서 함수를 추가한다.
동적 결합이란
메시지를 보냈을 때 해당 메시지가 어떤 메소드에서 실행되는지 런타임에 결정하는 방식을 동적 결합(dynamic binding)이라고 한다.
동적결합의 예는 아래와 같다. 컴파일 시 에러가 없고 0, 1을 눌렀을 때는 정상적인 로그가 출력되지만 2을 누르면 에러가 뜬다.
(dyma.m)
#import <Foundation/NSObject.h>
#import <stdio.h>
@interface A : NSObject
- (void)whoAreYou;
@end
@implementation A
- (void)whoAreYou {printf("I'm A\n");}
@end
@interface B : NSObject
- (void)whoAreYou;
@end
@implementation B : NSObject
- (void)whoAreYou {printf("I'm B\n");}
@end
int main(void) {
id obj;
int n;
scanf("%d", &n);
switch (n) {
case 0: obj = [[A alloc] init]; break;
case 1: obj = [[B alloc] init]; break;
case 2: obj = [[NSObject alloc] init]; break;
}
[obj whoAreYou];
return 0;
}
클래스 타입으로 사용하기
사용 방법
Volume *v;
MuteVolume *mute;
*는 C의 포인터를 지칭하는 것과 같다. 그래서 아래와 같이 사용이 가능하다
Volume *v1, *v2;
v1 = [[Volume alloc] initWidthMin:0, max:10, step:1];
v2 = v1;
[v1 up];
printf(":%d\n":, [v2 value]); // ':'이 왜 들어가는지 확인하기
//printf("%d\n", [v2 value]);
nil(null) 사용하기
Objective-C는 nil에 대해서 메시지를 보낼 수 있기 때문에 그에 따른 사이드 이펙트가 있을 수 있다.
아래의 3가지 경우에 대해서 확인해보자
1) nil이면 조건에 의해서 increment가 실행되지 않는다.
if (val = [list entryForKey:"NeXT"]) != nil)
[val increment]
2) 이 경우는 val이 nil을 때 아무것도 문제가 없다.
val = [list entryForKey: "NeXT"];
[val increment];
3) 이 경우 nil이라서 아무 실행되지 않지만 n의 값이 증가되어 문제가 있을 수 있다.
[val increment: ++n]
팁, char 타입의 포인터 p에 대해 그것이 null도 아니고 공백 문자열도 아닌 경우를 확인할 때 다음과 같은 코드를 사용한다.
if (p && *p)
만약 p가 null이만 처음 p가 거짓이 되므로 다음 *p는 평가하지 않습니다. 그래서 p가 null인 경우 *P를 평가하면 런타임 에러를 발생시키지만 &&을 사용하면 간결한 코드 작성이 가능합니다.
(C언어의 관계 연산자인 ||, &&는 논리식 전체의 값이 결정되면 더 이상 평가하지 않는다는 특징을 활용한 것)
시그네처란
메시지 셀렉터에는 인수가 리턴값의 타입 정보가 없는데 이 셀렉터 정보에 타입의 정보를 합쳐서 시그네처(signature)라고 부릅니다. 인터페이스에서 메소드 선언에 사용되는 아래와 같은 형식을 시그네처라고 합니다.
- (id) callAtRow: (int)row column:(int) col;
instancetype 키워드
instancetype은 형 안정성(type safety)가 보장하기 위해서 만들어진 키워드
https://www.euler.kr/trl/2014/07/11/instancetype.html
타겟-액션 패러다임
(+ outlet : Interface Builder로 이용되는 것, 이런것도 있다.)
카테고리
한 클래스의 다수의 메소드 중 일부 메소드를 구현한 모듈을 카테고리(category)라고 합니다. 클래스를 구성할 때 카테고리를 사용할 수도 있고, 사용하지 않을 수도 있다. 메소드를 호출하는 방법은 둘 다 동일하다.
카테고리는 클래스와 마찬가지로 인터페이스에서 선언하고 구현부에서 정의를 합니다. 단, 카테고리는 클래스와 달리 인스턴스 변수를 선언하지 못하고 메소드만 선언할 수 있습니다. 메소드에는 인스턴스 메소드와 클래스 메소드 모두 쓸 수 있습니다.
선언부
@interface 클래스명 (카테고리명)
메소드의 선언;
...
@end
(클래스 명은 존재하는 것으로 써야한다.)
구현부
@implementation 클래스명 (카테고리명)
메소드의 정의;
...
@end
Q?? 카테고리로 만든 클래스와 메인 클래스와 형 변환이 되는 것인가? 실제 해석은 어떻게 이루어지지?
- 카테고리의 인터페이스는 메인 인터페이스를 참조해야 합니다.
- 카테고리의 구현부는 대응하는 인터페이스를 참조해야 합니다.
- 어떤 카테고리의 메소드를 사용할 경우, 호출하는 쪽에서는 그 메소드를 포함하는 인터페이스를 참조해야 합니다.
(실제 사용 예시 1)사용 방법 중 하나,
메소드의 개수가 만은 규모의 큰 클래스라면 구현부를 파일 하나로 만드는 것이 어려울 수 있습니다. 이런 경우 메소드를 여러 카테고리로 분할한 후, 구현부를 각각 다른 파일로 만들 수 있습니다. 즉 카테고리를 클래스의 서브 모듈처럼 사용할 수 있습니다.
(실제 사용 예시 2)카테고리를 사용하는 상황 중 하나, 전방선언을 이용한 지역함수 사용하기
인터페이스에 선언되어 있지 않은 지역 메소드의 경우, 프로토타입을 선언하지 않는 함수와 마찬가지로 소스 코드 상에서 그 정의가 나오기 전에 사용하지 못한다고 했습니다. 그래서 카테고리를 이용해서 이 문제를 해결할 수 있습니다.
/*
error
*/
- (int)methodA {
if (...)
[self methodB: 0];
...
}
- (void)methodB:(int)arg {
double v = [self methodA];
...
}
/*
forward declare
*/
@interface 클래스명 (Local)
- (int)methodA;
- (void)methodB:(int)arg;
@end
@implementation 클래스명(Local)
- (int)methodA {
if (...)
[self methodB: 0];
...
}
- (void)methodB:(int)arg {
double v = [self methodA];
...
}
@end
이렇게 카테고리를 사용하면 소스 코드의 앞 부분에 프로토타입 선언을 쓰면 되는데, 이것과 동일한 효과입니다.
클래스의 확장
클래스 외부에 공개하지 않는 private한 메소드를 클래스 안에서 공유하는 방법을 설명하였는데 이 방법은 private메소드의 정의를 누락시켜도 사전에 확인되지 않는 위험이 있습니다. 그래서 이런 상황에서 유용하게 사용할 수 있는 특별한 카테고리가 도입되었습니다. 이 기능을 클래스 확장(class extensions)라고 부릅니다.
이 선언은 카테고리와 비슷하지만 카테고리명을 지정하지 않는 것입니다.
@interface Card ()
- (BOOL)hasSameSuit:(Card *)obj;
@end
즉, 비공개 메소드를 사용할 때 안전하게 사용하려면 클래스의 확장을 사용해서 구현해야한다.
클래스 확장에 선언한 메소드는 클래스 본체의 구현부에서 정의되어야 합니다. 이것을 아래에 표현했는데 클래스 확장의 선언을 임포트하고 있는 것과 관계없이 대응하는 메소드의 정의가 클래스 본체의 구현부에 없으면 컴파일 시 에러가 발생합니다.
기존 클래스에 카테고리 추가하기
기존엔 카테고리란 존재하는 클래스의 함수들을 몇개만 골라서 담는 형태였다면, 이것은 존재하지 않는 함수를 새로 정의하는 것이다.
#import <Foundation/NSString.h>
@interface NSSTring (PathComp)
- (NSString *)stringByAppendingPathComponents:(NSString *) aString, ...
@end
이 방법은 함수만 추가할 수 있다는 단점이 있지만 언젠간 유용할 것 같다.
가변인수를 사용하는 메소드의 정의
- 인수 리스트에 가변 인수만 있으면 안된다.
- 가변 인수는 인수 리스트의 마지막이어야 한다.
- 가변 인수의 타입은 프로그램 안에서 관리해야 한다.
// Definition
- (void)argListTest1:(NSString *)str1, ... NS_REQUIRES_NIL_TERMINATION;
// Implementation
- (void)argListTest1:(NSString *)str1, ... {
va_list args;
va_start(args, str1);
for (NSString *arg = str1; arg != nil; arg = va_arg(args, NSString *)) {
NSLog(@"Argument : %@", arg);
}
va_end(args);
}
위의 코드를 해석해보면,
1. va_start에서 지정한 포인터가 가변인자의 시작점이 되기 때문에 str1의 포인터를 알려준다. 즉, 인자가 나열된 메모리 상의 시작점을 알려주는 것이다.
2. for 루프에서 사용되는 것처럼 va_arg를 통해 차례대로 각 인자의 포인터를 가져올 수 있다. 인자가 나열된 메모리에서 순서대로 포인터를 액세사 한다는 의미
3. 사용이 끝나면 va_end를 호출해서 알려줘야 한다.
(ref link)
참고로 다른 방법도 있다 : 링크
예제 소스
RGB.h
#import <Foundation/NSObject.h>
@interface RGB : NSObject
{
unsigned char red, green, blue;
}
- (id)initWithRed:(int)r green:(int)g blue:(int)b;
- (id)blendColor:(RGB *)color;
- (void)print;
@end
----
RGB.m
#import "RGB.h"
#import <stdio.h>
static unsigned char roundUChar(int v)
{
if (v < 0) return 0;
if (v > 255) return 255;
return (unsigned char)v;
}
@implementation RGB
- (id)initWithRed:(int)r green:(int)g blue:(int)b
{
if (([super init]) != nil {
red = roundUChar(r);
green = roundUChar(g);
blue = roundUChar(b);
}
return self;
}
@end
가비지 컬렉션(지금은 사용하지 않음)
런타임에 불필요한 메모리 영역을 찾아내고, 그것을 자동으로 해제하는 메커니즘
가비지 컬렉션의 회수 대상은 id나 클래스명을 타입으로 사용하는 객체들입니다.
(참고, retain 등 링크)
프로퍼티 리스트는 내부 구현과는 비교적 관계가 없는 추상도가 높은 정보를 보존해서 공유하는 목적으로 사용됩니다.
ASCII 형식, XML 형식, 바이너리 형식의 세 종류 형식을 사용할 수 있다.
ASCII형식 - 문자열(NSString), 데이터(NSData), 배열(NSArray), 사전(NSDictionary)의 네 가지 클래스를 조합한 구조를 텍스트로 표현
XML형식 - 문자열, 데이터, 배열, 사전, NSNumber(수, 부울 값 표현), NSDate(날짜)를 조합하여 표현 가능
바이너리 형식 - 이러한 구조를 텍스트가 아닌 바이너리 파일로 보존
파일로 보존할 때는 확장자는 '.plist'가 관례 (이 파일은 Property List Editor라는 툴에서 사용 가능)
- (id)propertyList
NSString의 인스턴스 메소드로 ASCII 형식의 프로퍼티 리스트의 문자열에서 대응하는 객체의 구조를 복원해서 리턴합니다. 리턴되는 구조는 불변 객체로 구성됩니다.
프로퍼티 리스트를 살펴보니 결국 객체를 특정 형태로 바꿔서 보관하는 것으로 변수type, 변수 명, 변수 값을 저장하는 것이다. 그리고 저장하고, 불러오고를 반복하는 것
Objective-C의 동적인 객체 관리
레퍼런스 카운트를 조절하여 retain은 count를 하나 증가시키고 release는 count를 하나 감소시킨다.
dealloc이 인스턴스 메소드로 인스턴스를 해제하는 메소드지만 일반적인 경우라면 개발자가 콜을 하진 않는다.
- (id)retain;
- (oneway void)release;
- (void)dealloc;
oneway는 async하게 동작하기 위해서 사용하는 키워드
오너쉽이란 사람이 프로그램을 분석할 때, 객체 간의 관계에 의미를 붙인 것으로 객체의 생명을 관리하는 역활이라고 보면 된다.
(어디서 어디로 참조를 하고 있는지)
Reference count를 볼 수 있는 함수 : [obj retainCount]
인스턴스가 해제될 때 ref count를 정리하는 과정은 dealloc을 오버라이드해서 정리한다. 이게 destructor의 역활을 한다.
- (void)dealloc
{
//... reference 정리
[super dealloc];
}
참고, Mac OS X의 objective-C 컴파일러로는 정적인 객체를 만들지 못한다. 그래서 다 alloc을 사용해야한다.
자동 해제 매커니즘은 - (id)autorelease; 로 처리 된다.
자동 해제 풀의 인스턴스를 끊임없이 만들 수 있는데 이것은 여러 번 반복되는 루프 안에서 임시 인스턴스를 여러 개 만들어야 하는 경우, 장시간 실행되는 로직이나 일시적으로 많은 인스턴스를 만들어 사용하는 로직에서 지역적으로 메모리를 관리할 수 있다.
메모리를 해제해야하는 경우 3가지
alloc을 사용한 인스턴스 생성, copy를 사용한 인스턴스의 복사, retain을 사용한 보존
특이한 문법 발견, 본인을 autorelease에 넣고 다른 객체로 전달하는 것
- (id) temporaryValue
{
id tmp = [[ComplexData alloc] initWithData:myValue];
return [tmp autorelease];
}
case study
아래의 코드는 위험요소가 있는 코드이다.
왜냐하면 입력된 obj가 myValue에 있는 값과 동일한 것이면 myValue가 release하는 순간 obj의 값도 사라지기 때문이다.
이것은 의도와도 상충되고 에러가 나올 확률이 있다.
- (void)setMyValue:(id)obj
{
[myValue release];
myValue = [obj retain];
}
아래는 해결 방법 3가지
- (void)setMyValue:(id)obj { [myValue autorelease]; myValue = [obj retain]; } |
- (void)setMyValue:(id)obj { [obj retain]; [myValue release]; myValue = obj; } |
- (void)setMyValue:(id)obj { if (myValue != obj) { [myValue release]; myValue = [obj retain]; } } |
아래와 같은 방법으로 retain cycle을 구성하고 있으면 메모리의 문제를 일으킬 수 있다.
아래와 같은 경우가 retain cycle에 빠져서 무한루프에 빠지는 경우이다.
id A = [[MyClass alloc] init];
id B = [[MyClass alloc] init];
[A setMyValue B];
[B setMyValue A];
[A release];
[B release];
- (void) dealloc {
[myValue release];
[super dealloc];
}
임시 인스턴스라고 하는 개념은 autorelease가 호출된 인스턴스로 생성 직후에 자동 해제 풀에 등록하는 방법이다
예를 들면 NSString클래스에 있는 두 개의 메소드 차이를 보면 알 수 있다.
- (id) initWithUTF8String:(const char *) bytes;
alloc으로 만들어진 인스턴스에 대한 이니셜라이저로 만든 객체가 인스턴스의 오너가 됩니다.
- (id) StringWithUTF8String:(const char *) bytes;
임시 인스턴스를 만드는 클래스 메소드로, 결과로 리턴되는 인스턴스는 자동 해제 풀에 등록되어 있습니다.
Objective-C에서 임시 인스턴스를 간단하게 만드는 클래스 메소드를 컨비니언스 컨스트럭터(convenience constructor)라고 부릅니다. 이게 constructor의 역활로 다른 생성자를 콜하는 일을 수행하는데, 임시 인스턴스를 만드는 클래스 메소드를 의미하기도 합니다.
예시를 보니깐 init을 할 때 새로운 함수를 하나 더 만들고, 그 함수에서 init과 autorelease를 하는 것.
프로퍼티란?
'외부에서 접근할 수 있는 객체의 속성'이라는 의미 (쉽게 말해서 getter, setter이다)
디클레어드 프로퍼티는 접근자 메소드에서 구현한 개념을 간결하게 기술할 수 있도록 만든 것
인트로스펙션(introsepction, 컬럼 참고)의 기능을 빼면 이제까지 사용한 방법과 동일)
프로퍼티의 개념은 3가지가 있다
키-밸류 코딩에서의 프로퍼티 > 접근자 메소드에서의 프로퍼티 > 선언 프로퍼티
(이 순서로 차이가 있다)
1) 접근자 메소드의 생성
2) 접근자의 호출을 간단히 기술
3) 프로퍼티에 관한 인트로스펙션
디클레어드 프로퍼티
property는 뒷 부분에 정리되어 있는데 메소드의 선언과 혼재되어 있어도 상관이 없습니다.
표현은 아래와 같이 읽고/쓸 수 있는 것, 읽기마 가능한 것 순으로 나타나있습니다.
@property int hitPoint;
@property(readonly) NSString *name;
@property int hitPoint, magicPoint;
,을 사용해서 프로퍼티를 나열할 수 도 있습니다.
가비지 컬렉션을 이용하는 경우엔 __weak, __strong을 지정할 수 있습니다.
synthesize는 구현부에 적는 것인데 왜 적는지 모르겠다.
synthesize에 존재하지 않는 변수를 넣으면 32비트는 에러를 뱉고 64비트는 새로운 인스턴스 변수가 자동적으로 만들어 집니다.
프로퍼티의 속성에 대해서
setter의 이름을 바꾸는 것은 아래와 같이 한다.
@property(setter=setValue:) int hitPoint;
그리고 readonly와 같이 명시적으로 읽기, 쓰기가 가능하단 것을 표현하려면 readwrite로 지정을 하면 된다.
프로퍼티가 객체인 경우 어떻게 값을 설정할 지 3가지의 옵션으로 지정할 수 있다.
assign, retain, copy
이 3가지가 객체가 아닌 경우, 객체이면서 레퍼런스 카운트를 사용하는 경우, 객체이면서 가비지 컬렉션을 사용하는 경우에 따라 나뉜다.
이것들을 정리하면 아래와 같이 3문장이 된다.
1) 프로퍼티가 객체가 아닌 경우, 옵션은 지정하지 않아도 된다. 지정한다면 assign만 지정할 수 있다.
2) 프로퍼티가 객체이고 레퍼런스 카운트 방식을 사용하는 경우 assign, retain, copy 중 하나를 옵션으로 지정해야 한다.
3) 프로퍼티가 객체이고 가비지 컬렉션을 사용하는 경우 assign, copy 중 하나를 옵션으로 지정해야 한다.
멀티 스레드 환경에서는 atomic, nonatomic이라는 것을 사용한다.
nonatomic옵션이 디폴트이다.
atomic으로 개발을 할 때 여러 개의 @synchronized가 관여하면서 데드락이 발생할 가능성도 있습니다.
@dynamic이라는 지시어는 @synthesize대신 사용할 수 있으며 getter와 setter메서드가 슈퍼클래스 어딘가에 구현되어 있다고 알려주어 실제 implements에서 구현되어 있지 않아도 컴파일러가 경고하지 않게 해줍니다.
즉, 런타임 중 동적메소드를 결합시켜주는 과정이 들어갑니다.
디클레어드 프로퍼티와 상속
슈퍼 클래스에 선언된 디클레어드 프로퍼티는 서브 클래스에서 접근자 메소드를 오버라이드 할 수 있습니다. 그리고 readonly였던 것을 readwrite로 변경할 수 있고 프로퍼티의 선언은 카테고리 혹은 프로토콜에 포함할 수 있습니다. @synthesize를 카테고리 구현부에 기술하는 것도 가능합니다.
단, 슈퍼 클래스의 @property로 지정한 이름의 속성(assign인지 retain인지)과 게터나 세터의 이름을 지정한 것 등은 모두 동일해야한다.
도트 연산자로 프로퍼티로에 접근하기
프로퍼티 값이 void, id만 아니면 전부 접근이 가능하다.
대입에 대한 식
obj.name = val // = // [obj setName: val];
참조에 대한 식
val = obj.name; // == // val = [obj getName];
도트 연산자를 연속으로 사용할 수 있다.
n = obj.productList.length;
n = [ [obj productList] length];
obj.contents.enabled = YES;
[[obj contents] setEnabled: YES];
구조체에 대해 도트 연산자를 적용한 경우, &cell.size와 같이 주소 연산을 사용할 수 있다.
도트 연산자 사용시 주의 사항
인스턴스에 대한 도트 연산자 적용은 C언어에서 구조체 멤버에 접근하는 것과는 의미가 조금 다르다. '객체로의 포인터'에서 인스턴스 변수로 접근은 -> 연산자를 사용하는 것이 정석이지만 이것은 일반적인 구조체 멤버를 참조하는 방식이 아닌 프로퍼티에 접근하기 위한 메소드를 호출하는 것이다.
결국 도트 연산자의 역활은 프로퍼티에 접근하는 것을 의미한다.
어플리케이션 구조
1. 애플리케이션 래퍼
Resource 디렉토리에 있는 자원은 NSBundle 클래스가 자동으로 어느 언어에 대한 코드를 사용할지 정해주므로 별도 기술할 필요가 없다.
로컬라이즈라는 특정 언어에 대응하게 하는 것이 있는데, Cocoa 애플리케이션에선 여러 언어를 대응할 수 있도록 프로그램을 기술해둔 후 애플리케이션 래퍼에 특정 언어에 맞는 자원을 추가하여 쉽게 진행한다.
일단 어플리케이션이 실행되기 위해선 info.plist에 있는 정보들을 불러와야 하는데 그 정보를 불러오는게 NSBundle이 진행한다.
2. 애플리케이션과 실행 루프
NSApplication 클래스는 Application 프레임워크의 클래스로 GUI를 가지는 Cocoa 애플리케이션의 실행 관리를 담당
애플리케이션은 NSApplication을 하나만 갖는다.
애플리케이션이 실행되면 실행 루프(run loop)위에 이벤트 루프(event loop)를 만들어서 GUI에서 보내지는 마우스나 키보드와 같은 이벤트, 네트워크에서의 입력, 타이머에 의한 지연 실행 같은 실행 요구의 감시와 각각에 대응한 처리를 하는 부분
3. 모듈의 동적 로딩
로더블 번들이라 로딩되는 시점에서 연결이 되는 것다.
그래서 로더블 번들로 만들어질 클래스는 불완전하더라도 실제 로딩되는 시점에만 완벽하면 되고,
로더블을 불러오는 클래스들은 로더블 번들의 클래스에 대한 정보가 담겨있으면 안된다.
Objective-C에선 플러그인도 로더블 번들로 구현됩니다.
플러그인은 로더블 번들과 다르게 코드에 포함된 클래스명이 무엇인지 미리 알지 못합니다. 그래서 플러그인에서 코드를 로딩하기 위해서는 다음 메소드를 사용해야합니다.
- (Class)principalClass
: 로더블 번들의 주요 클래스의 클래스 객체를 리턴
info.plist에 NSPrincipalClass에 값으로 기술되어 있거나 코드가 링크될 때 최초로 나타나는 클래스
4. 유저 디폴트
: 설정값의 보존, 사용자가 선택했던 값들을 저장하는 것으로 user default, 디폴트 데이터 베이스라고 부른다
접근 인터페이스가 존재한다. NSUserDefaults
기록된 정보는 ~/Library/Preferences/ 디렉토리 안에 저장, 파일명은 애플리케이션 식별명에 확장자 'plist'가 붙는다
유저 디폴트는 몇 개의 그룹으로 나눌 수 있는데, 이름을 도메인 혹은 디폴트 도메인이라고 부릅니다.
5가지 : 애플리케이션 도메인, 글로벌 도메인, 인수 도메인, 언어 도메인, 등록 도메인
(1) 애플리케이션 도메인
애플리케이션의 고유한 설정을 기록하는 도메인
(2) 글로벌 도메인
사용자 계정별로 어플리케이션에 적용되는 설정값들을 관리하는 것
도메인명은 NSGlobalDomain이고 파일명은 .GlobalPreferences.plist
예를 들면 사용하는 언어, 파일명을 표시할 때 확장자를 붙일지 여부, 안티앨리어싱 등
(3) 인수 도메인
argument domain이라고 해서 실행될 때 인수를 같이 넘겨서 설정값을 적용시키는 것
도메인 명은 NSArgumentDomain이며 파일에 저장되진 않는다.
(4) 언어 도메인
사용하는 언어가 지정되는 경우, 그 언어의 이름을 도메인명으로 하는 임시 도메인이 만들어지고 파일이 저장되진 않는다.
(5) 등록 도메인
기본 설정값의 집합으로 도메인은 NSRegistrationDomain을 사용, 키에 대응하는 설정값을 검색할 수 있다.
우선순위는 다음의 순서이다
인수 도메인 > 애플리케이션 도메인(파일에 저장) > 글로벌 도메인(파일에 저장) > 언어 도메인 > 등록 도메인
NSUserDefaults
: 유저 디폴트에 접근하기 위한 클래스
(1) 인스턴스 객체 얻어오기
(2) 키에 대응하는 값 얻어오기
- (id)objectForKey:(NSString *)defaultName
- (NSString *)stringForKey:(NSString *)defaultName
- (NSArray *)stringArrayForKey:(NSString *)defaultName
- (NSInteger)integerForKey:(NSString *)defaultName
(3) 키에 대응하는 값을 설정하거나 삭제하기
- (void)setObject:(id)value forKey:(NSString *) defaultName
- (void)setInteger:(NSInteger)value forKey:(NSString *) defaultName
- (void)removeObjectForKey:(NSString *)defaultName
(4) 기본 설정값의 정의
- (void)registerDefaults:(NSDictionary *)dictionary
(5) 도메인 내용 얻어오기
- (NSDictionary *)dictionaryRepresentation
(6) 설정 내용을 파일에 반영하기
- (BOOL)synchronize
5. 애플리케이션의 로컬라이즈
메시지를 다국어가 지원되게 하는 방법이 있다. 그러면 소스 내에 한글을 넣을 필요가 없어진다.
이를 위해 Localizable.strings라는 파일을 만들고 각 언어별로 대응하는 디렉토리(언어명.lprog)에 저장하면 된다.
파일명을 지정할 수도 있지만 특별히 지정하지 않으면 Localizable.strings가 사용된다.
- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName
사용 예시
NSString *msg = [[NSBundle mainBundle] localizedStringForKey:@"File does not exist" value:@"" table:nil];
NSLocalizedString(key, comment)
NSLocalizedStringFromTable(key, tbl, comment)
tip) 명령어 genstrings가 제공된다.
한글의 경우 소스코드에 넣지 못하기 때문에 Korean.lproj라는 디렉토리에 항상 파일을 만들어놔야한다.
한글로 날짜를 출력하는 코드 예제
id date = [NSDate date];
id str = [NSString stringWithFormat:@"Date=%@", date];
printf("%s\n", [str UTF8String]);
str = [NSString localizedStringWithFormat:@"Date=%@", date];
printf("%s\n", [str UTF8String]);
메시지 송수신 패턴
1. 델리게이트의 개념
'어떤 객체가 처리하지 못하는 메시지를 받았을 경우, 다른 객체에서 처리를 대행하게 하는 것'
하지만 Cocoa 환경에서는 처리하지 못하는 것을 대행한다기 보단 부가적인 기능을 확장하는 개념이 강하다.
델리게이트를 사용하면 그 클래스의 독립성을 해치지 않으면서 프로그램의 기능을 추가하는 것이 가능하다
델리게이트는 하나의 객체에 반드시 하나를 써야 한다는 제약은 없다.
상속과 비교하면, 상속은
(1) 런타임에 델리게이트를 할당하거나 교체할 수 없다.
(2) 하나의 객체가 여러 객체의 델리게이트로 동작하게 하지 못한다.
(상속과 비슷하지만 장단점이 다 있다)
델리게이트를 별도로 만들일은 없고, 만드는것도 어렵다. 만약 만든다면 각 메시지를 처리할 수 있는지 respondsToSelect: 같은 메소드로 확인할 필요가 있다.
Foundation프레임워크엔 델리게이션이 많진 않지만, Application에는 많이 존재한다.
(실제 개발시엔 각 클래스에 어떤 메소드가 있는지, 동시에 델리게이트에서 어떤 기능을 구현하는지 살펴봐야한다.)
Cocoa에선 델리게이트는 delegate라는 이름의 id 타입 인스턴스 변수로 구현하는 것이 관례이다.
델리게이트를 리턴하는 객체에선 아래 함수들이 무조건 들어간다.
- (void)setDelegate:(id)anObject
: 인수의 객체를 델리게이트로 설정합니다. 보통 인수의 객체는 보존하지 않습니다.
- (id)delegate
: 리시버 객체의 델리게이트를 리턴합니다.
2. 노티피케이션
어떤 사건이 발생했을 때 일제히 다른 객체에 알리는 것을 노티피케이션이라고 한다.
프로그램에서는 노티피케이션 센터라는 객체가 제공된다.
노티피케이션을 받고 싶은 객체는 미리 어떤 노티피케이션을 받을지 노티피케이션 센터에 등록해둔다.
노티피케이션의 식별은 미리 준비된 노티피케이션명(notification name)으로 식별된다.
어떤 객체가 메시지 송신을 의뢰하는데 이것을 post라고 부른다. 그러면 해당 노티에 등록된 객체에 메시지를 일제히 보낸다.
메시지를 수신하기 위해 등록해 놓은 객체들을 '옵저버'라고 부른다.
옵저버는 본인을 등록시킬때 어떤 이름을 가진 노티피케이션을 어떤 셀렉터의 메시지로 수신하고 싶은지 지정한다.
또한 특정 옵저버가 포스트한 통지만 수신하도록 지정도 가능하다. 옵저버는 여러 노티피케이션 센터에 등록을 해놔도 되고 아무곳에도 안해놔도 된다.
포스팅은 어느 객체라도 할 수 있는데 꼭 노티피케이션 센터로 등록할 필요는 없다.
그리고 노티피케이션의 이름만 알고 있으면 아무것도 몰라도 노티피케이션을 포스팅할 수 있다.
센더와 리시버는 1대 1의 통신이 아니라 멀티 캐스트(1대 다) 방식이다. (브로드 캐스트와 동일한 말)
포스트할 때는 NSNotification이라는 클래스의 인스턴스에 담아서 노티피케이션 센터로 보낸다.
노티피케이션 객체의 정보는 아래와 같다.
- (NSString *)name
- (id)object
- (NSDictionary *)userInfo
+ (id)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)userInfo
+ (id)notificationWithName:(NSString *)aName object:(id)anObject
노티피케이션 객체는 NSCopying 프로토콜을 따르고 있어서 복사도 할 수 있습니다.
복사를 하게 되면 이름, 노티피케이션한 객체, 사용자 사전도 복사됩니다.
노티피케이션 센터(NSNotificationCenter)라는 클래스의 인스턴스로 구현되어 있음
각 구성요소 별 사용함수는 아래와 같다.
(1) 기본 노티피케이션 센터
+ (id)defaultCenter
(2) 노티피케이션의 포스트
- (void)postNotification:(NSNotification *)notification
- (void)postNotificationName:(NSString *)notificationName object:(id)anObject userInfo:(NSDictionary *)userInfo
(3) 옵저버 등록
- (void)XXXXXX:(NSNotification *)notification
- (void)addObserver:(id)anObserver selector:(SEL)aSelector name:(NSString *)notificationName object:(id)anObject
(4) 옵저버 삭제
- (void)removeObserver:(id)anObserver
- (void)removeObserver:(id)anObserver name:(NSString *)notificationName object:(id)anObject
ex)
[[NSNotificationCenter defaultCenter] removeObserver:obj];
obj가 옵저버로 지정된 설정을 기본 노티피케이션 센터에서 삭제하는 것
[[NSNotificationCenter defaultCenter] removeObserver:nil name:nil object:pobj];
pobj가 포스트한 설정을 기본 노티피케이션 센터에서 삭제하는 것
주의할점, 만약에 레퍼런스 카운트 방식의 객체를 기본 노티피케이션 센터에 옵저버로 등록했다면 소멸할 때 옵저버를 삭제해야한다.
가비지 컬렉션의 경우엔 약한 참조를 사용해서 옵저버에 등록을 하면 제로화가 될때 포인터도 nil이 되기 때문에 명시적으로 삭제할 필요는 없어진다.
노티피케이션 큐가 있는데 여기는 비동기 전송을 지원하고 동일한 노티피케이션을 merge시킨다.
3. 메시지 포워딩
메시지 수신에 대한 구현이 없는 객체에게 메시지를 보내면 런타임에서 에러가 나야하지만 런타임은 리시버에게 아래와 같은 메시지를 보낸다.
- (void)forwardInvocation:(NSInvocation *)anInvocation
- (void)doesNotRecognizeSElector:(SEL)aSElector
즉 리시버 측에 다른 객체에 전송하거나 독자적인 에러 처리를 수행할 수 있다.
메시지 송신에는 타켓, 셀렉터, 인수 같은 정보들이 필요한데 NSInvocation 객체에 정보가 들어있다.
- (SEL)selector
- (id)target
- (void)invokeWithTarget:(id)anObject
인수의 갯수가 정해진 메소드의 경우에 NSInvocation의 정보를 이용하여 forwardInvocation:가 메시지를 전송할 수 있도록 오버라이드 할 수 있다.
ex)
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL sel = [anInvocation selector];
if ([fellow respondsToSelector:sel])
[anInvocation invokeWithTarget:fellow]
else
[super forwardInvocation:anInvocation];
}
위의 코드를 작성한다면 아래 코드도 쌍으로 작성해야한다.
아래의 코드는 NSMethodSignature를 반환하는 함수로 NSMethodSignature에는 메소드의 인수와 리턴값의 정보를 기록하고 있다.
ex)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([super respondsToSelector:aSelector])
return [super methodSignatureForSElector:aSelector];
return [fellow methodSignatureForSelector:aSelector];
}
그리고 전송 기능을 사용해서 처리하게 된 메소드는 respondsToSelector:로 확인하지 못한다.
혹시 전송 받을 객체가 처리해주는 메시지일 지라도 모두 자신이 처리한것 처럼 보일려면 해당 함수를 오버라이딩하면 된다.
메시지를 사용하지 못하게 만드는 팁
ex) doesNotRecognizeSelector:를 사용한다.
- (void)setSize:(NSSize *)size
{
[self doesNotRecognizeSelector:_cmd];
}
4. 리스폰더 체인
: 리스폰더 체인(responder chain)이란 계층적으로 배치된 GUI부품 간에 메시지가 자동적으로 전송되는 동작 방식
처리할 수 있는 객체가 발견될 때까지 다음 객체로 연쇄적으로 옮겨갑니다.
부품 A를 포함한 부품 B가 있다고 가정할 때, 리스폰더 체인은 부품 A에서 부품 B의 방향으로 메시지를 전달하도록 구성됩니다.
(프로그램으로 접속하거나, 런타임에 동적으로 변경이 가능하고 리스폰더(responder)는 반드시 NSResponder 클래스의 서브 클래스가 아니라도 상관 없습니다.
용어 : first responder, 처음 메시지가 보내지는 객체.
5. 취소 기능
Cocoa 환경에서는 이미 실행한 조작을 취소하거나 복귀 기능을 전담하는 NSUndoManager를 제공한다.
취소의 동작은 실행한 조작과 반대 효과를 내는 조작을 실행하는 방법을 NSUndoManager가 택하고 있다
(조작 전의 상태를 저장해 두고 원복시키는 방법이 아니다.)
NSUndoManager는 Foundation 프레임워크 클래스 중 하나이다
여러 개의 다큐먼트 윈도우가 있는 애플리케이션에서는 윈도우마다 하나씩 취소 관리자를 두는 것을 권장한다.
(tip. Application framework의 NSDocument클래스로 다큐먼트 윈도우를 관리하는 애플리케이션일 경우 NSDocument로 NSUndoManager instance로 접근하는게 가능하다)
동작원리 : 어플리케이션에서 어떤 동작을 할 때, 그 동작의 반대의 기능을 하는 메시지(들)을 NSUndoManager instance에 기록을 해둔다.(stack에 쌓는다) 그러다가 undo 콜이 오면 꺼내서 실행한다.
또한 취소 동작에 대한 반대되는 동작을 다시 NSUndoManager instance에 기록해둔다.
NSUndoManager instance로 조작을 기록하기 위한 두 가지 방법
- (void)registerUndoWithTarget:(id)target selector:(SEL)aSelector object:(id)anObject
- (id)prepareWithInvocationTarget:(id)target
'취소 관리자에 [self decrement:n]를 기록'에 해당하는 부분이 아래처럼 표현이 된다.(decrement:n에 해당하는 부분이 메세지다. 이 부분은 NSInvocation형태로 통째로 보존된다.)
[[undoManager prepareWithInvocationTarget:self] decrement: n];
취소 관리자에 취소, 복귀 요청은 다음 메소드를 이용한다.
- (void)undo;
- (void)redo;
6. 메소드의 동적 결합
: Objective-C에서는 런타임 시스템의 기능을 사용해서 클래스의 일부 메소드를 런타임에 추가하거나 교체하는 것이 가능하다.
(단, 다른 코드가 어려워지니 카테고리나 서브 클래스등을 검토후에 사용한다.)
함수를 특정 셀렉터의 메소드로 반영하기 위해선 런타임 시스템의 아래의 함수를 사용해야하고, objc/runtime.h를 임포트 시켜야한다.
BOOL class_addMEthod(Class cls, SEL name, IMP imp, const char *types);
ex. 첫 번째 함수를 메소드에 바인딩할 때, 두 번째 줄은 프로토타입, 세 번째는 메소드를 동적으로 결합시키는 코드
- (void)setList:(NSArray *)obj
void set_list(id, SEL, NSArray *)
class_addMethod(self, @selector(setList), (IMP)set_list, "v@:@");
메소드의 동적 결정
정의되지 않은 메소드가 호출될 때 동적으로 함수를 바인딩하게 할 수 있다.
+ (BOOL)resolveInstanceMethod:(SEL)name;
[예제]
추상 클래스와 클래스 클러스터
1. 추상 클래스
서브 클래스를 정의하는 것을 전제로, 서브 클랫스에서 정의할 메소드를 선언하는(혹은 불완전한 형태로 정의하는) 클래스를 추상 클래스(abstract class) 혹은 가상 클래스(virtual class)라고 합니다.
Objectivce-C에는 추상 클래스와 보통 클래스를 구분하기 위해 선언을 달리할 수 있는 키워드가 없습니다. Objective-C에는 추상 클래스란 용어는 개념만 존재합니다.
Objective-C에선 추상 클래스라도 alloc 메소드를 사용하면 인스턴스를 만들 수 있습니다. (하지만 NSObject를 인스턴스로 만들어서 사용하는 것 보단 추상 클래스 역활을 하는 것이 더 유용합니다)
[클래스, 클래스 구현, 상속에 대한 예제]
2. 클래스 클러스터
: 클래스 클러스터(class cluster)란 같은 인터페이스를 가지고 같은 기능을 제공하는 여러 클래스의 집합입니다.
인터페이스를 표현하는 추상 클래스를 공개하는데, 이것을 그 클래스 클래스터의 공개 클래스(public class)라고 부릅니다. 각각의 구체적인 클래스는 공개 클래스의 인터페이스가 추상화하여 클러스터 내부에 숨깁니다.
이때 이들 클래스를 직접 사용하면 안됩니다.
프로그램을 개발할 때 보통 공개 클래스로 만드는데, 실제 생성된 메모리 상에 존재하는 인스턴스는 클래스 클러스터의 내부에 숨겨있는 어떤 클래스의 인스턴스인 셈입니다.
- 클래스를 구현할 때 여러 방법(상황에 따라서 최적의 구현 방법이 달라질 수 있다.)
initializer, 컨비니언스 컨스트럭터 혹은 임의의 생성 메소드(ex. stringWithUTF8String) 이 중에 어떤 것을 사용할지에 따라 구현 클래스를 사용할지 결정된다.
(정리하면 목적에 맞는 constructor를 만들어놓고 그것을 사용해서 initilize를 하면 된다는 것)
Foundation 프레임워크의 주요 클래스 클러스터
배열 - NSArray, NSMutableArray
문자 집합 - NSCharacterSet, NSMutableCharacterSet
데이터 - NSData, NSMutableData
일자 - NSDate
사전 - NSDictionary, NSMutableDictionary
스캐너 - NSScanner
집합 - NSSet, NSMutableSet
문자열 - NSString, NSMutableString
속성이 있는 문자열 - NSAttributeString, NSMutableAttributeString
수치 데이터 - NSValue, NSNumber
통지 - NSNotification
파이프 - NSPipe
프로그램 개발 시 주의할 점
Objective-C에서는 클래스 클러스를 만들기 위한 언어 자체의 매커니즘은 없습니다.
하지만 일반적으로 공개 클래스는 추상 클래스로, 구체적인 클래스는 공개 클래스의 비공개 서브 클래스로 구현되어 있습니다.
클래스 클러스터를 이용하는 쪽에선 특별히 신경쓸건 없지만 두 가지 주의할 점이 존재한다.
1) 클래스 클러스터의 각 인스턴스는 비공개 서브 클래스의 인스턴스입니다. 따라서 공개 클래스를 인수로 한 메소드 isMemberOfClass:를 사용해도 어떤 결과가 나올지 알 수 없다.
인스턴스가 속하는 클래스를 확인해서 처리 내용을 변경하는 경우, 서브 클래스인지를 확인하려면 isKindOfClass:를 사용하거나 특정 메소드에 대응하고 있는지 respondsToSelector:로 확인하는게 좋다
2) 서브 클래스를 만드는 경우
대부분의 경우 공개 클래스는 추상 클래스로 구현되어 있어 각 메소드의 구체적인 구현은 비공개 서브 클래스에 기술되어 있습니다. 따라서 공개 클래스를 직접 상속하는 서브 클래스를 만들어도 기대한대로 작동하지 않습니다.
3. 클래스 클러스터의 서브 클래스를 만드는 방법
클래스 클러스터는 몇 종류의 클래스 구현을 추상화하여 클러스터 외부에서는 공개 클래스만 보이게 만듭니다. 이렇게 하면 클래스 자체를 사용하는 것은 어렵지 않지만 클래스 클러스터로 제공되는 클래스의 서브 클래스를 만들 때는 다소 애를 먹습니다.
카테고리로 대응하기
새로운 기능을 추가하는 것은 카테고리로 할 수 있지만 새로운 인스턴스 변수를 추가할 수 없기 때문에 구현에 한계는 있다.
공개 클래스 NSString에 추가된 카테고리는 클래스 클러스터 내에 숨겨진 서브 클래스에도 상속되므로 결과적으로 '새롭게 추가된 기능은 클래스 클러스터 전체에서 사용할 수 있습니다.'
클래스 클러스터의 인스턴스를 인스턴스 변수로 가지기
: 슈퍼 클래스인 클래스 클러스터의 인스턴스를 새롭게 생성하는 서브 클래스에서 인스턴스 변수로 가져가는 것 같은 방법
(새롭게 추가하거나 오버라이드한 메소드의 처리는 서브 클래스에서 실행하고 그 이외의 것들은 원래 인스턴스에 맡기는 것)
클래스 클러스터의 인스턴스에 처리를 맡길 경우, 그 처리 내용을 위임할 대상을 지정해야만 합니다.
원칙적으론 새로운 메소드를 정의하는 것을 고려해볼 수 있지만 NSObject에 정의되어 있는 forwardIncovation:이라는 메소드를 이용해서, 메시지를 인스턴스 변수에 위임하는 것을 생각해볼 수 있다.
(단, 이 메소드는 받아들인 메시지가 리시버에서 처리하지 못할 경우 호출되므로 새로 만들어진 클래스는 클래스 클러스터의 서브 클래스여서는 안됩니다. 따라서 비교적 쉽게 사용 가능하지만, 사용할 수 있는 경우는 극히 제한적이다.)
원시 메소드를 오버라이드하기
: 각 클래스 고유의 부분으로의 접근을 담당하는 메소드를 원시 메소드(primitive method)라고 한다. 그 이외의 메소드는 원시 메소드를 사용하도록 구현되어 있고, 정의는 추상 클래스인 공개 클래스에 기술되어 있다. 즉 클래스 클러스터 내에서도 처리의 추상화와 정보 은닉이 되어 있다.
구체적인 클래스 클러스터으 서브 클래스를 만드는 방법은 아래와 같다.
1. 비공개 데이터 구조를 결정한다.
인스턴스 변수로 가질 데이터 구조를 결정합니다.
2. 이니셜라이저를 정의한다.
init...
3. 컨비니언스 컨스트럭터를 정의한다.
4. 원시 메소드를 정의한다.
5. 그 외의 메소드를 정의한다.
문자열의 서브 클래스 만들기
주요 Foundation 프레임워크 클래스
1. 객체의 변경 가능성
: 클래스에는 속성을 변경할 수 있는(mutable) 클래스와 변경할 수 없는(immutable) 클래스가 있다.
Foundation 프레임워크 내 주요 불변, 가변 클래스
배열 - NSArray / NSMutableArray
데이터 - NSData / NSMutableData
사전 - NSDictionary / NSMutableDictionary
집합 - NSSet / NSMutableSet
문자열 - NSString / NSMutableString
속성이 있는 문자열 - NSAttributedString / NSMutableAttributedString
문자 집합 - NSCharacterSet / NSMutableCharacterSet
인덱스 집합 - NSIndexSet / NSMutableIndexSet
불변 객체는 객체에 할당한 값을 못바꾸는 것이고, 바꿀려면 새로운 객체를 만들어서 새로 할당해주는 형식
(Java에서 String str = String.append("aaa"); )
가변 객체는 동적할당이 가능한 변수인것으로 판단된다.
모든 가변 클래스가 불변 클래스에게 상속을 받아서 구현된 것입니다.
만약 불변 객체를 가변 객체로 취급하고 싶으면 아래의 함수로 복사본을 생성해서 사용합니다.
단, NSObject에서 정의한 메소드로 불변 클래스와 가변 클래스가 쌍으로 제공되는 클래스여야 가능합니다.
- (id)mutableCopy
2. 문자열 클래스
Objective-C 에서는 C언어 문자열처럼 문자열 객체를 프로그램 내에 간단히 기술하는 방법을 제공하고 있다.
@"[문자열]"
ex.
NSString *myname = @"Hello World!";
NSString *work = [@"Name: " stringByAppendingString: myname];
NSString
문자열 객체는 unicode를 사용
C언어와 문자열이 다른 특징은 Objective-C에서는 전부 객체이기 때문에 다른 객체들과 연동도 가능하고 질의성 메소드들도 사용이 가능하다. 또한 Cocoa API는 문자열 객체를 사용한다는 것을 가정하고 있다.
NSString은 클래스 클러스터로 제공되기 때문에 NSString이 인스턴스의 직접적인 클래스가 아니라는 것과 통상적인 방법으로는 서브 클래스를 만들 수 없다는 것에 주의가 필요합니다.
이니셜라이저 설명 중 컨비니언스 컨스트럭터라는 의미는 alloc 메소드와 그 initializer 조합으로 인스턴스를 생성한 후 리턴하는 클래스 메소드를 의미
내용 중 unichar타입은 한 문자의 Unicode를 표현하기 위한 것으로 char 타입과는 다른 것
(1) Unicode 문자열 조작
- (id)initWithUTF8String:(const char *)bytes
문자 코드가 UTF8이고, 널 문자로 끝나는 형식의 C언어 문자열에서 정보를 복사한 후 리시버를 초기화함
# 컨비니언스 컨스트럭터 : stringWithUTF8String
- (const char *)UTF8String
문자 코드가 UTF8이고 널 문자로 끝나는 형식을 리턴
(단, 리시버의 문자열 객체가 해제될 때 리턴된 문자열도 같이 소멸하기 때문에 해제된 후에 사용하기 위해선 복사를 해둬야 한다.)
- (MSUInteger)length
리시버가 가진 Unicode 문자의 문자 수를 리턴, C언어와 달리 데이터 조작을 위한 길이로 활용할 순 없다.
- (unichar)characterAtIndex:(NSUInteger)index
unicode type의 index 위치의 문자 1개를 리턴
- (id)initWithCharacters:(const unichar *)characters length:(NSUInteger)length
전달받은 문자와 길이로 초기화를 한다.
# 컨비니언스 컨스트럭터 : stringWithCharacters:length:
- (void)getCharacters:(unichar *)buffer
out parameter로 buffer에 리시버의 문자열을 넣는다. buffer가 충분해야하고, 만약에 범위만큼 복사하려면 getCharacters:range:을 사용한다.
(2) 문자 코드를 지정한 상호 변환
C언어의 문자열 혹은 바이트 배열과 NSString 사이에서 문자 코드를 지정한 후 서로 변환을 하는 것이 가능하다.
o NSASCIIStringEncoding 7비트 ASCII인코딩
o NSUTF*StringEncoding Unicode 문자의 8비트 표현(UTF8)
o NSMacOSRomanStringEncoding Mac OS의 인코딩
o NSKoreanEUCStringEncoding 한국어의 8비트 EUC 인코딩
...
- (id)initWithCString:(const char *)nullTerminatedCString
널 문자로 끝나는 C스타일의 문자열을 사용하여 리시버를 초기화한다.
# 컨비니언스 컨스트럭터 : stringWithCString:encoding
- (const char *)cStringUsingEncoding:(NSStringEncoding)encoding
지정한 encoding에 맞는 C스타일의 문자열을 반환한다. 단, 이것도 동일하게 리시버의 문자열 객체가 해제되면 같이 해제된다.
지정한 문자열로 변환할 수 없는 경우엔 NSCharacterConversionException이 발생한다.
(참고 getCString:maxLength:encoding:)
- (id)initWithData:(NSData *)data encoding:(NSStringEncoding) encoding
문자열을 unicode로 변환하고 NSString으로 초기화된 문자를 리턴한다.
- (NSData *)dataUsingEncoding:(NSStringEncoding)encoding
- (NSUInteger)lengthOfBytesUsingEncoding:(NSStringEncoding) encoding;
필요한 바이트 수를 리턴, 마지막 null은 포함하지 않는다.
- (BOOL)canBeConvertedToEncoding:(NSStringEncoding)encoding
지정한 인코딩으로 변환이 가능한지 확인
(3) 서식에 따른 문자열 작성
서식 문자열 자체도 NSString이라는 것과 서식으로 '%@'를 사용할 수 있는 점
- (id)initWithFormat:(NSString *)format, ...
format으로 지정된 서식에 맞게 문자열을 생성하고, 그것을 리시버에 초기화시킵니다. (가변인수 함수)
# 컨비니언스 컨스트럭터 : stringWithFormat:
(4) 비교
:문자열 간의 비교, 검색을 위한 메소드 설명
- (NScomparisionResult)compare:(NSString *)aString
객체의 값을 비교
- (BOOL)isEqualToString:(NSString *)aString
객체의 포인터를 비교해서 동일한지 판단
- (NSComparisonResult)caseInsensitiveCompare:(NSString *)aString
영문의 대, 소문자 구분없이 비교
- (BOOL)hasPrefix:(NSString *)aString
문자열의 앞 부분이 일치하는지 비교, 뒷 부분은 hasSuffix:
또 commonPrefixWithString:options:을 사용하면 리시버와 인수의 앞 부분에서 공통 문자열을 얻을 수 있다.
(5) 결합
- (NSString *)stringByAppendingString:(NSString *)aString
끝에 문자를 연결
- (NSString *)stringByAppendingFormat:(NSString *)format, ...
문자의 서식이 다른 경우에 문자열을 합치는 것인듯
(6) 부분 문자열
: 부분 문자열로 임시 문자열을 리턴한다. 범위를 나타내는 NSRange는 시작 위치와 길이를 멤버로 갖는다.
- (NSString *)substringToIndex:(NSUInteger)anIndex
앞부터 index까지 객체 생성
- (NSString *)substringFromIndex:(NSUInteger)anIndex
anIndex부터 끝까지 새로운 객체 생성
- (NSString *)substringWithRange:(NSRange)aRange
aRange에 해당하는 범위의 문자로 새로운 객체를 만든다.
(7) 검색과 치환
- (NSRange)rangeOfString:(NSString *)aString
문자열의 위치의 길이를 받는 함수, 대소문자를 무시하게 하는 옵션이 있다.
- (NSRange)lineRangeForRange:(NSRange)aRange
여러 행 안에 행의 인수로 지정한 문자의 범위를 리턴한다.
이때 어느 개행문자를 사용하던지 상관없다. (CR, LF, CRLF, Unicode의 한 글자, Unicode의 단락 구분 문자)
- (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement
리시버의 aRange로 표시되는 범위를 다른 문자열로 치환한 새로운 문자열을 만든다.
- (NSString *)stringByReplacingOccurrentcesOfString:(NSString *)target withString:(NSString *)replacement
리시버에 포함된 모든 target을 replacement로 치환한 새로운 문자열을 만든다.
(8) 대소문자 처리
lowercaseString은 리시버 내의 영문자를 모두 소문자로 변환한 임시 객체를 만든다.
uppercaseString은 리시버 내의 영문자를 모두 대문자로 만든다.
capitalizedString은 단어 앞 글자를 대문자로 나머지는 소문자로 변환한 임시 객체를 만든다.
(9) 수치의 평가
doubleValue은 리시버의 문자열을 double type의 수치로 평가한 것을 리턴한다.
이처럼 floatValue, intValue, integerValue, boolValue는 리시버의 문자열을 각각
float, int, NSInteger, BOOL 타입으로 평가한 것을 리턴합니다.
(아직은 어디에 쓰이는지 모르겠다)
(10) 경로의 조작
NSPathUtilities.h에 인터페이스가 기술되어 있음
(tip 사용자의 홈 디렉토리는 함수 NSHomeDirectory()이다)
- (NSString *)lastPathComponent
- (NSString *)stringByAppendingPathComponent:(NSString *)aStr
- (NSString *)stringByDeletingLastPathComponent
- (NSString *)pathExtension
- (NSString *)stringByAppendingPathExtension:(NSString *)aStr
- (NSString *)stringByDeletingPathExtension
확장자를 제거한 문자열 리턴, '.'도 삭제함
- (BOOL)isAbsolutePath
리시버를 경로명으로 간주하고 절대 경로일 경우 YES를 리턴한다.
+ (NSString *)pathWithComponents:(NSArray *)components
인수의 배열 요소를 문자열로 만든다.
- (NSArray *)pathComponents
리시버를 경로명으로 간주하고 경로의 구성요소를 표시하는 문자열을 배열로 만들어 리턴한다.
- (NSString *)stringByExpandingTildeInPath
리시버를 경로명으로 간주하고 경로의 앞 요소가 틸드 문자로 시작하는 경우, 사용자의 홈 디렉토리의 경로로 치환한 새로운 문자열을 리턴한다.
(반대기능 stringByAbbreiatingWithTildeInPath)
- (const char* )fileSystemRepresentation
리시버를 경로명으로 간주하고 파일 시스템이 경로명을 표시하기 위해 사용되는 문자 코드로 변환한 C언어 문자열을 리턴한다.
(11) 파일 입출력
문자열의 내용을 파일에서 읽어들이거나 파일에 쓰는 것이 가능하다
아래 메소드에서 사용되는 인수 error는 에러 발생 시 정보가 전달되는데 null을 이용하면 전달되지 않는다.
-(id)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error
인수의 경로로 지정한 파일에서 문자열을 읽어드린다. 파일을 읽을 수 없는 경우엔 리시버는 해제되고 nil 리턴과 동시에 포인터 error가 가리키는 변수에 자세한 에러 내용이 전달된다.
# 컨비니언스 컨스트럭터 : stringWithContentsOfFile:encoding:error:
-(id)initWithContentsOfFile:(NSString *)path usedEncoding:(NSStringEncoding *)enc error:(NSError **)error
파일에 사용되는 문자 코드를 자동으로 인식해서 인수 enc로 전달한다.
# 컨비니언스 컨스트럭터 : stringWithContentsOfFile:usedEncodingL:error:
-(BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile encoding:(NSStringEncoding)enc error:(NSError **)erro
리시버의 문자열을 문자 코드 enc로 변환하고, path에서 지정한 파일명으로 만듭니다. 파일을 만드는 순서는 임시 폴더에 만든 후 경로를 변경하는 것이기 때문에 이름이 같은 것과 같은 에러가 나더라도 파일은 유지된다.
- (id)initWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSERror **)error
인수의 URL에서 지정한 파일에서 지정한 문자 코드로 문자열을 읽어서 그 내용으로 리시버를 초기화합니다.
# 컨비니언스 컨스트럭터 : stringWithContentsOfURL:encoding:error:
(12)그 외 기타
- (id)init
리시버를 초기화해서 빈 문자열을 리턴. 팁으로 NSMutableString에서 사용하면 유용하다
# 컨비니언스 컨스트럭터 : string
- (id)initWithString:(NSString *)aString
문자열을 복사해 리시버를 초기화, 인수에는 NSMutableString의 인스턴스도 지정할 수 있다
가변 문자열에서 불변 문자열을 만들때도 활용할 수 있다.
# 컨비니언스 컨스트럭터 : stringWithString
- (NSString *)description
이 메소드는 NSObject로 정의되어 있어 객체의 내용을 표시하는 문자열을 리턴
NSString에서는 self가 그래도 리턴된다.
- (id)propertyList
리시버를 프로퍼티 리스트의 텍스트 표현으로 간주하고 NSString, NSData, NSArray, NSDictionary로 구성되는 프로퍼티 리스트를 만들어 리턴합니다.
- (NSArray *)componentsSeparatedByCharactersInSet:(NSCharacterSet *)sep
인수에 문자열 집합 객체를 지정하고, 그 집합에 속하는 문자로 구분되는 부부 문자열을 요소로 하는 배열을 리턴합니다.
문자 집합 객체에 대해서는 클래스 NSCharacterSet 및 NSMutableCharacterSet의 레퍼런스를 참고
tip) chrs = [NSCharacterSet whitespaceCharacterSet]; // 구분 문자로 공백이나 탭등을 지정하기 위해서는 다음과 같은 문자 집합 객체를 사용
NSMutableString
(1) 인스턴스의 초기화, 생성
- (id)initWithCapacity:(NSUInteger)capacity
# 컨비니언스 컨스트럭터 : stringWithCapacity
(2) 문자열에 추가
-(void)appendString:(NSString *)aString
-(void)appendFormat:(NSString *)format, ...
(3) 삽입, 삭제, 치환
: 가변변수라서 중간에 삽입, 삭제가 가능하구나. 또한 NSString의 클래스를 상속받은 것이라서 기존 함수들도 다 사용이 가능할 것이다.
- (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc
- (void)deleteCharactersInRange:(NSRange)range
- (void)setString:(NSString *)aString
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString
- (NSUInteger)replaceOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)opts
3. 데이터 클래스
NSData
Cocoa 환경에서 바이트 배열의 데이터를 다루는 표준 클래스
NSData는 임의의 바이트 배열을 객체로 다루기 위한 래퍼(wrapper)
(바이트 배열에 '객체의 껍데기를 씌운' 형태의 클래스)
NSData의 인스턴스는 작성된 데이터에 대해서 변경을 할 수 없다.(NSMutableData를 사용하면 변경이 가능하다)
NSData도 다른 것과 동일하게 클래스 클러스터로 제공되기 때문에 NSData가 인스턴스의 직접적인 클래스가 아니라는 것과 통상적인 방법으론 서브 클래스를 만들 수 없다는 것에 주의한다.
(1) 데이터 객체의 초기화, 생성
- (id)initWithBytes:(const void *)bytes length:(NSUInteger)length
bytes가 지정한 위치에서 시작하고, 길이가 length인 데이터를 복사해서 데이터 객체의 내용이 되로록 초기화합니다.
# 컨비니언스 컨스트럭터 : dataWithBytes:length:
- (id)initWithBytesNoCopy:(void *)bytes length:(unsigned)length freeWhenDone:(BOOL)flag
bytes가 지정한 위치에서 시작하고, 길이가 length인 데이터가 데이터 객체의 내용이 되도록 초기화합니다. Flag가 YES이면 생성된 데이터 객체가 bytes의 오너쉽을 가져서 객체가 해제될 때 동시에 해제됩니다. 따라서 bytes는 malloc()으로 획득한 메모리 영역이어야만 합니다. flag가 NO인 경우 bytes는 자동적으로 해방되지 않지만, 데이터 객체가 존재하는 동안에는 해제하면 안됩니다.
# 컨비니언스 컨스트럭터 : dataWithBytesNoCopoy:length:freeWhenDone:
- (id)initWithData:(NSData *)aData
tip. 인수에 가변 데이터도 넣을 수 있기 때문에 불변 데이터를 만드는데 사용되기도 한다.
# 컨비니언스 컨스트럭터 : dataWithData
+ (id)data
데이터 길이가 0인 것을 만들어 return한다. 이 메소드는 주로 NSMutableData에서 사용한다.
(2) 데이터로의 접근
주로 NSRange는 시작 위치와 길이를 멤버로 보유하여 범위를 나타낸다.
- (NSUInteger)length
- (const void *)bytes
- (void)getBytes:(void *)buffer
:output parameter
- (void)getBytes:(void *)buffer length:(NSUInteger)length
: length만큼 buffer에 넣는다.
- (NSData *)subdataWithRange:(NSRange)range
(3) 비교
- (BOOL)isEqualToData:(id)anObject
:리시버와 인수의 객체가 가진 데이터 길이와 내용이 일치할 경우 YES를 리턴한다
(4) 파일 입출력
- (NSString *)description
데이터의 내용을 ASCII 형식의 프로퍼티 리스트에서 표현한 문자열을 리턴한다.
tip. 바이트 배열은 <>로 16진수를 둘러싸서 표현합니다.
- (id)initWithContentsOfFile:(NSString *)path options:(NSUInteger)mask error:(NSError **)errorPtr
# 컨비니언스 컨스트럭터 : dataWithContentsOfFile:options:error:
-(id)initWithContentsOfFile:(NSString *)path
위 함수와 동일한 기능
# 컨비니언스 컨스트럭터 : dataWithContentsOfFile
-(id)initWithContentsOfURL:(NSURL *)aURL options:(NSUInteger)mask error:(NSError **)errorPtr
# 컨비니언스 컨스트럭터 : dataWithContentsOfURL:options:error:
- (BOOL)writeToFile:(NSString *)path automically:(BOOL)flag
NSMutableData
: 변경 가능한 바이트 배열 데이터를 다루는 클래스
(1) 데이터 객체의 초기화, 생성
- (id)initWithCapacity:(NSUInteger)capacity
# 컨비니언스 컨스트럭터 : dataWithCapacity
- (id)initWithLength:(NSUInteger)capacity
# 컨비니언스 컨스트럭터 : dataWithLength;
(2) 데이터의 접근
- (void *)mutableBytes
리시버에 할당된 바이트 배열의 포인터를 리턴, 해당 변수는 쓰기가 가능하다
(3) 데이터의 추가
- (void)appendData:(NSData *)otherData
- (void)appendBytes:(const void *)bytes length:(NSUInteger)length
(4) 데이터의 변경
- (void)replaceBytesInRange:(NSRange)range withBytes:(const void *)replacementBytes length:(NSUInteger)replacementLength
리시버의 바이트 배열 중 range로 지정된 범위를 길이 replacementLength의 바이트 배열 replacementBytes로 치환
- (void)replaceBytesInRange:(NSRange)range withBytes: (const void*)bytes
- (void)resestBytesInRange:(NSRange)range
(5) 바이트 배열 길이의 변경
- (void)increaseLEngthBy:(NSUInteger)extraLength
- (void)setLEngth:(NSUInteger)length
4. 배열 클래스
NSArray
NSArray 객체는 일단 만들어지면 할당된 객체를 다른 객체로 치환하거나 추가, 삭제하는 것이 불가능합니다.
오너쉽의 간략한 설명은, 배열 객체는 할당된 객체에 retain을 보내고, 해제될 때 release를 보내야한다.
NSArray도 클래스 클러스터로 제공되고 있다.
(1) 배열 객체의 초기화, 생성
+ (id)array
+ (id)arrayWithObject:(id)anObject
- (id)initWithObjects:(id)firstObj, ...
# 컨비니언스 컨스트럭터 : arrayWithObjects:
- (id)initWithObjects:(id *)objects count:(NSUInteger)count
인수 objects는 객체가 할당된 (C언어의) 배열입니다. 그 배열 앞 부분에서 count개의 객체를 할당할 수 있도록 배열 객체를 초기화해서 리턴
# 컨비니언스 컨스트럭터 : arrayWithObjects:count:
- (id)initWithArray:(NSArray *)anArray
이것 또한 인수를 가변 배열로 받을 수 있다.
# 컨비니언스 컨스트럭터 : arrayWithArray
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag
flag가 참일 경우 배열의 각 요소에 대해 복사본이 만들어져 새로운 배열에 할당된다.
(2) 배열 객체로의 접근
배열 객체의 요소에 접근하거나 어떤 객체가 포함되어 있는지 확인하는 메소드
- (NSUInteger)count
- (NSUInteger)indexOfObject:(id)anObject
존재하면 index, 없으면 NSNotFound
- (id)objectAtIndex:(NSUInteger)index
- (id)lastObject
- (void)getObjects:(id *)aBuffer
리시버에 할당된 객체를 aBuffer로 지정된 (C언어의) 배열에 복사합니다.
객체로의 포인터가 복사해서 전달할 뿐이다.
- (NSArray *)subarrayWithRange:(NSRange)range
새로운 배열을 만들어 리턴한다.
(3) 비교
- (BOOL)isEqualToArray:(id)anObject
요소의 갯수, 위치, 내용이 전부 동일하면 YES를 리턴한다.
- (id)firstObjectCommonWithArray:(NSArray *)otherArray
리시버와 인수의 배열이 공통으로 가진 객체 중, 처음 발견된 것을 리턴합니다.
(4) 새로운 요소의 추가
- (NSArray *)arrayByAddingObject:(id)anObject
- (NSArray *)arrayByAddingObjectsFromArray:(NSArray *)anArray
리시버와 동일한 요소를 가지고 인수로 지정된 배열 객체가 가진 요소를 끝 부분에 추가한 임시 배열 객체를 만들어 리턴한다.
(5) 정렬
배열 객체의 내용을 정렬해서 '새로운' 배열 객체를 만드는 메소드
- (NSArray *)sortedArrayUsingSelector:(SEL)comparator
배열 객체 내의 요소를 서로 비교해서 요소가 오름차순이 되도록 정렬된 임시 배열 객체를 만들어서 리턴
(요소 간의 비교는 셀렉터 comparator로 지정된 비교 메소드를 사용)
이 메소드는 인수를 하나 가지고 결과로 NSComparisonResult타입으로 만들어진 값을 리턴합니다.
예를 들면 배열 객체 anArray의 요소가 모두 NSString 문자열인 경우, NSString의 메소드 compare:을 사용해서 정렬합니다.
newArray = [anArray sortedArrayUsingSelector:@selector(compare:)];
- (NSArray *)sortedArrayUsingFunction:(NSInteger(*)(id, id, void *))comparator context:(void *)context
배열 객체 내의 요소를 서로 비교해서 요소가 오름차순이 되도록 정렬된 임시 배열 객체를 새로 만들어 리턴합니다.
요소 간의 비교는 인수 comparator로 지정된 함수를 사용합니다. 이 함수는 인수를 세 개 사용해서 결과로 NSComparisonResult 타입인 결과를 리턴합니다.
NSInteger myCmp(id arg1, id arg2, void *context);
첫 번째, 두 번째 요소는 배열 객체 내의 요소가 전달되고, 세 번째 인수는 메소드의 인수 context가 전달됩니다.
(6) 요소로 메시지 송신
배열 객체의 요소에 바로 메시지를 보내는 것이 가능하다.
- (void)makeObjectsPerform:(SEL)aSelector
셀렉터로 지정하는 메시지는 인수가 없는 것이어야만 한다.
- (void)makeObjectsPerform:(SEL)aSelector withObject:(id)anObj
셀렉터 aSelector로 지정하는 메시지는 id타입의 인수를 하나 가지는 것이어야만 합니다. 배열의 요소에 메시지가 보내질 때 인수로 anObj가 전달됩니다.
(7) 문자 배열 요소의 조작
- (NSString *)componentsJoinedByString:(NSString *)separator
리시버의 전체 요소들 사이에 separator로 지정한 문자열을 삽입하여 결합한 임시 객체를 만들어 리턴
- (NSArray *)pathsMathcingExtensions:(NSArray *)filterTypes
리시버의 각 요소는 파일명을 표시하는 문자열이라고 가정할 때 인수의 배열은 파일의 확장자를 표시하는 문자열을 요소로 가집니다. 이 메소드는 리시버의 요소 중 인수의 배열에 포함된 중 확장자를 가지는 것만 꺼내서 임시 배열 객체로 만들어 리턴합니다.
(8) 파일 입출력
파일 입출력은 프로퍼티 리스트 형식으로 이루어집니다.
- (NSString *)description
배열 객체의 내용을 ASCII 형식의 프로퍼티 리스트로 표현한 문자열을 리턴합니다. 문자열으느 각 요소에 description을 보내서 얻어낸 문자열을 ','로 구분해서 ()로 둘러싸서 표현한다
- (id)initWithContentsOfFile:(NSString *)aPath
프로퍼티 리스트의 형식으로 보존된 파일에서 내용을 읽어서 배열 객체를 초기화합니다.
# 컨비니언스 컨스트럭터 : arrayWithContentsOfFile:
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
프로퍼티 리스트의 형식으로 보존된 파일에서 내용을 읽어서 배열 객체를 초기화합니다. 파일을 읽을 수 없을 경우, 리시버는 해방되어 nil을 리턴합니다.
NSMutableArray
(1) 배열 객체의 초기화
- (id)initWithCapacity:(NSUInteger)numItems
# 컨비니언스 컨스트럭터 : arrayWithCapacity
(2) 배열에서의 추가와 치환
레퍼런스 카운트 방식을 사용하는 경우, 배열 객체에 추가된 객체에는 retain 메시지가 보내지고, 제거된 객체에는 release 메시지가 보내집니다.
- (void)addObject:(id)anObject
삽입하는 인수는 nil을 제외하고 가능합니다.
- (void)addObjectsFromArray:(NSArray *)otherArray
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject
- (void)replaceObjectsInRange:(NSRange)aRange withObjectsFromArray:(NSArray *)otherArray
- (void)setArray:(NSArray*)otherArray
모든 요소를 삭제하고 치환
- (void)exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2
요소 순서 바꾸기
(3) 객체의 제거
삭제된 객체에는 release 메시지가 보내집니다.
- (void)removeAllObjects
- (void)removeLastObject
- (void)removeObjectAtIndex:(NSUInteger)index
- (void)removeObjectInRange:(NSRange)aRange
- (void)removeObject:(id)anObject
- (void)removeObjectInArray:(NSArray *)otherArray
인수로 지정된 배열에 포함된 객체를 모두 삭제한다.
(4) 정렬
- (void)sortUsingSelector:(SEL)comparator
요소 간의 비교는 셀렉터 comparator로 지정된 비교 메소드를 사용합니다.
배열 객체와 오너쉽
배열 객체는 할당된 객체에 retain 메시지를 보내서 그 데이터를 유지합니다. 또 배열 객체가 해제될 때 할당된 객체 모두에게 release 메시지를 보냅니다. 그래서 아래와 같이 코드를 사용하면 안됩니다.
NSArray *arr = [[NSArray alloc] initWithObjects:[[Card alloc] init, [[Player alloc] init], nil];
위 코드에서 오너쉽이 이 코드블록에도 존재하기 때문에 코드블록에 연결되어 있는 refCount를 줄여야한다.
그래서 아래와 같이 사용해야 한다.
NSArray *arr = [[NSArray alloc] initWithObjects:[[[Card alloc] init] autorelease], [[[Player alloc] init] autorelease], nil];
혹은 아래와 같이 release를 별도로 호출해도 된다.
id card = [[Card alloc] init];
id player = [[Player alloc] init];
NSArray *arr = [[NSArray alloc] initWithObjects:card, player, nil];
[card release];
[player release];
그리고 배열에서 객체를 얻어낸 후에 일단 유지시키고 있을려면 아래와 같이 한다.
NSMutableArray *marr;
id obj;
...
obj = [[[marr objectAtIndex: index] retain] autorelease];
[marr removeObjectAtIndex: index];
복수의 객체를 할당하기 위한 '그릇'에 해당하는 것을 컬렉션(collection)이라고 부릅니다.
이 컬렉션도 객체를 담을 땐 retain, 뺄때는 release를 부릅니다.
고속 열거
for ( 변수 in 컬렉션 표현식) {
/* 처리 내용 */
}
(가변 컬렉션은 루프를 실행하는 동안 내용이 변경되면 예외가 발생하며 실행이 중단된다)
열거자(enumerator)
열거자를 표현하는 추상 클래스가 NSEnumerator로 컬렉션 클래스의 인스턴스 메소드에 따라 구체적인 인스턴스가 리턴됩니다. 이 클래스에는 아래와 같이 2개의 함수가 있습니다.
- (id)nextObject
열거된 다음 요소를 '꺼냅니다'. 더 꺼낼 요소가 없으면 nil을 리턴합니다.
- (NSArray *)allObjects
그리고 NSEnumeration의 인스턴스를 리턴하는 메소드명은 컬렉션 클래스에 따라 다른데 NSArray의 경우 아래의 2가지가 NSEnumerator class를 리턴합니다.
- (NSEnumerator *)objectEnumerator
- (NSEnumerator *)reverseObjectEnumerator
이 함수들을 사용한 예시를 보면 아래와 같습니다.
NSArray *myarray;
id obj;
NSEnumerator *enumerator;
...
enumerator = [myarray objectEnumerator];
while ((obj = [enumerator nextObject]) != nil) {
/* 처리 내용 */
}
주의할점, 레퍼런스 카운트 방식을 이용하는 경우 NSEnumerator의 인스턴스는 요소를 열거하는 동안 그 컬렉션 객체를 유지합니다. 하지만 마지막 요소를 꺼낸 후, 컬렉션 객체에 대한 오너쉽은 해제됩니다.
거꾸로 꺼내는 코드
enumerator = [myarray reverseObjectEnumerator];
for (obj in enumerator) {
/* doing */
}
만약 컬렉션내의 요소의 가감이 존재한다면 index를 사용해서 룹을 도는게 좋다
NSMutableArray *myArray;
id obj;
NSInteger len, idx;
...
len = [myArray count];
for (idx = len - 1; idx >= 0; idx--) {
obj = [myArray objectAtIndex: idx];
if ([obj isGeek])
[myArray removeObjectAtIndex: idx];
}
집합 클래스
(요소 간 순서가 없고 같은 요소가 반복적으로 포함되는 것 : NSSet, NSMutableSet)
NSSet의 메소드
+ (id)set
- (id)initWithArray:(NSArray *)array
배열에 같은 객체가 여러 번 포함된 경우, 집합에는 하나만 담깁니다. 물론 array로 가변 인수 리스트나 C언어의 배열을 사용하는해서 요소를 지정하는 이니셜라이저 혹은 그들의 컨비니언스 컨스트럭터가 제공된다.
- (NSUInteger)count
- (NSArray *)allObjects
- (BOOL)containsObject:(id)anObject
- (BOOL)isEqualToSet:(NSSet *)toherSet
- (BOOL)isSubsetOfSet:(NSSet *)otherSet
NSMutable
- (id)initWithCapacity:(NSUInteger)numItems
# 컨비니언스 컨스트럭터 : setWithCapacity
- (void)addObject:(id)anObject
- (void)removeObject:(id)anObject
- (void)unionSet:(NSSet *)otherSet
set별로 합집합을 만듭니다. 마찬가지로 메소드 munusSet:은 공통 요소를 삭제합니다. intersectSet:은 공통 요소만 남기고 교집합을 만듭니다.
5. 사전 클래스
: 키값에 관련된 값을 검색하기 위한 데이터 구조
사전 클래스는 NSDictionary와 NSMutableDictionary가 제공된다. 키와 값의 쌍을 엔트리라고 부릅니다. 일반적으로 키는 문자열로 정하는 경우가 많습니다.
NSDictionary
NSDictionary도 일단 생성되면 담겨있는 엔트리를 다른 객체에 치환하거나 추가, 삭제할 수 없습니다. 하기 위해선 NSMutableDictionary를 사용하면 됩니다. 이 클래스도 클래스 클러스트로 제공되고 있습니다.
(1) 사전 객체의 초기화, 생성
+ (id)dictionary
+ (id)dictionaryWithObject:(id)anObject forKey:(id)aKey
- (id)initWithObjects:(NSArray *)objects forKey:(NSArray *)keys
# 컨비니언스 컨스트럭터 : dictionaryWithObjects:forKeys:
key와 object를 쌍으로 꺼내서 배열을 만듭니다. 그래서 두 객체의 갯수는 동일해야합니다.
- (id)initWithObjects:(id *)objects forKeys:(id *)keys count:(NSUInteger)count
# 컨비니언스 컨스트럭터 : dictionaryWithObjects:forKeys:count:
- (id)initWithObjectsAndKeys:(id)object, (id)key, ...
인수 리스트는 값과 키의 객체를 표현하고 마지막에 nil이 붙어 있어야 합니다. 이들 값과 키로만들어진 엔트리를 포함하도록 리시버를 초기화합니다.
# 컨비니언스 컨스트럭터 : dictionaryWithObjectsAndKeys:
- (id)initWithDictionary:(NSDictionary *)otherDictionary
인수로 다른 사전에 담겨있는 엔트리로 초기화를 시킵니다.
# 컨비니언스 컨스트럭터 : dictionaryWithDictionary:
(2) 사전으로의 접근
- (NSUInteger)count
- (id)objectForKey:(id)aKey
- (NSArray *)allKeys
- (NSEnumerator *)keyEnumerator
- (NSArray *)allKeysForObject:(id)anObject
여기서 '같다'라는 의미는 isEqual: 메소드로 비교했을 때 YES를 리턴하는 것을 의미함
(3) 비교
- (BOOL)isEqualToDictionary:(id)anObject
엔트리의 수가 같고, 서로 같은 키에 같은 값이 대응할 경우 YES
(4) 파일 입출력
: 파일의 입출력은 프로퍼티 리스트 형식으로 이루어집니다.
- (NSString *)description
- (id)initWithContentsOfFile:(NSString *)path
# 컨비니언스 컨스트럭터 : dictionaryWithContentsOfFile:
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
NSMutableDictionary
(1) 사전 객체의 초기화, 생성
- (id)initWithCapacity:(NSUInteger)capacity
# 컨비니언스 컨스트럭터 : dictionaryWithCapacity:
(2) 엔트리의 추가와 삭제
컬렉션 클래스들과 동일하게 객체를 담을 때는 retain, 뺄 때는 release가 호출된다.
- (void)setObject:(id)anObject forKey:(id)aKey
- (void)addEntriesFromDictionary:(NSDictionary *)otherDic
다른 사전의 객체를 추가하는데, 만약 같은 키를 갖은 엔트리가 존재한다면 인수의 사전에 있는 값이 새로운 값이 된다.
(기존에 있는 것은 리시버, input parameter는 인수라고 부를 때 인수의 값이 새로운 값으로 오버라이딩 된다)
- (void)setDictionary:(NSDictionary *)otherDic
치환
- (void)removeObjectForKey:(id)aKey
- (void)removeObjectsForKeys:(NSArray *)keyArray
- (void)removeAllObjects
6. 약한 참조를 사용하는 컬렉션 클래스
:이 경우 컬렉션에 담겨 있더라도 불필요해지면, 회수되어 컬렉션에서도 삭제된다.
약한 참조로 객체를 참조하는 컬렉션의 각 요소를 루프로 처리할 경우에는 for...in문을 이용하면 됩니다. 고속 열거는 요소 객체를 스택 내에 대입해서 동작하도록 되어 있어, 처리 중에 객체가 해제되는 문제를 방지할 수 있습니다.
포인터 배열
NSPointerArray는 배열 객체이긴 하지만 NSArray와 상속관계는 없다.
nil도 담을 수 있다.
for...in문 사용 가능(null도 나올 수 있으니 주의)
(책에선 NSPointerArray의 인스턴스를 포인터 배열이라고 한다.)
(1) 컬렉션 객체의 생성
+ (id)pointerArrayWithStrongObjects
강한 참조를 사용해서 요소를 참조하는 포인터 배열을 만들어 리턴합니다.
+ (id)pointerArrayWithWeakObjects
약한 참조를 사용해서 요소를 참조하는 포인터 배열을 만들어서 리턴합니다.
(2) 요소 포인터로의 접근
- (void *)pointerAtIndex:(NSUInteger)index
- (NSArray *)allObjects
(3) 포인터의 추가, 삭제
- (void)addPointer:(void *)pointer
- (void)insertPointer:(void *)item atIndex:(NSUInteger)index
- (void)removePointerAtIndex:(NSUInteger)index
- (void)replacePointerAtIndex:(NSUInteger)index withPointer:(void *)item
index위치의 객체를 삭제하고 두 번째 인수의 포인터를 새로운 요소로 추가합니다.
(4) 컬렉션의 조작
- (NSUInteger)count
- (void)setCount:(NSUInteger)count
현재 요소보다 큰 값을 지정하면 끝 부분에 null을 추가시키고, 현재 요소 갯수보다 작은 값의 경우, 남은 요소를 삭제한다.
- (void)compact
리시버에 포함되는 null 요소를 모두 제거합니다.
약한 참조의 집합 : NSHashTable
NSHashTable은 NSMutableSet과 비슷한 가변 집합 객체 클래스, NSMutableSet과 상속 관계는 없다. 하지만 약한 참조로 요소 객체를 참조한다는 것은 많이 유사하다.
NSHashTable에는 NSSet 및 NSMutableSet과 같은 메소드가 준비되어 있으므로 아래는 다른 메소드만 소개한다.
+ (id)hashTableWithWeakObjects
약한 참조를 사용해서 요소를 참조하는 집합 객체를 만들어 리턴
- (BOOL)containsObject:(id)anObject
- (NSSet *)setRepresentation
모든 객체를 포함하는 집합의 인스턴스를 만들어서 리턴
- (BOOL)isEqualToHashTable:(NSHashTable *)other
인수가 모두 같은지 확인하는 메시지, NSSet과 비슷하게 isSubsetOfHashTable, intersectsHashTable:이 존재해서 부분 집합인지, 공통 부분을 가지는지 판단한다
- (void)unionHashTable:(NSHashTable *)other
리시버의 집합에 인수의 집합의 요소를 더해 합집합을 만든다. NSSet과 비슷하게 minusHashTable:이나 intersectHashTable:이 있어서 공통 부분을 삭제하거나 교집합을 만드는 역활을 한다.
약한 참조가 사용되는 사전 : NSMapTable
비슷한 클래스로는 NSMapTable, NSDictionary가 있는제 NSDictionary와 상속 관계는 아니다.
키와 값 어느 한쪽이나 양쪽 모두에 약한 참조를 사용하는 것이 가능하다. 가비지 컬렉션에 따라 키와 값 어느 한 쪽이 해제될 경우, 그 엔트리는 사전에서 제거됩니다.
(1) 컬렉션 객체의 생성
NSDictionary와는 달리 엔트리를 추가할 때 키는 복사되지 않습니다. 키를 복사하기 위해서는 다른 설정을 필요로 합니다.
+ (id)mapTableWithStrongToStrongObjects
키 : 강한 참조 / 값 : 강한 참조
+ (id)mapTableWithStrongToWeakObjects
키 : 강한 참조 / 값 : 약한 참조
+ (id)mapTableWithWeakToStrongObjects
키 : 약한 참조 / 값 : 강한 참조
+ (id)mapTableWithWeakToWeakObjects
키 : 약한 참조 / 값 : 약한 참조
(2) 엔트리의 조작
- (void)setObject:(id)anObject forKey:(id)aKey
키와 값 객체 모두 nil이 되면 안된다.
- (id)objectForKey:(id)aKey
인수 aKey를 키로 하는 엔트리의 값 객체를 리턴
- (id)removeObjectForKey:(id)aKey
참고로 removeAllObjects는 모든 엔트리를 삭제한다.
(3) 그 외 기타
- (NSUInteger)count
- (NSEnumerator *)keyEnumerator
- (NSDictionary *)dictionaryRepresentation
7. 수에 대한 래퍼 클래스
:objective-C는 정수나 실수와 같은 데이터 타입의 경우 객체로 취급하지 않기 때문에 그대로는 컬렉션에 담지 못한다.
그래서 NSValue와 그 서브 클래스 NSNumber가 있다. 이것들은 수치나 좌표와 같은 데이터 타입을 객체로 취급하기 위한 래퍼(wrapper)입니다.
NSValue와 NSNumber는 클래스 클러스터로 제공된다.
NSNumber
:NSNumber의 인터페이스는 Foundation/NSValue.h로 선언되어 있다.
(1) 초기화, 생성
다음 메소드는 NSNumber의 인스턴스를 초기화해서 NSInteger형 데이터를 래핑합니다.
- (id)initWithInteger:(NSInteger)value
아래의 함수가 컨비니언스 컨스트럭터이다
+ (NSNumber *)numberWithInteger:(NSInteger)value
여러 데이터 타입에 다 제공된다.
BOOL, char, double, float, int, NSInteger, long, long long, short, unsigned char, unsigned int, NSUInteger, unsigned long, unsigned long long, unsigned short
(2) 값의 참조
예로 NSNumber의 인스턴스가 가지는 값을 NSInteger 타입으로 참조하기 위해서는 다음 메소드를 사용합니다.
리시버는 NSInteger 타입의 데이터를 래핑하는 것이 아니라도 상관 없습니다. 이 경우 필요하면 자동적으로 타입 변환을 해서 값을 리턴합니다.
- (NSInteger)integerValue
마찬가지로 여러 데이터 타입으로 값을 참조하기 위한 메소드가 제공된다.
(3) 그 외 기타
- (BOOL)isEqualToNumber:(NSNumber *)aNumber
리시버와 인수 객체를 비교한다
- (NSComparisonResult)compare:(NSNumber *)aNumber
리시버와 인수 객체 간의 '크기'를 비교한다. 겨로가는 NSComparisionReulst라는 타입으로 리턴한다.
- (NSString *)stringValue
리시버가 가지는 수치를 문자열로 리턴한다.
NSValue
인터페이스는 Foundation/NSValue.h에, 좌표나 구조체의 정의는 Foundation/NSGeometry.h에 기술됨
주요 데이터 타입은 아래와 같다.
Pointer, NSPoint, NSRect, NSSize, NSRange
- (BOOL)isEqualToValue:(NSValue *)value
리시버와 인수 객체를 비교
NSNull
: 배열 객체나 사전 객체에는 요소로 nil을 포함하지 못하지만 경우에 따라 적절한 값이 없다는 것을 의미하기 위해서 넣을 수 있다.
이런 경우 빈 객체를 표현하기 위해서 NSNull을 사용하고, Foundation/NSNull.h에 선언되어 있다.
+ (NSNull *)null
이 메소드는 NSNull의 인스턴스를 리턴하는데 이 객체는 보통 상수입니다. 그래서 상수 객체이므로 release 메시지를 보내도 해제되지 않고 description 메시지에 대해서 <null>이라는 문자열을 리턴한다.
NSNull 객체인지 확인하는 방법은 아래와 같다.
if (obj == [NSNull null]) ...
Q. C임에도 불구하고 encoding type을 Unicode를 채택하고 있는데, 그 장점이 있는지
키-밸류 코딩
: 객체가 가지는 정보에 접근할 때, 그 정보를 의미하는 문자열을 키로 사용해서 간접적으로 그 정보의 값을 참조하는 매커니즘
키-밸류 방식이 간접적인 2가지 이유
1) 키가 되는 문자열은 런타임에 결정되어도 무방
2) 프로퍼티로 접근하는 구체적인 방법에 대해서는 이용하는 측에서는 알지 못함
키-밸류 코딩의 기본 동작
주요 메소드는 비공식 프로토콜인 NSKeyValueCoding에 선언되어 있고, 헤더 파일은 Foundation/NSKeyValueCoding..h이다.
NSObject에 존재하기 때문에 오버라이드하지 않는 한 선언할 필요가 없다
- (id)valueForKey:(NSString *)key
key에 해당하는 값을 출력, 값이 없을 경우 valueForUndefinedKey: 메소드가 호출
- (void)setValue:(id)value forKey:(NSString *)key
key, value를 설정. 프로퍼티의 값을 설정하지 못할 경우 setValue:forUndefinedKey: 메소드가 호출됨
기타 지식들
Message Forward순서
- 'finalize' is deprecated: Objective-C garbage collection is no longer supported
- 'NSGarbageCollector' is deprecated: first deprecated in macOS 10.10 - Building Garbage Collected apps is no longer supported.
__NSCFConstantString
nil, Nil, NSNull, NULL
nil - literal null value for Objective-C objects
Nil - literal null value for Objective-C classes
NSNull - singleton object used to represent null
NULL - literal null value for C pointers
NSNotificationCenter
story board에서 동작을 하려면 IBAction
정보를 보여주기만 하는거면 IBOutlet
'소프트웨어 > Objective-C' 카테고리의 다른 글
프로 오브젝티브-C 디자인 패턴 (0) | 2018.01.16 |
---|---|
effective objective-c 2.0 (0) | 2018.01.10 |