오브젝티브씨 시작하기...
오브젝티브씨! 이 초보적이고 재미없는 주제로 몇 자 적어보자 하는 것은... 처음 시작하는 분들이 조금이라도 방향을
잡았으면 하는 마음에서입니다. 또 개인적으로는 오브젝티브씨라는 언어가 주는 단순함이 좋고, 그네들의 프레임웍
설계 방식을 좋아하기 때문이기도 합니다. 애플이 주는 답답함에 애증이 교차하기도 합니다만...
어떤 것이든 시작은 어렵습니다. 그러나, 대충 아~ 이렇구나라고 느끼는 순간을 넘으면, 그 이후엔 거침없이 나아갈 수
있다고 봅니다. 강좌란에 올리기 부끄럽습니다만, 처음 시작하시는 분들에게 그런 글이 되었으면 하는 바램입니다.
예전의 기억과 더듬더듬 읽은 문서를 바탕으로 작성한 글이니 조금 틀리고 설명이 빈약하여 어렵게 느껴지지 않을까하는
우려가 있습니다만 가볍게 시작하겠습니다. 그러니 가볍게 읽어 주시고, 틀린 부분은 알아서들 필터하세요~
참고로 Objective C를 만든 분은 Brad Cox라는 분입니다. (설마 잡스가 만들었다고 생각하시는 분들은 없겠죠? ㅋㅋ)
http://www.virtualschool.edu/cox/
* 문서 이력 *
v1 : 최초 작성
v2 : 문구 수정, 오타수정, 메모리관련 추가, 문서이력 추가
* 우선 코딩 기본 규칙 *
1. 클래스는 대문자로 시작합니다.
2. 인스턴스 변수는 소문자로 시작합니다. private할 경우 _xxx, __xxx 허용합니다.
3. 클래스 메소드이건 인스턴스 메소드이건 상관없이 소문자로 시작합니다.
4. 캐멀 표기법 따릅니다.
어디 정의된 것은 아닙니다만, 제가 이제까지 보면서 느낀 최소한의 규칙입니다.
나쁜 코딩습관이 에러를 만든다는 것은 조엘 아저씨의 주옥같은 명언집을 언급하지 않더라도 아실겁니다.
1. 최상위 클래스
NSObject입니다. NSProxy도 있지만 우선 무시하세요.
자신만의 클래스를 만들 때는 최소한 NSObject에서 상속을 받아야 합니다. (무조건 그렇게 하세요.)
2. 상속, 클래스변수, 연산자 오버로딩
다중상속을 지원하지 않습니다.
클래스 변수를 지원하지 않습니다.
연산자 오버로딩을 지원하지 않습니다.
아규먼트 오버로딩도 지원하지 않습니다.
3. id, self, super, nil, @""
id는 객체를 가르키는 포인터입니다. void *와 같습니다.
self는 자신을 가르키는 포인터입니다. java의 this와 같습니다.
super는 현 객체가 상속한 상위 객체입니다. java의 super와 같습니다.
nil은 객체가 없음을 나타내는 객체 포인터입니다. NULL은 C의 그것입니다.
c style의 스트링은 "하하" 처럼 표기하고, 유니코드 스트링은 @"하하" 처럼 표기합니다.
* 참고로 메소드의 기본 리턴 타입은 id입니다. 아래에서 보겠습니다만,
- (id) sayHello;
- sayHello;
위 둘은 같습니다. 즉, 명시하지 않으면 기본으로 객체타입을 리턴하는 것으로 간주합니다. 요즘은 첫번째를 많이
사용하더군요.
4. 객체 생성
MyObject *className = [[MyObject alloc] init];
alloc은 클래스 메소드입니다. init은 인스턴스 메소드입니다.
alloc은 메모리를 준비합니다. init은 인스턴스 변수를 초기화합니다.
init은 기본 초기화 메소드입니다. 이런 초기화 메소드를 만들 때는 반드시 initXXX로 만드세요. (NSObject 참고)
만약 다른 객체를 생성해서 리턴하는 것이라면 가능한 createXXX로 만드세요.
5. 메세지 보내기
오브젝티브씨는 "["로 시작하고, "]"로 닫고, 문장의 끝은 ";"로 마무리를 합니다.
또한 표현을 '객체에 메세지를 보낸다'고 합니다. 함수호출 이런 말 제발 안봤으면 합니다.
[myObject draw];
6. 메세지 중첩하여 보내기
어떤 메소드의 결과값(리턴값)이 객체일 경우 메세지를 중첩해서 보낼 수 있습니다.
즉, myObject 에 draw라는 메세지를 보냈을 경우, 그 리턴값이 myObject라면
[[myObject draw] transform];
처럼 보낼 수 있습니다. 3중, 4중으로 보내도 됩니다...
위에 것은
[myObject draw];
[myObject transform];
과 같습니다. 처음엔 생소하겠지만, 자주 보면 익숙해집니다.
7. 메세지 인자
메세지 인자는 ":"로 구별합니다. 또 ":"앞에 좀 더 분명하게 하기 위해 메세지 구별자를 추가할 수도 있습니다.
[myObject drawWithX:pointX withY:pointY];
[myObject drawWithX:pointX :pointY]; //위의 것보다는 pointY가 무엇을 의미하는지 알기 힘들죠.
[self show]; // 자기 자신에게 보내는 메세지입니다.
8. import
헤더를 인클루드 할 때는 C의 include보다는 import를 사용하세요. 이중으로 include되더라도 허용케 합니다.
물론 C쪽 헤더는 include도 사용합니다.
9. 클래스 선언
*. "클래스이름.h" 로 파일을 만듭니다. (클래스 이름과 달라도 상관은 없습니다. 그러나 구별을 어렵게 합니다.)
*. Foundation이나 UIKit을 임포트하세요. 모르겠다면 UIKit을 임포트하세요.
*. "@interface 클래스네임 : 상위클래스" 로 시작하세요.
*. 그 아래 { } 사이에 인스턴스 변수를 선언하세요.
*. "+ (리턴형) 클래스 메소드;" 로 클래스 메소드를 선언하세요.
*. "- (리턴형) 인스턴스 메소드;" 로 인스턴스 메소드를 선언하세요.
*. "@end"로 닫습니다.
#import <Foundation/Foundation.h> // 또는 #import <UIKit/UIKit.h>
@interface MyName : NSObject
{
NSString *name;
}
+ (MyName*) newInstance; //또는 +(id) newInstance;
- (id) initWithName:(NSString *)name;
@end
10. 클래스 구현
*. "클래스이름.m" 로 파일을 만듭니다.
*. "#import 클래스이름.h" 로 클래스 선언을 임포트합니다.
*. "@implementation MyName"으로 구현을 시작합니다.
*. "+ (리턴형) 클래스메소드 {...}" 에서 구현합니다.
*. "- (리턴형) 인스턴스메소드 {...}" 에서 구현합니다.
#import "MyName.h"
@implementation MyName
+ (MyName*) newInstance
{
// 여기에 구현을 합니다. 아래는 기본 패턴입니다.
return [[MyName alloc] initWithName:nil] autorelease];
}
- (id) initWithName:(NSString *)name
{
// 여기에 구현을 합니다. 아래는 기본패턴입니다.
if (self = [super init]) {
}
return self;
}
@end
여기까지가 기본적인 사항입니다. 어렵나요? 약간 낮설다는 기분이지 이해하고 그럴 만한 그런게 없을 겁니다.
그럼 조금은 이해가 필요한 것들에 대해 이야기해보겠습니다.
11. 객체의 메모리 관리; alloc/dealloc/release/autorelease
메모리 관리는 좀 복잡합니다. '생성한자 또는 명시적으로 소유하겠다고 선언한 자가 소거의 책임이 있다.' 라고
정의하면 될 듯 합니다. 이것을 OWNERSHIP이이라고 합니다. 가비지 콜렉션을 지원하지 않고, 개발자가 관리하는
구조이며, retainCount를 사용하는 메커니즘입니다. retainCount가 0이면 소거될 수 있습니다.
(바로 소거되지 않을 수도 있습니다. 이런 메모리 관리 기법은 인터넷을 찾아보시고...)
객체의 생성은 alloc, copy가 있습니다. 어떤 객체를 생성/복사하지 않고 소유하고자 할 경우 retain이란 메세지를
보냅니다. alloc, copy, retain 메세지를 보내면 리테인카운트는 1씩 증가합니다. 그 반대는 release 메소드입니다.
리테인카운트를 1씩 감소합니다. alloc, copy, retain시 소유권이 생긴다는 것을 반드시 기억하세요!!!
어떤 객체가 가진 인스턴스 변수를 소거하고자 할 경우엔 - (void)dealloc; 메소드에서 처리하면 됩니다. 반드시
끝 날 때는 [super dealloc]; 을 해줘야 합니다.
- (void)dealloc {
[myName release];
[super dealloc];
}
autorelease는 릴리즈풀에 넣어두고 소유자가 없을 경우나 풀이 소거될때 소거되도록 합니다. 프로그램을 하다보면
소유자가 불분명해지는 경우가 있습니다. 이 말은 소거를 누가할지 결정하기 힘들다는 말과 같습니다. 그럴 때
많이 사용합니다. 클래스메소드에서 객체를 생성해 리턴해줄 경우를 생각해보죠. 리턴을 하게되면 이 객체는 누가
소유 할까요? 네, 아주 잠시 아무도 소유할 수 없는 상황이 발생합니다. (아~ 이해안되더라도 그냥 그렇다고 넘어가시길...)
이때 소거를 잠시 미루도록 autorelease를 해서 릴리즈풀에 담아둡니다. (릴리즈풀은 애플 문서를 참고하시고, 우선 스킵합니다.)
몇 가지 예제를 보면서 맛이라도 한번 보겠습니다.
- (void) memoryTest {
// 인스턴스 변수일 경우
myInstance1 = [[NSString stringWithString:@"Hangul"] retain]; // autorelease된 객체
myInstance2 = [[NSString alloc] initWithString:@"Hangule"];
// 일반 변수일 경우
NSString *str1 = [NSString stringWithString:@"Hangule"];
NSString *str2 = [[NSString alloc] initWithString:@"Hangule"];
NSString *str3 = [NSString stringWithString:@"Test"];
[str2 release];
}
myInstance1에 autorelease된 객체입니다. stringWithString:으로 생성된 객체는 메소드가 끝나면 소거됩니다.
따라서 retain을 해서 소거를 막고, 소유를 합니다.
myInstance2는 alloc된 객체입니다. 리테인카운트 1이고 인스턴스 변수이므로 객체가 살아있는 동안 남아 있어야 하므로
release를 하면 안되겠죠.
str1은 autorelease된 객체입니다. 메소드가 끝나면 소거됩니다. (바로 안될 수도 있음)
str2는 alloc된 객체입니다. 리테인 카운트 1이고, 메소드가 끝나도 소거되기 위해선 반드시 release를 해줘야 합니다.
myInstance1에 str3을 어사인하고자할 경우엔 어떻게 하는지 보겠습니다.
[myInstance1 release]; //릴리즈 카운트 감소. 더이상 own할 필요가 없으니 이제 해방시켜줍니다. 안하면 메로리릭!
myInstance1 = [str3 retain]; // str3는 지역변수이므로 메소드가 끝나면 사라집니다. 리테인해서 소거를 막습니다.
여기서 myInstance1 = str3; 하는 것과 myInstance1 = [str3 retain]; 하는 것은 엄청난 차이가 있습니다.
앞의 것은 포인터가 같아지는 것이고, 뒤의 것은 소유(ownership)하는 것이기 때문입니다.
또 다른 예를 하나 더 보겠습니다. NSArray란 것은 객체 어레이를 담는 콜렉션중 하나입니다. NSMutableArray는 가감을
할 수 있고, NSArray는 생성된 시점이후에 가감을 할 수 없습니다. Mutable이 붙은 클래스는 동일한 룰이 적용됩니다.
- (void) someMethod {
NSMutableArray *mArray = [NSMutableArray array];
NSString *str = [NSString stringWithString:@"Hangul"];
[mArray addObject:str];
[mInstanceArray release]; // 이전 인스턴스변수는 새로운 값을 소유하기 전에 반드시 릴리즈시켜야 합니다.
myInstanceArray = [mArray retain];
}
이 경우 메소드가 끝나면 str은 없어지겠죠? 그럼 mArray에는 str이 어떻게 될까요?
네... 바로 ownership을 생각해야 합니다. mArray는 str을 담고 있어야 하므로 addObject:할때 str을 retain하게
됩니다. 따라서 메소드가 끝나도 str은 없어지지 않습니다. 즉, 자기가 쓰겠다고 했으니 retain을 하는 겁니다.
따라서 mArray가 소거될때, 또는 str이 제거될때 release가 불리는 것을 생각할 수 있을 겁니다. 그럼 retain해서
1증가하고, release해서 1 감소하니 결국 소거가 될 수 있는 것입니다. 어렵다구요? 만약에 자신이 NSArray를
만든다고 생각을 해보세요. 그러면, add한 객체를 어떻게할지... 또 내가 만든 클래스에서는 어떻게 객체를 소유할지...
바로 알아챘을 거라 봅니다.
마지막으로 하나만 더 들어보겠습니다. (초보단계는 아니지만, 개념을 확실히 이해하시라고 추가합니다.)
여기 MutableArray *myMutableArray가 있습니다. 첫번째 것을 꺼내 마지막으로 넣고자 할때 어떻게 해야할지
생각해보겠습니다.
- (void)moveToLast {
id firstObject = [myMutableArray objectAtIndex:0]; // 객체 포인터 얻기
[firstObject retain]; // 메모리에서 소거되지 않도록 소유하고, (+1)
[myMutableArray removeObjectAtIndex:0]; // 어레이에서 제거한다. 이때 어레이는 제거되는 객체를 release한다. (-1)
[myMutableArray addObject:firstObject]; // 어레이 마지막에 추가한다. 이때 어레이는 객체를 retain한다. (+1)
[firstObject release]; // 이제 필요가 없으니 소유권을 버립니다. (-1)
}
원리는 똑~ 같습니다. 즉, 사용할 것이면 리테인을 하라는 겁니다!
즉, 단순히 제거할 것이 아니라 다시 사용할 것이므로 retain을 해서 잠시 소유해야 합니다.
그리고 소유할 필요가 없으면 release를 합니다.
만약 [firstObject retain]; 을 하지 않았다면 위 코드가 문제없이 수행될 수 있을까요?
정답은 그럴 수도 있고 아닐 수도 있다입니다.
id firstObject = [myMutableArray objectAtIndex:0];
[myMutableArray removeObjectAtIndex:0];
[myMutableArray addObject:firstObject];
즉, 이렇게 해도 문제가 없을 수도 있습니다. 어떤 상황에서 그럴까요?
네~ 그렇습니다. firstObject 객체를 누군가 소유하고(즉, 리테인카운트가 2이상) 있다면 removeObject를 할때
release를 보내더라도 소거가 되지 않겠죠. 그래서 이 코드는 상황에 따라 제대로 돌았다가 안 돌았다가 하는 알 수
없는 미치고 팔짝뛰는 상황이 만들어 집니다. 또 release를 하더라도 바로 소거되지 않을 수도 있기 때문에, 설령 다른 객체가
소유를 하지 않더라도 왔다갔다 하는 상황이 나올 수도 있습니다. (이쯤되면 개발자는 온 게시판에 도배질을 시작합니다… ㅋㅋ)
이런 상황은 소유개념에 대한 이해가 없으면(또, 살짝 주의를 게을리하면) 충분히 발생할 수 있습니다.
이것이 리테인카운트 메커니즘을 사용하는 메모리 관리시스템의 약점 중 하나입니다.
어느 곳 하나 삐긋하면 메모리에 구멍이 나고, 매직스런 상황이 연출된다는...
암튼, 소유(retain, ownership)라는 말을 수도 없이 지긋지긋하게 했으니, 이제 중요한 포인트가 뭔진 아시겠죠?
그래도, 메모리 관련해서는 반드시 애플의 문서를 읽기를 권합니다.
참고로, 원래 오브젝티브씨란 언어에 이런 개념이 있던게 아니랍니다. OPENSTEP이후부터 이런 개념이 추가되었습니다.
그게 없던 시절(NeXTSTEP) 오브젝티브씨는 C와 똑같이 malloc/free를 사용했습니다.
12. SEL 타입, @selector()
오브젝티브씨에서 메소드 포인터를 SEL이라는 타입을 쓴다고 이해하면 됩니다.
그 SEL 타입을 만들 때, @selector()라는 것을 사용합니다.
SEL method = @selector(drawWithX:pointX);
SEL은 어떤 객체가 불리워질지를 정의하지 않습니다. 요건 오브젝티브씨의 런타임을 보면 이해할 수 있는데, 우선은
어떤 객체에 메소드를 어떻게 부르는지만 살펴보겠습니다.
[myObject performSelector:@selector(drawWithX)]; // 인자가 없을 경우
[myObject performSelector:@selector(drawWithX:) withObject:floatObject]; //인자는 반드시 객체여야 함
[myObject performSelector:@selector(drawWithX:) withObject:floatObject afterDelay:0.1f]; // 0.1sec 뒤에 실행
마지막 것은 예전에 없던건데 추가된 겁니다. 폰의 특성상 약간 늦게 시작해야 빠른 듯 느껴지는 경우가 있습니다.
그럴때 유용할 수 있습니다.
만약 myObject에 해당 메소드가 없으면? 에러입니다. 그럼 어떤 메소드를 받을 수 있는지는 어떻게 알아내냐면
- (BOOL)responseToSelector:(SEL)aSelector; 를 사용합니다. 아래 delegate를 한번 보세요.
13. delegate, dataSource
이건 패턴의 하나인데, 구태여 소개하는 것은 아예 delegate라고 못 밖아서 인스턴스 이름으로 쓰기 떄문입니다.
말 그대로 대행자입니다. 내용은 간단합니다. 어떤 객체가 어떤 일을 하고자 할 때나 끝났을 때 대행자를 가지고 있을
경우 그 대행자가 수행할 수 있도록 메세지를 보내는 것입니다. (메소드 내에 Will,Did,Should가 들어가 있다면
대부분 딜리게이트 메소드입니다.) 내부적인 구현 방법을 보면 이해하기 쉽습니다.
if ( delegate && [delegate responseToSelector:@selector(doOther:) )
[delegate performSelector:@selector(doOther:)]; //performSelector 메소드가 SEL을 호출합니다.
즉, 객체의 변수로 delegate라는 객체가 있고, -doOther라는 메소드를 받을 수 있으면 메세지를 보내는 식입니다.
비슷한 것으로 dataSource도 있습니다. 마찬가지로 패턴의 하나인데, 직접적으로 dataSource라고 사용하고 방법도
delegate와 같습니다. 테이블, 피커등에 데이터를 전달할 경우 많이 사용합니다.
delegate, dataSource의 개념에 해당하는 인스턴스 변수를 어떻게 짓던 상관은 없지만, 가급적 맞추는 것이 좋습니다.
그래야 남들도 이해하기 쉽습니다.
또 하나, delegate나 dataSource를 지정할 때는 그 객체를 retain하지 않는 것이 보편적입니다.
단순하게 delegate = otherObject; 처럼 합니다. 이를 weak reference한다고 하고 소멸의 책임이 없습니다.
그러나, delegate = [otherObject retain]; 이렇게 하면 소멸의 책임을 집니다.
즉, -dealloc 에서 [delegate release];를 해줘야 합니다.
14. category
카테고리는 메소드의 묶음입니다. 위에서 *.h에 메소드를 정의하는 방법을 배웠습니다. 그것이 기본 카테고리입니다.
즉, 오브젝티브씨는 메소드를 묶음 단위로 표기할 수 있고, 또한 기존의 클래스에 묶음으로 메소드를 더할 수도 있습니다.
여기서 기존의 클래스에 메소드를 더할 수 있다는 것이 중요합니다. 예들들어 보겠습니다.
NSString이란 클래스는 스트링을 다루는 기본 클래스입니다. 이 클래스에 한글이 있는지 없는지를 리턴하는 메소드를
추가하고 싶을 경우 대부분은 서브클래싱을 생각하게됩니다. 그러나, 오브젝티브씨는 서브클래싱없이 메소드를 더합니다.
방법은 클래스의 생성과 매우 유사합니다. 다만, 수퍼클래스 이름 대신 메소드 묶음 이름(카테고리 이름)을 "(...)"
안에 적습니다.
@interface NSString (MyNSStringAddition)
- (BOOL)hasHangul;
@end
이렇게 선언하고, 구현은 interface대신 implementation을 사용하면 됩니다.
그리고 사용은 아래처럼 합니다.
NSString *myString = [[NSString alloc] initWith:@"Hangul 한글"];
BOOL hasHangul = [myString hasHangul];
자바스크립트에서 prototype으로 기존 클래스에 메소드를 추가하는 것을 본적 있는 분들은 이해가 빠를겁니다.
두개의 룰이 필요합니다. 기존의 메소드 이름과 겹치지 말아야 한다는 것과 카테고리명이 유일해야 한다는 것이 바로
그것입니다. 메소드의 묶음이기 때문에 인스턴스 변수를 가지는 것이 아닙니다. 따라서 별도의 인스턴스가 추가되는
상황이 아니라면 서브클래싱 하지 않고 메소드만 추가하는 카테고리가 훨씬 합당합니다. 또한 메소드를 구조적으로 묶어
주므로써 코드 관리를 용이하게 하는 장점도 있습니다.
카테고리는 다른 파일로(별도 헤더파일과 구현파일) 만들어 사용하는 경우가 많고, 구태여 헤더파일에 넣어 API를
공개시키지 않고 싶을 경우 (즉, 프라이빗하게 사용하고자 할 경우), *.m파일에서 정의하여 사용하기도 합니다.
또 예들 든것처럼 기존 클래스에 메소드가 없는 경우에도 사용합니다. (기존 클래스의 메소드를 완전히 변경할 수도
아예 클래스를 변경할 수도 있습니다. 메소드스위즐링, 포우져라고 하는데 넘어가도 좋습니다.)
15. protocol
프로코콜은 주고 받을 수 있는 메세지를 규약하기 위한 것입니다. 자바의 인터페이스와 흡사합니다.
프로코콜을 정의하는 방법은 다음과 같습니다.
@protocol 프로코콜이름 <적용대상 클래스 이름>
예제를 볼까요?
@protocol DoYouLikeMe <NSObject>
@required //이하 메소드는 반드시 구현해야 하는 메소드입니다.
- (BOOL)canAccept:(NSString *)otherName;
@optional //꼭 할 필요는 없습니다.
- (BOOL)doYouLikeIt:(NSString *)boysName;
@end
어떤 클래스가 프로토콜을 구현을 하고 있다고 컴파일러에게 알리고자 할 경우엔,
@interface MyName : NSObject <프로코콜이름, 프로토콜이름,...>
이렇게 합니다. dataSource 패턴에 적절하겠죠?
그럼 어떻게 객체가 프로코콜을 정의하고 있는지 알 수 있을까요? 역시 NSObject에서
- (BOOL)conformsToProtocol:(Protocol *)aProtocol; 를 사용하면 됩니다. 아래처럼 사용합니다.
if ([receiver conformsToProtocol:@protocol(MyXMLSupport)]) {
//your code here!
}
또 어떤 객체가 프로코콜을 사용하고 있다는 것을 알려주고자 할 경우,
MyObject <프로코콜이름> *myObject;
처럼 사용합니다.
informal, formal protocol이란 이야기도 보게 될겁니다. 위에 DoYouLikeMe같이 정의하는 것을 formal이라하고
NSObject에 카테고리처럼 정의하는 것을 informal이라고 합니다. formal은 컴파일러가 체크를 할 수 있지만, informal은
컴파일러가 체크를 할 수 없습니다. informal은 delegate methods를 구성할 경우 많이 사용합니다. 아래를 보면
카테고리와 흡사하게 정의됨을 볼 수 있을 겁니다. 대부분 NSObject에 바인딩되고, 구현하지 않는다는 점에서 다릅니다.
(프로코콜이란 것은 적용하는 객체가 구현하는 것이니 당연한 이야기겠죠.)
@interface NSObject (MyProtocol)
- (id)makeGirlfriend:(NSString*)name;
- (NSArray *)girlsLikeMe;
@end
informal 프로토콜은 카테고리와 약간 구별하기 애매할 겁니다. 구현을 하느냐 안하느냐로 우선 구분하세요.
뭐 처음부터 이해하려고 하지 않아도 됩니다. (그래서 설명을 빼려고 했습니다만,...)
16. get/set 그리고 property
오브젝티브씨에서는 get/set에 대한 규정이 자바처럼 엄격하질 않습니다. 대부분 setXXX는 쓰지만, getXXX 보다는
xXX로 씁니다. 이런 get/set을 보다 쉽게 만든 것이 바로 Objective C 2.0에 포함된 property이고, "." 로 객체의
변수에 접근할 수 있게 합니다. 사용방법은 간단합니다.
헤더 파일엔 다음처럼 선언합니다.
@property (nonatomic, retain) NSString *name; //객체를 쓰레드 세이프하지 않게하고, retain을 하라는 뜻
@property (nonatomic, readonly) NSString *myGirl; // get만 할 수 있다는 뜻.
nonatomic은 스레드로 접근할 때 락을 하는냐 마느냐입니다. 폰에서는 대부분 퍼퍼먼스의 이유로 nonatomic으로
사용하라고 합니다.
구현파일(*.m) 파일엔 @implementation 아래에
@synthesize name, myGirl;
처럼 하면 자동적으로 get/set 이 구현됩니다. (컴파일 타임에 생성된다는 의미)
사용은...
myObject.name = @"hehehe";
NSString *herName = myObject.myGirl;
처럼 "." 표현을 사용합니다. 코드량을 많이 줄여주고, 잘못 사용하면 메모리 릭이 날 수 있습니다. 따라서 애플 문서를
반드시 참고하세요.
17. private, protected, public...
@private, @protected, @public 으로 표기하고 그 이하의 변수가 포함됩니다.
다만, 메소드에는 이런 스코프 제한이 없습니다. 즉, 헤더에 보여지는 것은 모두 public입니다.
private한 경우 "_"를 붙여 개발자 상호간에 이해하는 컨벤션을 쓴다던가, 또는 *.m파일에 카테고리를 사용해서
헤더에 노출을 시키지 않는 방법을 씁니다. (물론 클래스 덤프하면 다 보입니다.)
더불어 클래스 변수도 없습니다.
*.m파일내의 @implementation 안에서 static으로 선언해 쓰거나 해야 합니다. 상속받은 객체에서 보기를 원한다면
extern 선언을 해야 합니다.
18. @class, @protocol
예제코드를 보다보면 헤더에 "@class XXXX;"식의 코드를 볼 수 있습니다. 이것은 XXX라는 클래스가 있다는 것을
컴파일러에게 알려주는 역할을 합니다. 마찬가지로 @protocol도 같은 역할을 합니다. 물론 헤더파일에 해당 클래스를
임포트시켜도 됩니다. @class는 컴파일러나 링커가 임포트보다는 좀 더 쉽게 처리할 수 있게 한다라는 정도만 알면
되겠습니다.
이 정도면 기본 문법은 대충 다 본 셈인듯합니다.
마지막으로 몇 가지 아이폰을 개발하면서 알아두면 좋은 것들을 이야기해보고자 합니다.
19. 디자인 패턴
첫번째로 MVC 모델입니다. 관련된 내용은 인터넷에 수 많은 글들이 있으니 생략하겠습니다.
두번째는 target/action 패러다임을 사용합니다. (아래 22. 참고)
세번째는 살펴본바와 같이 delegate, dataSource 패턴을 사용합니다.
네번째는 노티피케이션입니다. 어떤 일이 일어난 것에 관심이 있을 경우 노티피케이션센터에 관심이 있다고 등록을 합니다.
어떤 객체가 노티피케이션을 보내면 센터에 등록된 모든 객체가 반응할 수 있게 합니다. 일종의 메세지 브로드캐스팅
이라고 보시면 됩니다.
다섯번째는 late loading입니다. 불필요한 객체나 리소스를 처음부터 생성하지 말고, 가능하면 필요할 때 생성/로드
하라는 것입니다. 여기서 필요를 결정하는 것은 개발자입니다. 즉, 반드시가 아닙니다.
이상이 아이폰 개발에서 염두해두실 만한 고전적인(?) 주요 패턴으로 보시면 됩니다.
20. 프레임웍 구조
오브젝티브씨의 기본 프레임웍은 Foundation이라고 합니다. 문자, 문자셋, 어레이, 집합, 딕셔너리, 넘버, 날짜,
로케일등등이 모두 여기에 포함되어 있습니다. 화면과 관련된 것은 아이폰에서는 UIKit이라고 하고, OS X에서는 AppKit
이라고 합니다. 물론 스트링은 유니코드를 기반으로 합니다. 한글 인코딩은 EUC-KR(5601-1987), MS949, KSC5601_87,
KSC5601_92_Johab, Mac Korean(EUC-KR과 같으나 CRLF가 다름) 등을 지원합니다.
참고로 Objective C의 프레임웍/런타임을 통칭해서 Cocoa라고 부릅니다. NeXT사가 애플에 합병되면서 가져온
프레임웍이고 OPENSTEP이란 스펙에 따라 만들어졌습니다. Mac 고유의 API는 수정되어 Carbon이라고 불립니다.
CFxxx 이렇게 시작하는 것들이 그것들이라고 보시면 됩니다. (Carbon을 이런 함수들로만 보는 것은 무리가 있습니다만,
우선은 그렇다라고 생각해주세요.)
21. firstResponder, File's owner
이것은 언어적인 특성이라기 보다는 MVC를 구성하면서 파생된 개념으로 보시면 됩니다.
firstResponder은 OS X에서는 첫번째 키보드나 마우스 이벤트를 받는 객체입니다. 아이폰에서는 키보드나 마우스란
것이 없으니 터치만 해당됩니다. 반드시 그런 것은 아니지만, 우선 아이폰에서는 화면상에서 키보드가 나타나고 커서가
보이는 것이 바로 firstResponder 객체라고 보시면 됩니다.
예를 하나 들어보겠습니다. 만약 메뉴에서 copy&paste를 구현한다고 할때, -copy; -passte; 메소드를 보내야할 겁니다.
자~ 문제입니다. 어떤 객체에 보내야 효율적인 코딩이 될까요? 네 맞습니다. 바로 firstResponder에 보내는 겁니다.
File's owner는 인터페이스빌더(약칭, IB)에서 사용하는 개념입니다. 코코아에서는 MVC중 V와 C를 냉동보관했다가
어플리케이션이 필요할 때 녹여서 메모리에 올리는 기법을 사용합니다. 물론 코드만 가지고 어플리케이션을 짜도
되지만, 툴을 사용하면 코드량을 획기적으로 줄일 수 있다는 장점이 있습니다. 대략 UI를 잡은 다음에 코드로 세밀하게
다듬는 식의 방법을 많이 사용합니다. (OS X에서 애플의 어플리케이션을 보면 이런 방법을 많이 사용합니다.)
그 냉동된 파일을 NIB(NeXT Interface Builder) 이라고 하는데 요즘은 XIB(확장자 *.xib)라고도 합니다. IB를
가지고 화면을 꾸미고 컨트롤과 연결한 후 실제 프로그램에선 NIB를 로딩해야합니다. 이 때 그 NIB의 소유자를
File's owner라고 합니다. 대부분 MVC중 C에 해당하는 것들이 File's owner가 됩니다. 즉, 컨트롤과 뷰간에 객체를
인식(outlet)하고 메세지를 보내고자 할 경우, 컨트롤 객체가 생성이 되어 있어야 할 것입니다. 바로 그것이
File's Owner가 됩니다.
22. outlet, target, action, connection
오브젝티브씨는 이벤트 드리븐이 아니라 메소드(메세지) 드리븐이라는 말을 합니다. 어떤 객체에 이벤트가 발생했을 때
타 객체에 메세지를 보내는 방식으로 일을 처리합니다. 처리할 이벤트를 등록해서 처리하는 방법이 아닙니다.
NIB파일은 위에서 설명한 대로 MVC중 V와 C를 녹여(시리얼라이즈) 해 놓았는데, 컨트롤이 화면의 객체를 알아야 할 필요가 있습니다.
그래야 메세지를 보내 속성을 바꾸거나, 텍스트를 뿌리거나, 슬라이드를 이동하거나 사이즈를 줄이거나 할 수 있을 겁니다.
IB에서는 이를 outlet이라고 합니다. 또한 컨트롤(버튼,슬라이더등)이 움직였을 때 컨트롤이 반응할 수 도 있어야 합니다.
이처럼 메세지를 받는 쪽을 target이라고 하고 보내는 메세지를 action이라고 합니다. 또 IB에서 outlet을 정하거나,
메세지를 보내는 것을 커넥션을 맺는다고 합니다.(control+drag해서)
한참 안봐서 모르겠습니다만, OS X에서는 action메소드는 항상 "- (IBAction)actionMethod:(id)sender;"처럼
정의합니다. iPhone에서는 "- (IBAction)actionMethod;"처럼 정의해도 인터페이스빌더가 알아챕니다.
IBAction은 void 형이고, 인터페이스빌더가 액션메소드임을 알아내기 위한 예약어라고 보시면 됩니다.
IBOutlet은 인터페이스 빌더가 outlet을 구별하기 위해 사용하는 예약어라고 보면 됩니다.
자료출처 : 맥부기 카페([완천초급] 오브젝티브씨 시작하기...)
http://cafe.naver.com/mcbugi/29965