objective c 소유권
objective c에서 메모리 관리 방법은?
1.MRC - 수동 메모리 참조 계수
2.ARC - 자동 메모리 참조 계수
리테인 카운터란?
Retain Counter 란 소유계수라고도 한다. 대부분 인스턴스가 생성(alloc)될 때 1로 초기화 된다. 만일 해당 인스턴스를 다른 곳에서 공유(retain)하면 소유계수는 1만큼 증가된다. 반대로 더 이상 해당 인스턴스에 접근할 필요가 없을때는 이값은 1만큼 감소(release)된다. 최종적으로 계수가 0이되면 인스턴스는 시스템에 의해 제거된다.
예제1
아래 예제에서는 NSObject의 인스턴스를 alloc/init 으로 만들어서 obj 포인터에 할당한다. retain을 1회 release를 2회씩 호출한 결과이다. 마지막 release후 retainCount는 0이 되거나 해제된 메모리에 접근하고자 했으므로 메모리 오류를 기대했는데, 실제는 다른 결과가 나타났다. obj c 는 인스턴스를 제거할 때 즉시 제거하지 않고, 동일 인스턴스의 재생성을 위해 지연된 제거를 한다. 이런 이유로 애플은 retainCount 메소드를 디버깅 작업에만 사용하고, 프로그램 로직에는 사용하지 말것을 권고한다.
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
id obj, instance;
//리테인 카운터 1 증가
obj = [[NSObject alloc]init];
printf("alloc : %p %lu\n" , obj , [obj retainCount]);
//리테인 카운터 1증가
[obj retain];
printf("retain : %p %lu\n" , obj , [obj retainCount]);
//리테인 카운터 1감소
[obj release];
printf("release : %p %lu\n" , obj , [obj retainCount]);
/*
0으로 되고 포인터는 null이 되어야 하는데..?
release 한다고 해서 null이 되지 않는다.
바로바로 날리지 않는다. 언제쓸지 모르기때문에 잠시 가지고 있다. 실제로는 삭제가 되었다.
그래서 프로그램 로직안에서 사용하면 안된다...
*/
[obj release];
printf("release : %p %lu\n" , obj , [obj retainCount]);
//alloc 되지 않은 인스턴스는 nil값을 가지며,
//여기에 보내는 메시지는 전부 무시된다.
[instance release];
printf("instance : %p %lu\n" , instance , [instance retainCount]);
/*
결과
alloc : 0x10074a880 1
retain : 0x10074a880 2
release : 0x10074a880 1
release : 0x10074a880 1
instance : 0x0 0
*/
}
시스템 제공 클래스
NSString 또는 NSNumber 같은 시스템 제공 클래스의 인스턴스는 특이한 소유계수를 가진다.
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
NSString *strNomal;
NSString *strUTF8;
strNomal = [[NSString alloc] initWithString:@"안녕 Normal string"];
//@를 제거해고 " " 안에 문자열
strUTF8 = [[NSString alloc] initWithUTF8String:"안녕 UTF8 String"];
NSLog(@"%@" , strNomal);
NSLog(@"%@" , strUTF8);
//리테인 카운터 -1
//리테인 카운터가 -1 이면 고정형 인스턴스이다. = 항존객체 = 메모리에 언제나 존재한다.
printf("%lu %s \n" , [strNomal retainCount] , [strNomal UTF8String]);
//리테인 카운터 +1 이 나옴
printf("%lu %s \n" , [strUTF8 retainCount] , [strUTF8 UTF8String]);
NSNumber *nInteger;
NSNumber *nFloat;
nInteger = [[NSNumber alloc] initWithInt:100];
nFloat = [[NSNumber alloc] initWithFloat:100.0];
//아래 값 둘다 -1
printf("%lu %d\n", [nInteger retainCount], [nInteger intValue]);
printf("%lu %f\n", [nFloat retainCount], [nFloat floatValue]);
/*
결과
안녕 Normal string
안녕 UTF8 String
18446744073709551615 안녕 Normal string - (LONG_MAX 값을 가짐 signed 경우 -1)
1 안녕 UTF8 String
9223372036854775807 100
9223372036854775807 100.000000
*/
return 0;
}
위 예제에서 NSString (@" ") 형식으로 만들어진 NSString 인스턴스는 위와 같이 특이한 소유 계수를 가진다. 또한 자주 사용할 법한 NSNumber에서도 특이한 소유 계수는 나타난다. obj c 에서는 NSString 형식의 문자상수나 자주 사용될 만한 인스턴스는 컴파일 시간에 미리 준비하여 파괴되지 않는 비휘발성 저장소에 저장한다. 이런 객체를 항존객체(언제나 메모리에 존재하는 객체)라고 하고 소유 계수는 -1이 된다. 이 객체들은 retain 또는 release에 의해 소유 계수가 변하지 않으며 프로그램이 끝날때까지 제거 되지도 않는다.
인스턴스를 멤버로 가지는 인스턴스
int main() 모듈이 시작되면서 두 개의 문자열을 생성하고 이것을 가지고 공통클래스인 coL, coR 인스턴스를 할당 및 초기화 한다. 이 과정을 거쳐도 strL과 strR의 소유계수는 -1에서 변하지 않는다. 이들은 문자열 상수인 항존객체이기 때문이다. Foundation에서 제공하는 몇몇 클래스는 초기화 방법에 따라 소유계수가 -1인 인스턴스를 가지며 retain/release에 영향을 받지 않는다. 이들 인스턴스 포인터는 @property에서 일반 기본 자료형과 같이 assign으로 옵션을 설정하여도 무방하다.
CommonObject.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
//기본 생성/초기화 기능을 위해서 NSObject를 상속 받는다.
@interface CommonObject : NSObject
/*
Foundation에서 제공하는 몇몇 클래스는 초기화 방법에 따라
소유계수가 -1인 인스턴스를 가지며 retain/release에 영향을 받지 않는다.
이들 인스턴스 포인터는 @property에서 일반 기본 자료형과 같이 assign으로 옵션을 설정하여도 무방하다.
*/
@property (assign) NSString *str;
//지정초기자
-(id)initWithStr:(NSString *)s;
@end
NS_ASSUME_NONNULL_END
CommonObject.m
#import "CommonObject.h"
@implementation CommonObject
@synthesize str;
//지정 초기자
-(id)initWithStr:(NSString *)s{
//슈퍼 클래스의 보조초기자 먼저 호출!
self = [super init];
//멤버 변수 str은 assign 형식으로,
//기본 자료형과 같은 방식으로 포인터 주소를 할당한다.
//즉, 포인터 주소값만 간단히 복사한다.
if (self) {
str = s;
}
return self;
}
@end
MyObject.h
#import <Foundation/Foundation.h>
#import "CommonObject.h"
NS_ASSUME_NONNULL_BEGIN
@interface MyObject : NSObject
{
//참조를 위한 인스턴스 변수 생성
CommonObject *obj;
}
//만약 옵션으로 assign을 사용하면 프로그램은 오류가 발생하며 실행이 중단된다!!
//@property (retain) CommonObject *obj; // 이 구문을 사용하면 리테인 릴리스를 자동으로 해준다.
//인스턴스 변수 초기화
//공통 클래스 CommonObject의 인스턴스를 받아서 연관관계를 구성한다.
-(id)initWithObject:(CommonObject *)o;
//셋
-(void)setObj:(CommonObject *)o;
//겟
-(CommonObject *)obj;
@end
NS_ASSUME_NONNULL_END
MyObject.m
지정초기자를 셋팅할때 넘겨받은 인스턴스 o 가 오너십을 가져오기 때문에 멤버변수에 할당될 때 retain을 수행하고, 기존의 멤버 변수는 release하여 오너십을 해제 한다. 순서에 유의해야 한다. 만약 retain과 release의 순서를 바꿀 경우, 보통의 경우는 문제가 없지만, 만약 기존변수 obj와 새 변수 o의 포인터 값이 서로 같을 경우, 즉 현재의 obj를 다시 setter에 설정하고자 할때 문제가 발생할 수 있다.
#import "MyObject.h"
@implementation MyObject
//@synthesize obj;
//지정초기자 셋팅
- (id)initWithObject:(CommonObject *)o{
self = [super init];
if (self) {
/*
인수로 받은 인스턴스는 오너십을 가지게 한다.
기존 인스턴스는 오너십을 제거한다.
내부 멤버변수 obj 포인터 갱신,
*/
[o retain];
[obj release];
obj = o;
}
return self;
}
//셋팅
-(void)setObj:(CommonObject *)o{
[o retain];
[obj release];
obj = o;
}
//겟
-(CommonObject *)obj{
return obj;
}
/*
인스턴스 객체가 메모리에서 해제될때 자동으로 호출됨
NSObject의 dealloc의 오버라이드 , 멤버의 오너십 제거 후,
부모클래스의 dealloc 호출. init과 반대순서
*/
-(void)dealloc{
//객체 메모리에서 해제
[obj release];
[super dealloc];
}
@end
dealloc에서는 소유하고 있는 인스턴스의 오너십을 먼저 해제하여야 한다. 오너십을 해제할 때는 소유 인스턴스의 dealloc을 사용자가 직접호출하면 안된다. 대신 위와 같이 release를 호출한다. 모든 처리가 완료되면 슈퍼 클래스의 dealloc을 호출한다. init은 이와 반대다.
main.m
#import <Foundation/Foundation.h>
#import "MyObject.h"
int main(int argc, const char * argv[]) {
NSString *strL = @"첫번째 Common 객체";
NSString *strR = @"두번째 Common 객체";
//coL 리테인 카운터 1증가
CommonObject *coL = [[CommonObject alloc]initWithStr:strL];
//coR 리테인 카운터 1증가
CommonObject *coR = [[CommonObject alloc]initWithStr:strR];
//oL리테인 카운터 1증가 , coL 리테인 카운터 1증가
MyObject *oL = [[MyObject alloc]initWithObject:coL];
//oR리테인 카운터 1증가 , coL 리테인 카운터 1증가
MyObject *oR = [[MyObject alloc]initWithObject:coL];
printf("공용 coL : %lu , 공용 coR : %lu , 왼쪽 oL:%lu , 오른쪽 oR: %lu\n",
[coL retainCount] , [coR retainCount],
[oL retainCount] , [oR retainCount]);
//공용 coL : 3 , 공용 coR : 1 , 왼쪽 oL:1 , 오른쪽 oR: 1
printf("coR 인수로 셋팅\n");
[oL setObj:coR];
[oR setObj:coR];
printf("공용 coL : %lu , 공용 coR : %lu , 왼쪽 oL:%lu , 오른쪽 oR: %lu\n",
[coL retainCount] , [coR retainCount],
[oL retainCount] , [oR retainCount]);
//개별 객체 모두 해제 .
//dealloc의 정의에 따라 연관 객체도 함께 해제
[oL release];
[oR release];
printf("공용coL:%lu 공용coR:%lu\n",
[coL retainCount], [coR retainCount]);
//마지막으로 공통객체 모두 해제
//공통 객체의 오너십은 main 모듈에도 존재하기 때문이다.
[coL release];
[coR release];
return 0;
}
인스턴스 객체 오너십 정책을 위한 규칙
아래 예제에서 dealloc 부분을 보면 멤버변수로 할당된 myValue를 release 하고 있다. 문제는 이 부분에서 a->b, b->a 재귀적 연관관계로 인해 현재 메소드가 다시 호출된다. 재귀적 호출은 멤버변수의 형식이 자기와 동일한 클래스 형식을 참조한다고 발생하는 것은 아니다. 다만 재귀적 호출은 실제 인스턴스 포인터가 사이클을 형성할 때 발생한다. 소유계수가 1인 객체 a에 release 메시지를 보내면 [a dealloc]이 유발된다. [a release] 이후에는 a,b의 소유계수는 모두 0이 되어 제거 되었다. 이때 [b release]가 호출되어 시스템 오류가 발생한다. 해결방법으로는 한쪽은 소유권을 가지지 않는 백포인터(역방향 포인터)를 통해 단순 연관 관계를 가지도록 설계한다.
#import <Foundation/Foundation.h>
@interface MyClass : NSObject
@property (retain) id myValue;
@end
@implementation MyClass
@synthesize myValue;
//재정의
-(void)dealloc{
printf("%p deallocated \n" , self);
printf("%p releasing \n" , myValue);
[myValue release];
[super dealloc];
}
@end
int main(int argc, const char * argv[]) {
id a = [[MyClass alloc]init];
id b = [[MyClass alloc]init];
printf("after alloc \n");
printf("a : %p %i \n" , a , (int)[a retainCount]);
printf("b : %p %i \n" , b , (int)[b retainCount]);
//프로퍼티 구문으로 setMyValue 메소드 자동생성됨
[a setMyValue:b];
[b setMyValue:a];
printf("\n after associate \n");
printf(" %i \n" , (int)[a retainCount]);
printf(" %i \n" , (int)[b retainCount]);
[a release];
[b release];
printf("\n after release \n");
printf(" %i \n" , (int)[a retainCount]);
printf(" %i \n" , (int)[b retainCount]);
// retainCount 1 상태에서 release는 dealloc을 유발
[a release];
// [a release] > [a dealloc] > [b release] > [b dealloc] > [a release] 호출
printf("After [a release]\n");
[b release];
// [b release] > [b dealloc] > [a release] > [a dealloc] > [b release] 호출
printf("After [b release]\n");
return 0;
}
재귀적 오너십에 대해 아이폰 프로그래밍이 화면 구성과 연관지어 살펴보자. 아이폰의 화면 뷰 구성은 아래 그림처럼 계층형 구조로 되어 있다.
문제는 subviews와 함께 superview의 연관관계를 모두 가져야 하기 때문에 이들에 대해 오너십을 가지는 연관관계를 운영하면 무한 루프 문제가 발생한다. 그래서 아래 그림처럼 실선은 오너십을가지는 연관관계, 반면 점선은 오너십이 없는 단순 연관관계인 백포인터로 단순 참조용 포인터를 가지게 한다. @property 구문에서 실선은 retain 속성을, 점선은 assign 속성을 가지도록 설정하면 된다.
임시인스턴스 객체, 간편생성자
obj c에서 객체를 생성하고 연관관계를 설정하거나, 하나의 메소드 안에서 alloc 하거나 하면 반드시 release 해줘야 한다. 이런 행위가 많이 불편하다. 그래서 이런 불편을 해소하기 위해서 obj c에서는 자동 해제 pool 을 제공한다. 이 자동해제 풀은 자신에게 등록된 객체들을 [ ...drain] 또는 [...release] 메시지를 받았을 때 자기 자신과 함께 모두 제거한다.
#import <Foundation/Foundation.h>
/*
id 형식의 포인터 s는 문자열 객체를 생성 후 autorelease 메시지를 받는다.
이는 현재 스택에 등록된 NSAutoreleasePool의 인스턴스 poolMain에 자신(receiver, 메시지를 받은 객체 s)을 등록한다.
또한 같은 방식의 생성자 string...으로 만든 객체 t는 poolSub 인스턴스에 자신을 등록한다.
이 객체 t는 [poolSub drain] 메시지가 실행될 때 제거 되고,
객체 s는 [poolMain drain] 메시지가 실행될 때 제거된다.
*/
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSAutoreleasePool *poolMain= [[NSAutoreleasePool alloc]init];
//현제 자동해제 풀에 생성된 NSString 객체 s를 등록
id s = [[[NSString alloc] initWithUTF8String:"hello world"] autorelease];
@autoreleasepool {
NSAutoreleasePool *poolSub= [[NSAutoreleasePool alloc]init];
//Convenience constructor, 간편 생성자로 생성된 NSString 객체 t,
//이렇게 생성된 경우 자동으로 현재 자동해제 풀(poolSub)에 등록
id t = [NSString stringWithUTF8String:"hello world2"];
//객체 t 제거
[poolSub drain];
}
//객체 s 제거
[poolMain drain];
}
return 0;
}
obj c는 alloc / init 방식으로 인스턴스를 생성하고, 오너십을 획득한다. 이에 반해 간편 생성자는 각 클래스 이름에서 접두사 NS를 제거한 단어로 시작하는 클래스 메소드를 사용한다. 즉 NSString 경우 string .. 이라는 이름으로 시작하면 간편생성자를 뜻한다. 그리고 간편생성자는 클래스 메소드이다! 일반 생성자는 클래스 메소드 alloc을 먼저 호출하고 여기서 반환된 id 포인터를 리시버로 인스턴스 메소드 init을 호출했다. 반면 간편 생성자는 클래스 메소드로 한번만 호출하는 것으로 완료 된다.
간편생성자는 생성자 alloc 과 초기화 init을 합친 후에 AutoreleasePool에 등록하고 결과값을 반환한다. 클래스 MyClass가 있다고 가정하고 인수 두개를 전달 받는 간편생성자는 보통 다음과 같이 정의 할 수 있다. 이때 자신의 클래스 이름 대신 self 키워드를 사용하는 것도 주의한다.
간편생성자 응용, 클래스 객체
분수 클래스에 간편 생성자를 추가하는 예제
Fraction.h
#import <Foundation/Foundation.h>
@interface Fraction : NSObject
@property (readwrite) int num;
@property (readwrite) int den;
//간편 생성자 - 클래스 메소드이다.
+(id)fractionWithNum:(int)n den:(int)d; // Convenience constructor
-(id)initWithNum:(int)n den:(int)d; // Designated initializer
-(void)setTo:(int)n over:(int)d;
-(void)reduce;
-(void)print;
-(float)convertToFloat;
-(Fraction *)fractionAdd:(Fraction *)f;
@end
Fraction.m
#import "Fraction.h"
@implementation Fraction : NSObject
@synthesize num;
@synthesize den;
+(id)fractionWithNum:(int)n den:(int)d
{
/*
간편 생성자는 alloc/init을 하고 그 결과값 (self)를
autorelease pool에 등록하고 반환한다.
여기서 메시지의 리시버가 Fraction 클래스가 아닌 self로 표시한것에 주의한다.
클래스 메소드에서 self는 인스턴스가 아닌 클래스를 뜻한다.
*/
return [[[self alloc] initWithNum:n den:d] autorelease];
}
-(id)initWithNum:(int)n den:(int)d
{
self = [super init];
if(self != nil)
[self setTo:n over:d];
return self;
}
-(void)setTo:(int)n over:(int)d
{
num = n;
den = d;
[self reduce]; // 자신 인스턴스의 메소드 호출
}
-(void)reduce
{
int u = num; // 메소드 내부의 로컬변수 u, v, temp
int v = den;
int temp;
while(v != 0)
{
temp = u % v;
u = v;
v = temp;
}
num /= u;
den /= u;
}
-(void)print
{
printf("%i/%i\n", num, den);
}
-(float)convertToFloat
{
if(den != 0)
return (float)num / (float)den;
else
return 1.0;
}
-(Fraction *)fractionAdd:(Fraction *)f
{
int n, d;
n = num * f->den + den * f->num; // 동일클래스의 다른 인스턴스 접근연산자
d = den * f->den;
/*
이 메소드는 리시버 인스턴스 자신의 값을 변경하지 않고,
새인스턴스를 생성하여 결과 값을 설정한다.
이때 간편 생성자를 호하는데, 수신자로 Fraction 대신 [self class]를 사용한것에 주의한다.
*/
return [[self class] fractionWithNum:n den:d];
}
@end
main.m
#import "Fraction.h"
int main(int argc, char *argv[])
{
@autoreleasepool
{
Fraction *a, *b;
a = [[Fraction alloc] initWithNum:1 den:4]; // a = 1/4
b = [[Fraction alloc] initWithNum:1 den:2]; // b = 1/2
[a print];
printf("+\n");
[b print];
printf("---\n");
/*
이 메소드는 리시버 (self) 또는 인수 f의 값을 바꾸지 않고, 임시 인스턴스를 만들어서
결과 값을 저장하고 반환한다.
*/
[[a fractionAdd:b] print];
/*
fractionAdd 에 의해 반환된 임시 인스턴스는 즉시 [..print] 호출에 사용되고 변수에 저장되지 않는다.
즉, 메모리 누수가 발생할 가능성이 있다. 그러나 @autorelease pool 블럭이 닫힐때 자동으로 해제 되기 때문에
메모리 누수는 발생되지 않는다.
*/
[a release];
[b release];
}
return 0;
}
인스턴스 메소드 안에서 self는 현재 인스턴스 객체를 의미한다. 이 변수를 이용해서 현재 인스턴스의 멤버 변수와 메소드에 접근할 수 있다. 만일 클래스 객체 안에서 self라고 쓰면은 바로 클래스 객체를 가리킨다. 위의 예제 에서 [self alloc]은 [Fraction alloc] 과 같다. 그러나 추후 상속이 발생할 경우를 대비하여 정적(static)인 이름 보다 동적 (dynamic) 이름 지정을 사용하는 것이 바람직하다. 그렇다면 인스턴스 메소드 안에서 클래스 객체로 접근하려면 어떻게 할까? [self class] 라고 쓰면 현재 자신이 속한 클래스를 가리키게 된다.
메모리관리의 두가지 문제
1.메모리 누수
인스턴스를 생성하기 위해 메모리를 할당했지만 그것을 지시하는 포인터 변수가 사라져버리거나 사용이 완료되었으나 해제하지 않고, 그대로 메모리에 남겨두는 경우를 뜻한다. 이렇게 발생한 메모리 누수는 다른 메모리 요청에 쓰이지 못하고 계속 메모리에 남아 자원 낭비를 초래한다.
2. 무효 포인터 (댕글링 포인터)
인스턴스 객체가 해제 되었지만 포인터 변수는 계속 과거의 주소값을 가지고 있는 경우를 뜻한다. 만일 이 포인터 주소값으로 메시지를 전송하면 시스템 오류로 앱이 다운된다.
#import <Foundation/Foundation.h>
int main(int argc, const char *argv[])
{
@autoreleasepool
{
/*
[a] 변수 leak 이 지시한 객체의 메모리 누수 발생
*/
id temp;
id leak = [[NSObject alloc] init];
temp = [[NSObject alloc] init];
NSLog(@"%@ %@", leak, temp);
leak = temp;
NSLog(@"%@ %@", leak, temp);
/*
[b] 변수 dangling이 지시한 위치는 무효 포인터
*/
id dangling;
dangling = temp;
[temp release];
//temp = nil; // release 이후는 지시포인터 초기화
//dangling = nil; // 무효포인터 에러를 막기 위해서는 코멘트 제거
[dangling release];
NSLog(@"%@", dangling); //오류 발생
}
return 0;
}
프로그래머가 객체를 해제하여도 시스템은 동일 객체 만들때 다시 재활용될 수 있기 때문에 즉시 객체를 해제하지 않는다. 이런 이유로 해제된 객체에 메시지를 보내도 그대로 응답할 확률이 높다. 즉, 해제된 객체에 보통의 메시지를 보내도 이 메시지에 응답하기 때문에 이 객체가 해제되었는지 단정할 수 없다. 객체가 해제되었는지 확인해야 한다면, 한번더 [...release] 메시지를 보내 시스템 오류가 발생하는지 여부로 검사할 수 있다.
xocde 메모리 누수 체크
ARC 환경에서 지켜야 하는 규칙 1
1 메모리 관리용 메소드 호출의 제약
ARC에서는 retain/release 코드를 자동으로 삽입하기 때문에 사용자는 임의로 retain/release/ autorelease 메소드를 호출할 수 없다. 메모리 할당을 위해 alloc/init 만 호출할 수 있다.
2 dealloc의 오버라이드
dealloc 메시지는 재정의할 수 있지만 [super dealloc] 역시 컴파일러가 자동으로 삽입하기 때문에 사용자 가 넣어서는 안된다.
물론 예전과 같이 dealloc을 사용자가 호출하는 것은 금지되어 있다.
3 @property 구문에서 weak/strong으로 속성 변경
assign은 weak로, retain은 strong으로 변경되었다. weak 속성은 무효 포인터[dangling pointer, 댕글링 포인터] 문제를 막기 위해 지시하고 있는 객체가 해제되면 nil 값으로 설정된다.
4 NSAutoreleasePool은 @autoreleasepool로 교체되었다.
ARC 환경에서 지켜야 하는 규칙 2
•ARC 환경에서 객체를 지시하는 포인터는 strong과 weak 두가지가 있다. strong은 MRC의 retain과 유사하며, weak는 MRC의 assign과 유사하다. 모두 nil 값으로 초기화된다.
•strong 속성은 프로퍼티 구문의 옵션 속성이며, 변수에서는 __strong으로 기재한다.
•weak 속성은 프로퍼티 구문의 옵션 속성이며, 변수에서는 __weak로 기재한다. OS X 10.6과 iOS 4 이전 버전에서는 __unsafe_unretained 또는 assign으로 기재하였다.
•변수는 기본적으로는 strong 속성을 가진다. 이 변수에 특정 객체를 설정하면 객체에 자동으로 retain 메시지를 전송한다. strong 속성의 포인터 변수에 새로운 객체를 설정하면 자동으로 과거의 객체에 release 메시지를 전송한다. 이 과정을 통해 메모리 누수(memory leak)를 방지한다.
MRC 환경에서 메모리 관리 규칙
•인스턴스 포인터가 지시하는 객체를 지속적으로 사용하길 원한다면, 즉 오너십이 필요하면 retain 메시지를 전송한다. 사용이 완료되고 오너십을 반환하려면 release 메시지를 전송한다.
•인스턴스에 release가 전송된다고 해서 바로 삭제되지는 않는다. 인스턴스에 오너십을 가지는 모듈이나 객체가 없을 때 즉, 소유계수가 0이될 때 dealloc 메시지가 시스템에 의해 호출된다.
•만일 객체 안에 retain 또는 copy 속성의 프로퍼티 포인터가 있다면 이 포인터 객체는 retain 되 었거나 alloc, new, copy, mutableCopy 등으로 생성한 인스턴스를 가리키며 오너십을 가지게 된다. 이런 프로퍼티는 dealloc 동작에서 release 메시지를 전송하여 오너십을 반환하여야 한다.
•객체에 autorelease 메시지를 보내면 AutoreleasePool에 등록되고 이 pool이 제거될 때 함께 소유계수가 감소된다. 주로 처리 결과를 객체로 반환할 때 간편생성자(클래스 이름으로 시작)로 만들고 사용이 끝나면 자동 소멸하기 위해 사용한다. 만일 이를 유지하려면 retain을 전송한다.
•AutoreleasePool은 drain 또는 release 메시지를 받으면 자신에게 등록된 객체에 release 메시지를 전송한다. 각 객체들은 소유계수가 0이 되면 시스템에 의해 dealloc 되고 소멸된다.
•프로그램이 종료될 때는 모든 객체가 release 되어 최종적으로 dealloc 되어야 한다.
'아이폰 개발 > Objective C' 카테고리의 다른 글
NSObject 2 (0) | 2020.12.28 |
---|---|
NSObject 1 (0) | 2020.12.28 |
objective c 동적결합 (0) | 2020.12.26 |
objective c 상속과 클래스 (0) | 2020.12.25 |
objective c 프로그램 (0) | 2020.12.25 |