클래스 매서드 & 인스턴스 매서드
Object 클래스는 Obect 클래스를 포함하여 그것을 상속하는 클래스의 인스턴스를 제대로 생성 alloc 클래스 메서드를 정의한다.
일반적인 인스턴스 매서드는 매서드를 호출하는 인스턴스가 필요하지만 클래스 매서드는 인스턴스 없이도 실행될 수 있다.
따라서 alloc 메소드는 인스턴스가 존재하지 않더라도 호출에 문제가 없다.
alloc 메서드는 인스턴스를 생성하기 위한 클래스 메서드 이므로 팩토리 메소드라고도 한다.
Self
암시적 self
메서드 범위 내에서는 임시 인수 및 선언된 변수 이외에 절대적인 변수로 self 를 정의한다.
self 변수는 id 형식. 항상 메서드를 호출 인스턴스를 참조한다.
즉, 메서드를 실행하는 개체 자신을 나타내는 변수가 self 이다.
self 는 메소드 이외의 장소에서는 사용하지 않는다. 실행 코드 부분에서만 암시 값이 존재한다.
self 를 사용하여 개체의 인스턴스 변수 개체에서 볼 수 있다.
이것은 메서드의 정식 인수 변수 이름 인스턴스 변수 이름을 은폐할 때 등에 사용할 수 있다.
Property 와 Synthesize
Property 와 Synthsize 를 이야기 하면 먼저 MVC 를 이해해야 한다.
/*
* Model-View-Controller (MVC)
- 아이폰 애플리케이션은 객체지향 프로그램의 디자인 모델인 MVC 디자인 패턴을 기본으로 한다.
Model : 애플리케이션 데이터를 저장하고, 이 데이터를 조작하는 로직을 정의한다.
View : 사용자 인터페이스(윈도우나 버튼)에서 볼 수 있는 객체들의 집합으로 정의한다.
Controller : 위의 모델과 뷰를 조율하는 역할을 한다.
*/
이들 사이에 밀접한 연관이 있기 때문이다. 실제로 Property 는 MVC 의 Model 에 해당한다.
Property 는 어플리케이션에 필요한 데이터를 신뢰할 수 있고 안전하게 보존하는 데 필요한 룰을 제공한다.
Synthesize 는 Property 에서 선언한 데이터들을 외부에서 합법적으로 접근할 수 있는 통로를 편리하게 제공한다.
우선 다음의 예제 코드를 보자
//Model.h
#import<Cocoa/Cocoa.h>
@interface Model : NSObject {
NSString *data;
}
@property (copy, readwrite) NSString *data;
@end
//Model.m
#import "Model.h"
@implementation Model
@synthesize data;
@end
//부연 설명 시작
일반적으로 MVC 개발에서도 setter/getter 로 구성된 도메인 객체를 따로 두고 있듯이
Objective-C 에서도 역시 비슷한 구현을 하고 있다.
Objective-C 에서 일반적으로 객체의 Property 에 접근하기 위해서는 한 쌍의 접근자 메서드
(getter/setter) 를 사용한다.
이 메서드들을 사용함으로써 객체 지향 프로그래밍의 캡슐화(Encapsulation)에 더욱 충실 할 수 있다.
Property 를 정의함으로써 효과적으로 접근자 메서드들을 간략화 시키는 효과를 가져올 수 있다.
Property 를 사용하기 위해서 일반적으로 @property 지시자와 @synthesize 지시자를 함께 사용한다.
@property 지시자는 클래스의 @interface 내부에 선언하며 다음과 같은 형식을 지닌다.
@property (attributes) type name;
하나의 Property 선언은 두 개의 접근자 메서드와 동일한 기능을 갖는다.
@property float value;
위의 선언은 다음과 같이 두 가지 메서드를 선언한 것과 같은 기능을 하게 된다.
- (float) value;
- (void) setValue:(float)newValue;
@property 지시자의 attributes 에는 다음과 같은 정의를 할 수 있다.
getter = gettername
기본적으로 Property 의 getter 메서드 명은 Property 자신의 이름과 동일 하지만 (예 : Property 가 foo 일 경우 foo)
이 기본 설정을 내가 원하는 메서드 명으로 변경 할 수 있다.
setter = settername
Property 의 setter 메서드 명은 setPropertyName: 이다. (예 Property 가 foo 일 경우 setFoo)
역시나 이 기본 설정을 내가 원하는 메서드 명으로 변경 할 수 있다.
readwrite (DEFAULT)
Property 의 값을 읽고 쓸 수 있다. 기본 설정으로 되어 있다.
readonly
Property 의 값을 단지 읽기만 할 수 있다고 정의하는 속성.
프로퍼티가 변경되지 않도록 할 때 사용.
이 속성은 @implementation 블럭 안에서 오로지 getter 메서드만 필요할 경우 사용한다.
@synthesize 지시자를 사용하였을 경우에도 역시 getter 메서드의 역할만을 하게 된다.
값을 대입하려고 할 경우 에러를 출력하게 된다.
필드의 값을 바꾸는 메소드를 사용자가 직접 만들 수 있기는 하지만,
컴파일러가 세터를 자동으로 생성해 주지는 않는다.
assign(DEFAULT)
단순하게 값을 대입한다. 기본설정이다.
이전에 어떤 객체를 가리키고 있던 Property 라면 이로 인해 해당 객체는 미아가 되어 메로리릭의 주범이 될 수 있다.
가비지 콜렉터를 사용하지 않는다면 사용을 피해야 한다.
int 나 float 와 같은 기본형을 다룰 때 사용한다.
컴파일러는 세터를 단순히 "myField = value" 와 같은 단순 할당문으로 만든다. 이것은 기본값이다.
그러나 대부분 객체를 다룰 때에는 메모리 관리 측면에서 적절하지 않다.
retain
이것은 assign 과 비슷하지만 조금 다르다.
이전에 가리키고 있던 객체가 있다면 해당 객체를 Release 하여 메모리에서 제거해 준다.
가비지 콜렉터를 사용한다면 결과적으로 assign 과 동일한 결과를 가지겠지만
좀 더 명시적으로 사용해 주면 좋다.
객첵의 값을 다룰 때 주로 사용.
컴파일러는 입력값으로 쓰이는 객체를 리테인하고, 이전에 있던 객체를 릴리스 한다.
copy
객체를 바로 대입하지 않고 해당 객체의 복사 메서드를 Invoke 호출한다.
//Invoke : 간단히는 함수 호출.
다른 스레드에 있는 컨트롤을 안전하게 호출하기 위해 쓴다.
그리하여 다른 메모리 영역에 복사본을 만든 다음 그것을 반환하게 된다.
이전에 가리키고 있던 값은 Release 시킨다.
전달된 원래의 값이 변경되지 않도록 할 때 사용한다.
예를 들어 배열을 프로퍼티로 할당할 때, 프로퍼티로 지정한 다음에도
원래 배열에 있던 값을 보존하고자 하는 경우에 쓰인다.
객체를 복제하고 복제된 객체를 프로퍼티에 지정하게 된다.
nonatomic(비원자성)
이 속성은 접근자 메서드가 Atomic 하지 않게 동작한다.
기본적으로 접근자는 Atomic 하게 동작한다.
Atomic 이란 멀티스레드 등으로 구성된 프로그램이 특정 접근자 메서드를 호출할 때
서로 충돌이 나지 않도록 객체 레벨에서 Lock 을 걸고 Property 에 접근하게 되는데
접근할 때마다 Lock 을 걸고 다시 푸는 작업이 반복되므로 퍼포먼스를 떨어뜨리는 결과를 가져온다.
이런 접근이 필요없다면 이 속성을 사용하는 것이 좋다.
기본적으로 생성된 접근자 메소드는 프로퍼티의 값을 변경할 때,
뮤텍스(mutex)를 사용하도록 되어 있어서 멀티 쓰레드 환경에서도 안전하게 실행할 수 있다.
이런 특성을 atomic(원자성)이라고 한다.
그러나 클래스가 멀티 쓰레드 환경에서 실행되는 것이 아니라면 낭비이다.
nonatomic 으로 속성을 선언하면 불필요한 뮤텍스 관련 처리를 하지 않는다.
//mutex (Mutual Exclusion)- 스레드 동기화
- 임계영역(critiral section: 두 개 이상의 쓰레드에 의해서 공유되는 메모리 공간에 접근하는 코드 영역)과
동일한 기능을 하고, 공유 리소스를 접근하는 다수의 스레드가 있을 때 오직 하나의 스레드만 접근 할 수 있도록 하는 것.
- 뮤텍스는 임계영역과 달리 커널모드로 동작 => 속도느림
여러 프로세스에 속한 쓰레드간 동기화에 사용
- 상호배타적인 접근을 실현하기 위해 제안된 특수 변수
- 잠금상태, 잠금 해제 상태 존재
이제 예제를 보자
@interface MyClass : NSObject {
NSString *value;
}
@property (copy, readwrite) NSString *value;
@end
@implementation MyClass
@synthesize value;
@end
value 라는 이름의 Property 의 getter 메서드 명은 value 이고 setter 명은 setVlaue 이다.
값을 대입할 때 복사가 일어나고 읽고 쓰기를 할 수 있다.
또한 nonatomic 속성이 없으니 atomic 하게 동작한다.
마지막으로 Property 를 사용할 때 주의사항 한가지가 있다.
객체가 제거될 때 소멸자로 dealloc 이 호출되는 데 Property 들이 자동으로 소거되지 않아
명시적으로 제거해 줘야 한다.
- (void) dealloc {
[value release];
[super dealloc];
}
//부연 설명 끝
위와 같이 작성한 코드는 이제 다른 객체에서 다음과 같이 사용할 수 있다.
#import<Foundation/Foundation.h>
#imprt "Model.h"
int main(int argc, const char * argv[]){
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Model *model = [[Model alloc] init];
//synthesize 를 통해 생성된 setter, getter 사용법
[model setDate:@"Hello Eye!"];
NSLog([model data]); // 결과 Hello Eye!
// dot 문법 사용하기
model.data = @"Bye Eye!";
NSLog(model.data); //결과 Bye Eye!
[pool drain];
return 0;
}
// 여기서 저 drain 이라는 넘이 궁금해졌다... 사실은 첨 본 놈.. 잠시 공부해보고 넘어가자
GC (Garbage Collector) 가 없는 환경에서는 drain = release 이지만,
GC 가 있는 환경에선 조금 차이가 있다.
drain 은 GC 에다가 알려주기만 하고 실제로는 해제하지 않는다.
iPhone 환경에서는 GC 가 없으므로 drain 과 release 는 동일하게 보면 된다.
but 그러나 애플에서는 추후 GC 가 추가될 수 있는 점을 고려하여
drain 으로 해줄것을 권장한다고 한다.. 썅.. 뭐이리 어려워
조금 더 파고 들어보자
일단 Objective-C 의 객체가 저장되는 방식
포인터(pointer)라는 개념이 낯선 프로그래머라면 도대체 이딴 게 왜 존재해야 하는지 짜증 날만도 하다.
예컨데, PHP, Python 이나 VB 같은 언어라면 문자열(string) 하나 만들려면 이런 코드면 땡이다.
(Python 이라면 ; 이것 조차 필요없겠지만 가장 일반적인 의사코드로 나타내 본다)
theString = "현재미남!!";
숫자가 저장되는 방식과 다를 게 없다.
theNumber = 3;
또, 문자열을 다른 문자열에 할당(assign) 하거나 다른 문자열 값으로 수정하는 것도 간단하다.
theSecondString = theString;
theString = "현재는 똑똑하기도 해!";
문자열과 문자열을 합쳐 기존 문자열에 다시 저장할 수도 있다.
theString = theString + " 암! 누구나 아는 사실!";
theNumber, theString, theSecondString 등의 변수가 실제로 메모리에서 어떻게
치고 받을지 거의 신경 쓸 일이 없다는 것이다.
일부 자유도를 포기하는 대신 그만큼 프로그래머로서의 책임도 덜어주는 언어들이기 때문이다.
그래서 RAD(Rapid Application Development) 언어들이 대부분 프로그래머가 직접 메모리 관리를 하는 경우가 거의 없는 거고,
C 나 그 후손들(C++, Objective-C)은 성능을 최우선으로 따져야 하는 운영체제 또는 그 부속 코드, 게임 등에서나
사용되는 게 일반적이다.
사실, Objecitve-C 에서도 상수(constants)는 신경 쓸 일이 없다.
메모리를 관리해야 하는 건 포인터로 해당 객체와 연결고리(메모리상의 저장 위치정보),
즉, 레퍼런스(reference)를 유지해야 하는 변수(variables)들 뿐이다.
문제는, 다른 RAD 언어들에서 변수로 생각하는 객체들이 Objective-C 에서는 사실상 상수인 경우가 많다는 것이다.
예컨데, 문자열이 그렇다.
NSString *theString = [[NSString alloc] initWithFormat:@"현재는 똑똑한 미남이다!"];
theString 은 변수처럼 보이지만 일단 객체가 만들어지면 죽을 때까지 "현재는 똑똑한 미남이다!" 라는
문자열이 변하질 않는다.
다른 RAD 언어에서처럼 이 문자열을 수정하려 하거나 잘라내려 하거나 다른 문자열과 합치려 하면 에러가 발생한다.
뭔가 수정하려면 처음부터 수정할 수 있는 다른 클래스로 객체를 찍어내야 하는 것이다.
NSMutableString *theMutableStirng = [[NSMutableString alloc] initWithFormat:@"현재는 똑똑한 미남이다!"];
Mutable 이라는 클래스 이름에서도 알 수 있듯이 수정이 가능한 문자열 객체를 만들 수 있다.
그럼 상수 문자열과 변수 문자열이 뭐가 다르다는 거냐?
상수 문자열은 크기가 정해진다.
프로그램이 종료될 때까지 그 크기가 바뀌지 않기 때문에 메모리 관리고 자시고 할 게 없다는 거다.
100byte 면 100byte 로 끝까지 간다는 말이다.
근데, 이게 수시로 바뀌는 mutable 형식이라면 얘기가 달라진다.
객체를 처음 만들 때 얼마만큼의 메모리 공간을 할당해야 하는지 알 수가 없다.
여기서 포인터라는 꼬리표 또는 연결 고리 개념이 등장하는 거다.
(사실 모든 언어가 변수든 상수든 메모리에 저장하고 나면 포인터 비스무레한 방식으로 참조를 한다.)
그림으로 그려보면 이렇다.
쉽게 말해 theMutableString 이라는 넘은 단지 "현재는 똑똑한 미남이다!" 라는 수정 가능한
문자열이 저장된 메모리 공간의 주소정보만 담고 있다는 것이다.
그래서 포인터(pointer)라는 말을 쓰는 거다.
*teMutableString 의 별문자 (* : asterisk)가 포인터라는 의미다.
C를 격어보지 않은 프로그래머가 가장 헷갈리는 개념이다.
theMutableString 은 자신이 담을 수 있는 주소의 크기만 메모리를 할당 받는다.
다른 RAD 언어처럼 문자열 자체가 저장되는 것이 아니다.
문자열이 아무리 수정돼도 4 바이트 주소를 저장했다면 마르고 닳도록
theMutableString 은 4 바이트 크기 그대로이다.
이 포인터가 가리키는 문자열의 메모리 주소도 바뀐게 없다면
theMutableString 은 전혀 변화가 없다.
단지, theMutableString 에 담긴 주소가 가리키는 실제 메모리 공간 내의
"현재는 똑똑한.." 만 변할 뿐이다.
그럼 요 따위 문장이 실행되면 어떤 일이 벌어질까? 둘 다 NSMutableString 객체라고 치자.
theSecondMutableString = theMutableString;
다른 언어라면 위 문장이 실행되고 나서 theMutableString 을 수정한다고 해도 복사본인
theSecondMutableString 은 바뀔 이유가 없다.
그러나...
C는 바뀐다... 젠장
요 따위 현상이 발생하기 때문이다.
사실 위의 그림이 이해되면 C 의 포인터는 끝난 거나 마찮가지다.
theSecondMutableString = theMutableString ;
이 문장이 실행될 때 다른 RAD 언어라면 실제 문자열 내용을 복사하겠지만
C 는 걍 포인터의 값, 그러니까 theMutableString 이라는 포인터에 담긴 주소 정보만
theSecondMutableString 에 복사하기 때문에 결국 메모리의 동일한 위치를 가리키게 된다는 것이다.
그러니 theMutableString 이 가리키는 "현재 똑똑.." 문자열이 수정되면 당근 theSecondMutableString 이
가리키는 문자열도 바뀔 수 밖에없다. 결국 둘다 같은 나물이라는 소리다.
자 이제 C 에서 왜 메모리 관리라는 게 필요한지 답 나온다.
무슨 이유에서든 저 문자열이 저장된 실제 메모리 주소 정보를 잃어버린다고 가정해 보자.
연결고리가 끊어지면 저 메모리 공간은 영원히 되찾지 못한다. 100 바이트가 할당돼 있었다면 100 바이트 잃어버리는 것이다.
무쟈게 큰 그림을 메모리에 올리고 포인터가 끊겼다면? 그렇다 메모리 부족 경고 뜨는 건 시간 문제이다.
퉁 쳐서 말하면 윈도우 머신에 메모리 이빠이 달고도 한 달정도 리부팅 안하고 쓰면 메모리 부족현상이 벌어질 수 있는
이유도 윈도우 운영체제가 됐든 그 응용 프로그래들이 됐든 주소 적은 쪽지 잃어버리는 뻘 짓들을 하기 때문이다.
그걸 있어 보이는 말로 메모리 누수(memory leak) 라고 하는 거고 이런 뻘짓을 잘하는 프로그램을
영어로 leaky 하다고 한다.
근까..
아이폰에서 리키한 어플을 만들면 극악의 리뷰 홍수에 익사하는 건 시간문제라는 거다.
메모리 미아 되는 걸로 끝나지 않고 프로그램이 걍 골로 가는 이유는 다음 그림을 이해하면 답 나온다.
theMutableString 이 주소 정보를 잃어버리지는 않았는데 자긴 이제 더 쓸일이 없다고 시스템에 자신이
가리키던 문자열이 차지하고 있던 메모리를 회수해도 된다고 꼰지른 경우다.
뭔소리냐 하면..
저 문자열이 더 이상 쓸모가 없다면 메모리에 방치해 둘 필요가 없지 않은가?
시스템이 회수해서 다른 객체들에게 할당해줘야 메모리가 원할이 운용된다는 거다.
근데, 이 경우는 theMutableString 이 지 생각만 한거다.
theSecondMutableString 한테 주소를 적어주지 않았던가?
theSecondMutableString 은 당근 지가 가리키고 있는 문자열이 해당 메모리 주소에 살아 있을 것으로 믿는다.
프로그램이 theSecondMutableString 에 접근하려고 하면 이미 뽀개지고 없는 집을 찾아간다는 말이다.
그럼 어떻게 되냐고? 존재하지 않는 객체에 접근하려고 하는 행위 자체에 대한 오류 대책이 없다면
(exception handling : 예외 처리) 프로그램은 속수무책이기 때문에 비정상적으로 종료할 수 밖에 없다.
그게 숱하게 경험하는 윈도우 응용 프로그램들 뻑나는 광경이다.
프로그래머들이 그런 뻘짓을 왜 할까?
저 딴 포인터가 한 두개 뿐이고 프로그램이 간단한 구조라면 별 문제가 안된다.
근데, 아이폰 앱스토어에 올릴만한 내공이 좀 되는 애플리케이션이라면 구조도 복잡해지고 객체수도 장난 아니게
많아질 수밖에 없다. 자동으로 수거가 안되기 때문에 아이폰 프로그래머는 그걸 일일이 지 대가리 하나로
해결해야 한다는 것이다.
그럼 뭘로 해결하느냐...
바로 레퍼런스 카운팅이라는 개념을 통해서이다.
레퍼런스 카운팅 reference counting
메모리 관리를 책임져야 하는 프로그래머는 위에 말한 두 상황을 막아야 한다.
예컨데, Objective-C 에서 첫 번째 경우는 다음과 같이 해결할 수 있다.
[theMutableString release];
객체를 더 이상 쓸 일이 없어지면 메모리를 돌려주면 된다는 것이다.
여기서 또 오해가 생길 수 있다. release 라는 걸 메모리를 실제로 release 하는 걸로 오해하기 십상이다.
Objective-C 는 객체를 참조하고 있는 포인터가 몇 개인지 (개념 이해를 위해 포인터로 표현한다.
레퍼런스와 같은 의미다.) 레퍼런스 카운트라는 걸로 기록을 해 둔다.
NSMutableString *theMutableString = [[NSMutableString alloc] initWithFormat:@"현재는 똑똑한 미남이다!"];
처음 만들어진 객체의 레퍼런스 카운트는 1 이 된다.
이건 retainCount 라는 메소드를 사용해도 알 수 있다.
[theMutableString retainCount];
바로 이 레퍼런스 카운트가 0 이 되면 더 이상 참조하는 포인터가 없다는 뜻으로 간주해 메모리를 회수해 버린다.
[theMutableString release];
이 문장이 실행되는 순간 레퍼런스 카운트는 1 에서 0 으로 줄어들고 "현재는 똑똑.." 이라는
문자열이 저장됐던 공간을 깨끗이 비워 재활용한다는 것이다.
그럼 위에서 두 번째 상황을 어떻게 해결할 수 있을까?
theMutableString 이 더 이상 "현재는 똑똑..." 문자열이 필요 없게 됐다고 해도
theSecondMutableString 은 필요할 것이기 때문에 복사할 때 부터 레퍼런스 카운트를 1 올려주면 된다.
theSecondMutableString = theMutableString;
[theSecondMutableString retain];
레퍼런스 카운트를 올려줄 때 쓰는 게 retain 메소드다.
그럼 retainCount 가 2가 되기 때문에 theMutableString 에서 release 를 한다 해도 레퍼런스 카운트는 0 이
아닌 1 이 되기 때문에 시스템이 메모리를 회수하지 않는다.
theSecondMutableString 도 release 를 해줘야 비로소 문자열이 폐기처리 된다.
[theMutableString release];
....
[theSecondMutableString release];
그러니까 객체를 만들 때의 retainCount, 또 retain 메소드를 썼을 때 retainCount, 현재 객체를 가리키는 레퍼런스들
(또는 포인터들)의 상황 등등을 모두 종합해서 프로그래머가 애플리케이션 흐름상 해당 객체의 retainCount 를 꿰고
있어야 메모리 누수가 없는 어플을 만들 수 있다.
쉬울 것 같은가?
어려울 만한 예를 하나 들어보자. 전형적인 setter 메소드의 경우다.
-(void)setUserName :(NSString *) name
{
username = name;
}
PHP 냐 Python 이었다면 전혀 문제될 게 없어 보인다.
(VB는 인수를 ByBal 로 넘기느냐 ByRef 로 넘기느냐에 따라 문제될 수 있다.)
왜 문제가 발생하는지 저 메소드를 실제로 불러 보자
theUser 라는 객체가 있다고 가정하고 setUserName 은 theUser 의 이름을 변경하는 메소드라고 생각하자.
NSString *name = [[NSString alloc]initWithFormat:@"미남"];
[theUser setUserName:name];
[name release];
아주 깔끔한 코드이다.
근데 뭐가 문제일까?
[name release];
요 따위로 name 이 나 몰라라 "미남" 이라는 문자열과 연결 고리를 끊어버리면
theUser.username 프로퍼티는 존재하지 않는 메모리 공간을 가리키게 된다.
name 이 가리키는 객체의 retainCount 가 [name release]; 하는 순간 0 이 돼서
시스템 메모리를 회수해 버리기 때문이다.
이런 사태를 방지하려면 setUserName 메소드를 다음처럼 수정해줘야 한다.
-(void)setUsername:(NSString *) name
{
username = name;
[username retain];
}
[username retain]; 을 해주면 name이 가리키고 있는 (결국 username 이 가리키는 객체와 동일)
객체의 retainCount 가 2 가 되고 나중에 [name release]; 를 한다 하더라도 retainCount는 0 이 아닌 1 이
되기 때문에 "미남" 이라는 문자열 객체는 살아 남을 수 있다.
해당 객체가 더 이상 쓸모 없게 돼서 dealloc 메소드에서 [username release]; 로 retainCount 를 0 으로
만들어 줄 때야 비로소 "미남" 이라는 문자열이 저장된 메모리에서 회수된다.
메모리 저장 공간 크기가 고정돼 있는 상수 문자열이나 숫자 등은 레퍼런스 카운팅이 적용되지 않는다.
객체가 바뀔 수 있는 경우만 레퍼런스 카운팅을 신경 쓰면 된다.
이 레퍼런스 카운팅이 복잡해 지는 건 다음과 같은 이유 때문이다.
- new, alloc, copy 등이 메소드 이름에 포함돼 있다면 자동으로 객체의 retainCount 가 1 이 된다.
- array 나 dictionary 등 collection 객체에 저장되는 순간 retainCount 는 +1 이 된다.
- 객체가 auto release pool 에 추가된다고 해서 retainCount 가 증가하지는 않는다.
단지, 나중에 release 될 걸 예약할 뿐이다.
auto release pool 이 drain 이 된다고 해서 풀에 있던 객체들의 메모리를 강제로 회수하는 것도 아니다.
auto release 된 횟수만큼 retainCount 를 감소시킬 뿐이다.
기본은 간단하다.
retainCount 를 증가시켰다면 그걸 기억했다 고스란히 release 해줘야 한다는 것이다.
객체 초기화 또는 retain 메소드를 통해 올린 retainCount 와 release 개수가 맞지 않으면 여지 없이
메모리 누수가 일어날 수 밖에 없다.
1 번은 이해하기 쉽다. 메소드에 new, alloc, copy 등의 이름이 들어가 있다면
(예 : NSString 객체의 initWithFormat) 당근 새로운 객체가 처음 만들어 지는 거니 retainCount 는 1 이 된다.
(포인터가 가리키는 객체의 레퍼런스 카운트가 1 증가 하는 것이다.
그 객체를 참조하는 포인터가 하나 더 늘었다는 뜻. 포인터 자체가 객체라고 오해하면 안된다)
2 번의 경우는 모르면 retainCount 빵꾸나는 거 시간문제다 예를 들어보자
NSMutableArray *array = [NSMutableArray array];
NSNumber *integer = [NSNumber numberWithInteger:5];
[integer retainCount]; //레퍼런스 카운트 : 1
[array addObject:integer]; // 레퍼런스 카운트 : 2
[integer release]; // 레퍼런스 카운트 :1
위 코드에서도 알 수 있듯이 array 배열에 integer 가 가리키는 객체가 추가되는 순간 레퍼런스 카운트는
2 로 증가한다. 그래야 [integer release]; 처럼 포인터가 사라져도 array 라는 배열에 추가된 포인터는
해당 객체를 참조 할 수 있을 것 아닌가.
헷갈리지 마시라. 단순하다.
여기서 integer 는 5 라는 실제 값이 담긴 NSNumber 객체가 가리키는 포인터 일 뿐이다.
[integer release]; 라는 것도 이 포인터가 사라지는 것일 뿐이다.
NSNumber 5 라는 객체를 참조하고 있는 포인터의 개수인 레퍼런스 카운트만 줄어드는 거란 말이다.
따라서 NSNumber 5 라는 동일한 객체가 가리키는 array 배열 내의 포인터는 무사한거다.
그럼 다음처럼 별 생각 없이 배열에서 객체를 삭제했다면 무슨 일이 벌어질까?
NSMutableArray *array = [NAMutableArray array];
NSNumber *integer = [NSNumber numberWithInteger:5];
NSNumber *integer2;
[integer retainCount]; // 레퍼런스 카운트 : 1
[array addObject:integer]; // 레퍼런스 카운트 :2
integer2 = [array ObjectAtIndex:0]; // 레퍼런스 카운트 2 변화없음
[array removeObjectAtIndex:0]; // 레퍼런스 카운트 1
[integer release]; // 레퍼런스 카운트 0
[integer2 release]; // 에러
간단히 말해 배열 등 collection 객체에 포인터가 추가되면 그 포인터가 가리키는 객체의 레퍼런스 카운트가 1 증가하고,
포인터가 collection 객체에서 삭제되면 레퍼런스 카운트가 자동으로 1 감소한다.
따라서 맨 마지막 [integer2 release] 는 존재하지 않는 객체를 참조하게 되기 때문에 에러가 발생할 수 밖에 없다.
배열에서 더 이상 쓸모가 없더라도 객체를 살려두려면 retain을 해줘야 한다.
[array removeObjectAtIndex:0] // 레퍼런스 카운트 1
[integer2 retain]; //레퍼런스 카운트 2
[integer release]; // 레퍼런스 카운트 1
[integer2 release]; // 레퍼런스 카운트 0 메모리 회수
헷갈릴 수 있지만 강조하면 array 배열에 담긴 포인터, integer 라는 포인터 integer2 포인터
모두 동일한 객체를 가리키고 있을 뿐이다.
이 넘들이 '객체'라는 환상을 버려야 레퍼런스 카운팅 개념의 이해가 쉽다.
요렇게 integer, array[0], integer2 등 NSNumber 5 라는 객체를 가리키는 넘이 3개 있다는 것이 레퍼런스 카운팅의 개념이다.
그러니 레퍼런스 카운트가 0 이 됐다는 건 NSNumber 5 에 관심을 갖는 넘이 아무도 남지 않았다는
뜻이기 때문에 시스템이 안심하고 메모리를 회수할 수 있게 되는 것이다.
이제 3번에 등장하는 auto release pool 이라는 개념만 이해하면 된다.
이름에서도 알 수 있듯이 일일이 신경 쓰지 않아도 나중에 자동으로 release 를 해주는 pool 이다.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
요것만 해줘도 pool 이 만들어 지고 auto release 를해야 할 객체들을 관리 해준다.
[theObject autorlease];
이렇게 해주면 theObject 객체가 자동 풀에 들어가게 된다.
[pool drain];
풀을 drain 하는 순간 그 풀 안에 있던 객체들은 몽땅 release 한다.
하지만 중요한 것이 있다.
물이 빠지면 대부분의 놈들이 사라지겠지만 풀 안에 있던 넘들 중에는
여전히 참조하는 다른 객체가 있어 사라지지 못하는 경우도 있다.
autorelease 를 했다고 해서 프로그래머가 객체 뒤치다꺼리 할 일이
완전히 사라지는 것은 아니라는 소리이다.
다음 코드를 보자.
NSAutorelasePool *pool = [[NSAutoreleasePool alloc] init];
MyClass *theObject = [[Myclass alloc] init]; // 레퍼런스 카운트 : 1
[theObject autorelease]; // 레퍼런스 카운트 1 변화 없음
...
[theObject retain]; // 레퍼런스 카운트 2
...
[pool drain]; // 레퍼런스 카운트 1
...
[theObject release]; // 레퍼런스 카운트 0 메모리 회수
실제로 1번에서 설명한 new, init, copy 등의 이름이 들어가는 메소드들 이외의 다른 메소드들은
autorelease 객체들을 돌려주는 경우이다.
아이폰 어플이 main 이벤트 루프에서부터 자동 auto release pool 을 마련해 두는 이유가 바로 이때문이다.
#import<UIKit/UIKit.h>
int main(int argc, char *argv[]){
NSAutoreleasePool *pool = [[NSAutorelease alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
동시 다발적으로 객체에 접근하는 멀티 쓰레딩(multithreading) 애플리케이션 등을 개발한다면 모를까
여기까지 이해하고 강박적으로 메모리 관리를 하면 대부분의 아이폰 어플에서 메모리 누수는 없을 것이다.
하지만 메모리 관리라는 게 실제 해보면 쉽지가 않다.
// 여기까지 drain 때문에 다시 정리해봤다. 다 아는 내용인데 .... 2줄 빼고 drain 에 대해서 나오지도 않네
다시 본문으로 돌아가자
위의 예제에서 알 수 있듯이 model 객체의 data 인스턴스 변수에 접근하기 위해서는 synthesize 를 통해 생성된
getter(data)와 setter(setData) 를 통해 가능하다.
혹은 C 에서 익숙한 객체와 변수 사이에 점을 찍는 방식인 dot 문법도 사용가능하다.
하지만 여기서 중요한 점은 Property 이다.
Model.h 의 property 선언 부분을 다음과 같이 수정해 보자
@property(copy, readonly) NSString *data;
바뀐점은 readwrite 에서 readonly 로 바뀌었다. 실행해보면 에러가 난다.
error : object cannot be set - either readonly property or no setter found
property 와 synthesize 를 이용해서 MVC 에서 추구하는 데이터를 신뢰할 수 있고 안전하게
사용할 수 있는 방법을 손쉽게 구현할 수 있다는 것을 알 수 있다.
여기서 괜히 궁금한 것이 생겼다. (나 아님.. 이거 적은 블로그 씨임..)
synthesize 를 통해서 생성된 setter, getter 가 해당 변수를 보호하는 것일까?
property 를 통해 정의된 변수 그 자체를 보호하는 것일까? (뭔말이랴?)
이 궁금증을 해소하기 위해 setter 를 하나만 만들어 보기로 하자. 코드에 다음을 추가한다. (모르면 답부터..)
//Model.h
// 현재 @property 설정에 readonly 로 되어 있다고 가정
- (void)setCustomData:(NSString *)myData;
//Model.m
- (void)setCustomData:(NSString *)myData{
self.data = myData;
}
이제 만든 setCustomData 메서드에 메시지를 전달해 보겠습니다.
Model *model = [[Model alloc] init];
[model setCustomData:@"Hello Eye!"];
이 코드를 실행해 보면 아무런 문제가 발생하지 않습니다.
해당변수가 보호 받는 것이 아님을 알 수 있습니다.
클래스 형식
Object-C 에서 생성한 클래스의 인스턴스는 id 변수에 저장할 수 있다.
id 형식은 void * 를 닮은 존재 클래스에 관계없이 인스턴스를 저장하는 일반적인 개체 형식이다.
따라서 id 변수의 실체는 컴파일 타임이 아닌 런타임에 결정된다는 특징이 있다.
그러나 런타임에 인스턴스 확인한다는 것은,
예를 들면 메서드를 호출하기 위해 개체에 메시지를 보낼 때 해당 메시지에 대응하는
메서드가 없을 가능성이 있다. 다음과 같은 프로그램은 그 전형적인 예이다.
#import <stdio.h>
#import <objc/Object.h>
@ interface A : Object
-(void)Write;
@end
@implementation A
-(void)(Write
printf( "I am the born of my sword \ n");
)
@end
int main(){
id obj1 = [A new];
id obj2 = [Object new];
[obj1 Write];
[obj2 Write]; // 런타임 오류
retrun 0 ;
}
selector
Objective-C 컴파일러는 메서드를 식별하는 이름을 컴파일 할 때 내부 표현으로 변환한다.
이 메서드의 내부 표현을 선택기라고 메시지를 보내고 받는 뒷면은 이 셀렉터가 교환되고 있다.
메서드를 식별하기 위한 내부 표현은 컴파일러에 의존하는 문제이고, 개발자가 알아야 하는 범위는 없다.
개발자에게 중요한 것은 이 선택기를 SEL 형식으로 취급할 수 있다는 사실이다.
메서드가 어떤 데이터로 변환되고, 어떻게 인식되고 있다는 문제가 없다.
하지만 Objective-C 는 이 내부 표현을 SEL 변수로 취급하는 것을 보장한다.
즉, SEL 변수에는 메서드 이름을 확인하기 위하여 컴파일러가 할당 특수 코드를 저장할 수 있는 것이다.
메서드를 식별 선택기는 @selector 컴파일러 지시문을 사용하여 얻을 수 있다.
@selector(method)
method 는 메서드의 이름을 지정한다.
지정된 메서드의 이름이 있는지 여부는 메서드를 호출하는 경우 런타임에 결정되므로 컴파일 타임에 계산되지 않는다.
썅 어느 사람 블로그 보고 정리하고 있었는데
발가락으로 타이핑을 했나 도저히 알아 볼수가 없어 포기하네요 짜증나게 더 헷갈리게 하네요
'개발 > Objective-C' 카테고리의 다른 글
[Objective-C] tokenizerWithString in ParseKit (0) | 2010.08.29 |
---|---|
[Objectiv-C] UILabel과 관련된 주요 설정값 (0) | 2010.08.29 |
[Objectiv-C] Collection에 대해서 (0) | 2010.08.29 |
[Objective-C] Memory 관리 (0) | 2010.08.29 |
SEl, @selector (0) | 2010.08.29 |