블로그 이미지
내게 능력 주시는 자 안에서 내가 모든것을 할수 있느니라 - 빌립보서 4 : 13 - happydong

카테고리

Happydong (1363)
프로그래밍 (156)
MUSIC (16)
인물 (3)
Utility (10)
세미나 소식&내용 (22)
IT뉴스 (18)
운동 (830)
CAFE (10)
Life (282)
Total
Today
Yesterday



애플리케이션 델리게이트 메서드들입니다. 

 

application:didFinishLaunchingWithOptions:

에를리케이션 객체의 생성이 끝난 후 가장 먼저 불리는 메소드

applicationDidEnterBackground:
에플리케이션이 활동을 중단하고 백그라운드로 들어갈 때 불리는 메소드다. 중요한 상태 정보는 여기서 저장해야 한다. 사용자나 시스템이 백그라운드 프로세스를 종료해버릴 수도 있다는 점을 명심하라

applicationWillTerminate:
애플리케이션이 백그라운드로 들어간 상태에서 단순히 일시 정지가 아닌 종료할 때 불리는 메소드다.

applicationWillEnterForeground:
애플리케이션이 백그라운드에 있다가 다시 전면으로 나올 때 불리는 메소드다. 이 메소드가 불리는 시점에서는 에플리케이션이 아직 실행을 재개한 상태가 아니다. 백그라운드로 돌면서 바뀐 내용을 화면에 반영하려면 이 메소드에서 처리해줘야 한다.

applicationDidBecomeActive:
애플리케이션이 실행을 재개할 때 불리는 메소드다.

출처 :  맥부기 iPhone OS 개발자 모임 카페 (http://cafe.naver.com/mcbugi?1297657847000)
Posted by happydong
, |



원문 : http://cafe.naver.com/mcbugi/71571

출처 : 맥부기 아이폰 카페




안녕하세요 문씨입니다

앞서 다룬 메모리 관리 강좌에서 부족한 부분인

@Property부분을 집중적으로 다루어 보겠습니다.


 프로퍼티는 외부에서 객체에 접근하기 위해 선언해서 정의해 주는 것입니다.

사실 프로퍼티가 없어도 문제는 없습니다.

그냥 setter, getter함수를 만들어 놓으면 되기 때문이죠


실제로 Foundation에 있는 NS계열 클래스 들을 보면

작성된지 오래된 클래스의 경우 프로퍼티 선언 없이 함수만으로 정의해 놓기도 했습니다.



그럼 프로퍼티에 대해 다루기 전에 기초로 돌아가 보겠습니다.

클래스가 하나 있습니다
@interface object : NSObject {
        NSString *testString;
}
@end

내부에는 전역 변수로 문자열 객체(testString)를 가지고 있습니다.


이것을 외부에서 접근 하려면 어떻게 해야 할까요?


보통 프로그래밍 수업을 들으면 자주 듣는 setter와 getter입니다.


그래서 구현을 해보면

@interface object : NSObject {
        NSString *testString;
}
- (NSString *)testString;
- (void)setTestString:(NSString *)newString;
@end

이런 식으로 읽는용(getter)과 쓰는용(setter)함수를 만들게 됩니다.


그리고 본문에서는

@implementation object 
- (NSString *)testString {
        return testString;
}
- (void)setTestString:(NSString *)newString {
        [testString release];
        testString = [newString retain];
}
@end

이런식으로 구현합니다.
getter인 testString함수는 내부의 testString객체를 돌려줍니다.

여기서 함수명인 testString은 그저 함수명이고  전역변수 testString은 그저 변수일 뿐입니다.

명칭이 같아서 혼란이 있을수 있습니다.
이 부분에 대해서는 잠시뒤에 다루고

먼저 setter인 setTestString에서 보면
기존의 testString객체는 release처리해서 더이상 안쓴다고 알리고
새로운 객체에 retain걸어서 지금부터 쓴다고 알리고 객체의 주소값을 저장합니다.


이런식으로 받은 객체의 리테인 카운트는 책임 진다는 규칙을 따르고 있습니다.


다만 지금 위의 클래스는 아직 문제가 있죠

위 객체가 해제될때 처리가 안되어 있군요
@implementation object 
- (NSString *)testString {
        return testString;
}
- (void)setTestString:(NSString *)newString {
        [testString release];
        testString = [newString retain];
}
- (void)dealloc {
       [testString release];
       testString = nil;
       [super dealloc];
}
@end

이제 됬습니다.
객체가 해제될때 호출되는 dealloc함수에서 전역으로 가지고 있던 모든 객체들을 깔끔히 해제 처리해줍니다.

이것으로 메모리 관리는 잘된 객체가 완성되었습니다.

근데 코드가 좀 많네요

조금만 줄여볼까요

@implementation object 
- (NSString *)testString {
        return testString;
}
- (void)setTestString:(NSString *)newString {
        [testString release];
        testString = [newString retain];
}
- (void)dealloc {
       [self setTestString: nil];
       [super dealloc];
}
@end

이러면 setTestString함수를 호출하면서 기존의 객체는 해제 시키고 nil값이 저장됩니다.
위에서 일일이 release쓰고 nil처리 해주는 작업을 한줄로 처리하는것이죠


그럼 외부에서는 어떻게 사용할까요?

object *test = [[[object alloc] init] autorelease];
객체를 생성합니다.

NSLog(@”%@”, [test testString]);
이러면 testString함수를 호출해서 내부 전역변수(testString)의 데이터를 가져와서 로그로 찍어볼수 있게됩니다.


그리고 이번엔 
[test setTestString: @”I am your father”];
지정해봤습니다.


이걸로 클래스의 setter와 getter가 구현되었습니다.


근데 이런식으로 구현하다보면 대부분 같은 작업인데 변수만 일일이 만들고 지정하는 것도 고욕이겠군요

무엇보다 외부에서 접근하기 위한 변수 한개당 함수 두개씩 해서 최소 
6줄 정도의 코드가 x 변수수 = 코드량 만큼 고생합니다.

다르거 개발하기도 바쁜데 setter, getter하느라 시간 다보내겠군요
누구나 그렇겠지만 귀찬은건 질색입니다.

Objective-C를 개발하는 개발자들도 같은 생각을 했을겁니다.

지금 구현해본 클래스를 보면
@interface object : NSObject {
        NSString *testString;
}
- (NSString *)testString;
- (void)setTestString:(NSString *)newString;
@end


@implementation object 
- (NSString *)testString {
        return testString;
}
- (void)setTestString:(NSString *)newString {
        [testString release];
        testString = [newString retain];
}
- (void)dealloc {
       [self setTestString: nil];
       [super dealloc];
}
@end


헤더에서 한가지를 위해 변수, setter, getter 세줄이 소모되고,
본문에서는 7줄이 소모됩니다. (상황에 따라 6줄일수도 있음)

이것들을 각각 헤더에서 1줄씩 총 두줄로 줄여버릴수가 있습니다.


어떻게 할까요?




이렇게요?
@interface object : NSObject {
        NSString *testString;
}
- (NSString *)testString; - (void)setTestString:(NSString *)newString;
@end


@implementation object 
- (NSString *)testString { return testString; }
- (void)setTestString:(NSString *)newString { [testString release]; testString = [newString retain]; }
- (void)dealloc {
       [self setTestString: nil];
       [super dealloc];
}
@end


이래도 많네요 
단순히 줄만 줄여 보려는것이 아닌 문자도 줄여보려는 것입니다.


그래서 설계된것이 Property입니다.
프로퍼티 기능을 사용한 코드로 바꾸어 볼까요?

@interface object : NSObject {
        
}
@property (nonatomic, retain) NSString *testString;
@end

@implementation object 

@synthesize testString;

- (void)dealloc {
       [self setTestString: nil];
       [super dealloc];
}
@end

어떤가요?
매우 간단해 졌죠 

질문이 많겠지만 일단 헤더부터 봅시다

@property

프로퍼티는 말 그대로 속성이라는 뜻입니다.

그리고 헤더에서 @property는 단순히
이 클래스에는 정의한 이름의 (testString) 속성이 있다고 알리는 역활일 뿐입니다.

변수를 만든다거나 어떠한 작업도 없습니다.

그리고 프로퍼티에 달린 조건에 따라 이 속성이 어떤것인이 알릴수 있습니다.

문법을 볼까요

먼저 프로퍼티라고 선언합니다
@property

그리고 ( 를 열고 이 프로퍼티의 설정을 합니다 물론 이 부분은 빠질수도 있습니다.
@property (

atomic OR nonatomic
이 두 속성중 하나를 선택하는 것으로 기본값은 atomic입니다. 이부분은 멀티스레딩에 관련된 부분으로 보통 nonatomic을 사용합니다. 자세한건 개발자 문서 참고
assign OR retain OR copy
setter에서 객체를 지정 받을때 
assign의 경우 주소값만 지정받고
retain의 경우 기존것을 release후 새로 받은걸 retain합니다.
copy의 경우 기존것을 release후 새로 받은걸 copy합니다.
이부분은 setter에 관련있고 getter와는 관련 없습니다.
readonly OR 없음
readonly설정되면 setter가 없습니다. 말그대로 읽기 전용이죠


그외에 몇가지 더 속성이 있지만 개발자 문서(영어)를 참고하세요


그래서 nonatomic에 retain으로 하고 readonly는 안한다면
@property (nonatomic, retain) 
그리고  )를 닫습니다.

그리고 객체 종류를 선언합니다
@property (nonatomic, retain) NSString

그리고 마지막으로 외부 접근 변수명을 지정합니다 
마치 변수 선언하듯요
@property (nonatomic, retain) NSString *testString;


여기까지 선언했지만 한가지 주의할 점은 이 시점에서는
프로퍼티로 선언한 testString은 클래스 내부의 전역 변수와 아무런 관련이 없다는 것입니다.

그러면 헤더에서 작업은 끝났습니다.

프로퍼티로 NSString타입의 testString이 있다고 알렸을 뿐입니다.




이제 본문을 보죠.
@synthesize testString;

@synthesize문으로 헤더에서 정의한것을 실체화 하는겁니다.

이 @synthesize문은 참 많은것을 합니다.

간단히 
@synthesize testString;이라 설정해주면

세가지 작업을 합니다.

1. 먼저 같은 이름으로 내부에 전역 변수를 만듭니다.
즉 testString이라는 변수를 만드는것이죠

단 이미 헤더에 전역 변수로 같은 이름이 선언 되어 있으면 그대로 그것을 사용합니다.

getter함수를 만듭니다.
선언된 이름과 같은 이름의 함수를 만들고 돌려주는 변수도 같은 이름이 됩니다.

readonly설정이 되어 있지 않다면 setter함수를 만듭니다.

눈에 보이지는 않지만 @synthesize문 하나로 일일이 해줘야 하는 귀찬은 작업들을 알아서 해주는겁니다.



그리고 프로퍼티로 선언된것은 함수로서 인식하기도 하지만
점 문법으로 인식하기도 합니다.
test.testString 이런식으로요


위에서 사용해봤던
NSLog(@”%@”, [test testString]);
[test setTestString: @”I am your father”];

이부분은 여전히 이대로 사용할수도 있지만

Objective-C개발팀도 []가 많아지는건 원치 않았나 봅니다

그래서 지원되는 점 문법으로

NSLog(@”%@”, test.testString);
test.testString = @”I am your father”;
이렇게 알아보기 편하게 되었습니다.

확실히 알고 있어야 하는것은 이렇게 쓰기 편하게 되었지만 결국은 함수라는 것입니다.

점 문법은 함수명을 점(.)을 사용해도 인식해주는 기능입니다.

어떤분은 release보낼때 보통
[object release];
하는데

object.release;

하는 분도 봤습니다..
개발 스타일이야 각자 몫이지만 어떻게 사용하든 확실히 알고 사용해야 합니다.


실제로 
test.testString = @”I am your father”;
이렇게 써놓아도

컴파일러에서는 
[test setTestString: @”I am your father”];
이렇게 인식합니다.

그래서 readonly걸려있으면 함수를 찾을수 없다는 에러를 띄웁니다
변수를 찾을수 없다곤 안합니다.





그러면 애플이 잘쓰는 겉과 속이 다는 기법을 다루어 보겠습니다.


외부에서는 testString으로 접근하지만 내부는 전혀 다른 변수명을 사용하는 경우입니다.


아이폰 앱 좀 개발해 보셨으면 많이들 써봤을 겁니다.

UIViewController

거기서 특히 title속성

헤더를 보면 
@property(nonatomic,copy) NSString *title;  
라고 선언해두었습니다.

근데 헤더를 잘보면 어디에도 NSString *title이라는 전역 변수는 안보입니다.
대신
NSString         *_title;
이라는 변수가 선언 되어 있죠


그냥 딱 봐도 프로퍼티와 연결되어 있다는 걸 알수 있습니다.


어떻게 했는지 보겠습니다

@interface object : NSObject {
        
}
@property (nonatomic, retain) NSString *testString;
@end
일단 헤더에 다른 전역 변수를 만듭니다
@interface object : NSObject {
        NSString *_TESTString;
}
@property (nonatomic, retain) NSString *testString;
@end

그리고 본문에서는
@implementation object 

@synthesize testString = _TESTString;

- (void)dealloc {
       [self setTestString: nil];
       [super dealloc];
}
@end


이렇게 하면 외부에서는 testString으로 접근하며 내부에서는 실제로 _TESTString객체를 사용하게되는겁니다.


그리고 해외 강좌 중에서는
@interface object : NSObject {
        NSString *_testString;
}
@property (nonatomic, retain) NSString *testString;
@end

@implementation object 

@synthesize testString = _testString;

- (void)dealloc {
       [self setTestString: nil];
       [super dealloc];
}
@end

이렇게 사용하기도 합니다만

앞서 언급했다시피
코딩하는 사람의 스타일 문제이고

이런식으로 하는것은 단순히 프로퍼티 명과 실제 변수명을 다르게 하여
혼동을 방지하기 위한 것입니다.





@synthesize를 사용하지 않는 방법도 있습니다.


프로퍼티 선언만 해두고 직접 함수를 구현하는겁니다.

@interface object : NSObject {
        NSString *_testString;
}
@property (nonatomic, retain) NSString *testString;
@end

@implementation object 

- (NSString *)testString {
       return _testString;
}
- (void)setTestString:(NSString *)newString {
       [_testString release];
       _testString = [newString retain];
}

- (void)dealloc {
       [self setTestString: nil];
       [super dealloc];
}
@end

이러면 복잡해 지기도 합니다만
이렇게 밖에 할수 없는 상황도 있습니다.

예를 들어 testString에 접근할때 아무것도 없을때 기본적으로 값이 있어야하는경우
- (NSString *)testString {
       if (_testString == nil) {
           _testString = [[NSString alloc] initWithString:@”my eye, my eye”];
       }
       return _testString;
}

이런 기법은 UIViewController가 view속성에 처음 접근할때 뷰가 자동 생성되는 방식과 동일하다고 할수있습니다.



그리고 @synthesize설정을 하고도 함수를 구현할수도 있습니다.
함수가 없으면 자동으로 만들고 있으면 그것을 사용하도록 하는것이죠
@interface object : NSObject {
        NSString *_testString;
}
@property (nonatomic, retain) NSString *testString;
@end

@implementation object 

@synthesize testString = _testString;
- (NSString *)testString {
       if (_testString == nil) {
           _testString = [[NSString alloc] initWithString:@”my eye, my eye”];
       }
       return _testString;
}

- (void)dealloc {
       [self setTestString: nil];
       [super dealloc];
}
@end



readonly의 경우 setter가 없기 때문에 전역 변수를 직접 해제 처리하는 수밖에 없습니다.



마지막으로

외부에서는 프로퍼티로 접근할수밖에 없지만
내부에서는 프로퍼티와 전역 변수가 존재하기 때문에

확실히 이해하고 사용하지 않으면 

이런 사태도 벌어집니다.

프로퍼티로 선언했으니 nil처리만 하면 되겠지 하는 분도 봤습니다.
- (void)dealloc {
      _testString = nil; X
       [super dealloc];
}

_testString = nil;
이것과
self.testString = nil;
이것은 다른겁니다.

혼동하지 마세요

전역 변수는 직접 release후 nil처리
[_testString release]; _testString = nil;

프로퍼티는 setter로 nil
self.testString = nil;
Posted by happydong
, |




원문 : http://cafe.naver.com/mcbugi/71504

출처 : 맥부기 아이폰 카페




안녕하세요 문씨입니다


이번 강좌에서는 제일 중요한 기초인 메모리 관리에 대해 다루어 보겠습니다.


본론에 들어가기 전에 Objective-C에 대해 잠시 알아보겠습니다.

*이 부분은 제 개인적인 소견이므로 잘못된 정보가 있을 수도 있습니다.


Objective-C는 C++과 마찬가지로 C에서 파생된 언어입니다.

다만 구조적 특징을 보면 C++보다 Objective-C가 좀더 C에 가깝습니다.


C++은 C에서 메모리 관리를 직접 하는 포인터 개념에 충실한 반면 Objective-C는 메모리 관리를 어느정도 대신 해주는 개념을 가지고 있습니다.


저는 Objective-C가 가장 편합니다. (그동안 많이 써서 그렇겠지만;;;) 그리고 모바일의 제한적 성능 환경에 본의 아니게 가장 어울리는 언어라고 보고있습니다.


언어를 대체적으로 C나 C++, Java, Objective-C로 세분류로 나누고 본다면

일단 C나 C++은 개발하기가 어렵습니다. 하지만 메모리 관리를 직접할수 있기 때문에 고수가 개발한다면 정말 강력한 앱을 만들수도 있습니다.

Java는 메모리 관리를 신경 안쓰는 언어입니다. 덕분에 개발은 편하죠. 다만 메모리 관리하는 가비지콜렉션 때문에 모바일 환경에서는 상당히 느립니다. 

*가비지콜렉션은 시스템상의 메모리를 전부 읽어서 안쓰는 메모리는 제거해 주는 개념입니다.


데스크탑이라면 별 의미없지만 모바일이라는 환경에서는 차이가 납니다.

그래서 직접 메모리를 관리 하는 방법 VS 가비지콜렉션으로 메모리 관리 라는 두 구도가 생성됩니다만 어느쪽이든 사용하기는 힘든 감이 있죠. 


직접 메모리 관리는 너무 개발하기 힘들고 Java는 느려서 원하는 성능을 내기가 힘듭니다.

후에 모바일 기기의 성능이 PC급으로 올라가면 얘기가 달라지겠지만요.


그렇다면 개발하기는 편하면서 가비지컬렉션을 사용하지 않는 방법은 없을까 하는 질문에 등장하는것이 Objective-C입니다.


물론 Objective-C도 가비지콜렉션은 지원합니다. 하지만 모바일 기기인 아이폰이나 터치, 아이패드 등에서는 지원하지 않습니다. 즉 OSX에서는 지원하지만 iOS에서는 지원하지 않습니다.


애플도 가비지콜렉션의 무거움을 알기때문에 일부러 지원을 안했다고 합니다.


Objective-C에서는 리테인 카운트(retain count)기법을 사용합니다. 해당 객체를 몇군데에서 사용하고 있는지 그것을 수치로 가지고 있는겁니다. 그리고 아무곳에서도 사용하지 않을때 지워지는 개념입니다.


개념만 본다면 더이상 사용하지 않는 객체를 찾아서 지우는 가비지콜렉션과 유사하기도 합니다. 다른점이라면 가비지콜렉션은 시스템에 기생해서 메모리는 체크하는 방식이지만 Objective-C는 로직으로 제거되는 방식입니다.

즉 Java에서는 객체를 쓰고 뒤처리를 안하지만 Objective-C에서는 반드시 뒤처리를 합니다.

객체를 사용후 해당 객체에게 이곳에서는 사용을 중지했다고 알려주는겁니다. 


Objective-C에서 모든 객체들의 순환(Life Cycle)을 이해 하려면 우선 NSObject를 알아야 합니다.

모든 객체는 NSObject를 상속 받았기 때문이죠. 그래서 NSObject를 알면 메모리관리를 알게 됩니다.

NSObject의 NS는 Next Step의 약자이고 Object는 말 그대로 객체를 뜻합니다.


먼저 NSObject의 헤더를 보겠습니다.


클래스 선언 부분인 @interface NSObject를 보면 최상위의 클래스 이기때문에 상속받는 부모클래스가 없습니다. 보통 @interface test : NSObject 식으로 부모클래스 선언이 반드시 있습니다. 물론 없으면 에러 경고를 띄우기도 합니다.


그리고 선언된 함수들을 보면 +와 -가 있습니다. +의 경우 클래스 함수(Class Method)라 하고 -는 인스턴트 함수(Instance Method)라 합니다. 클래스를 도장이라고 본다면 인스턴트는 도장으로 찍어서 만든 것이라 할수 있습니다. 클래스도 주소가 존재하기 때문에 객체로 취급 받기도 합니다. 물론 클래스는 단일 존재라 단 하나만 존재합니다. 그리고 인스턴트는 이 클래스로 원하는 만큼 만들어 낼수가 있습니다,


그럼 예를 보겠습니다.


NSObject *object = [NSObject alloc];


object라는 변수에 NSObject라는 클래스에 alloc이라는 클래스 함수를 호출해서 새로운 인스턴트 객체를 만든후 그 객체의 주소를 object변수에 저장했습니다.


그럼 다른 예를 보겠습니다.


NSArray *array = [NSArray alloc];


array라는 객체변수에 NSArray라는 클래스에 alloc이라는 클래스 함수를 호출해서 새로운 NSArray인스턴트 객체를 만들고 그 주소를 array에 지정합니다.


객체 생성은 반드시 alloc이라는 클래스 함수를 호출해서 원하는 객체를 찍어내는 겁니다.


모든 클래스는 NSObject를 상속받고 있기 때문에 alloc함수는 어디는 존재하게 되는겁니다.


객체를 생성했으면 이번에는 초기화를 해줍니다.


NSArray *array = [NSArray alloc];

[array init];


위에 헤더에도 있는 init은 기본 초기화 함수입니다. init은 인스턴트 함수이기 때문에 객체 생성후 최초로 객체에 호출되는 함수입니다.


단 위의 코드는 문제가 있습니다. 애플의 가이드 문서도 위와 같은 사용은 금지하고 있습니다.


왜그럴까요? 


NSArray *array = [NSArray alloc]; 에서 객체를 생성한것까진 좋습니다.

문제는 [array init]; 입니다.


위 코드는 array에 init이라는 메세지만 날려줄뿐입니다.


init을 날렸는데 객체가 판단하기에 뭔가 잘못된 것이 있으면 객체가 스스로 해제 되기도 합니다.


위의 코드는 예문이라 그럴일은 절대 없지만 다른 코드에서는 그럴일도 있을수 있습니다.

init함수는 초기화가 제대로 되면 자기자신의 주소를 돌려주고 잘못되면 스스로 해제를 한 후 nil(0값)을 돌려줍니다.


즉 위의 코드는 init만 날리고 돌려주는 값에 대한 대처가 안되어 있기 때문에 array변수가 가지고 있는 객체의 주소는 쓰레기가 되어버립니다.


즉 

NSArray *array = [NSArray alloc];

array = [array init];

아니면

NSArray *array = [[NSArray alloc] init];

을 사용합니다


결국 코드의 간결화를 위해 후자를 사용하게 됩니다.


이제 초기화를 했으면 해제를 해야합니다.


NSObject헤더에 인스턴트 함수중에 release와 autorelease가 있습니다.

autorelease는 잠시 후에 다루고 일단 release부터 봅니다.


만든 객체에 release를 날려주면 객체는 해제가 됩니다.


[array release];


단 여기서 많은 분들이 해매는 개념이 있습니다. 저 또한 이해하기 전까진 많이 해맸습니다.

release는 객체를 완전히 제거하는 명령이 아닌 단순히 이곳에서는 이 객체를 더이상 쓰지 않는다고 알려주는 함수입니다.

NSObject에는 객체를 생성하는 함수는 있지만 완전 제거하는 함수는 존재하지 않습니다.

C에서 본다면 free문이 없다는 것입니다.

대신 NSObject에는 retainCount라는 개념이 있습니다. 

alloc으로 처음 생성될때 1이라는 수치로 시작됩니다.

그리고 release라는 함수가 호출되면 내부에 retainCount를 하나 줄입니다.

위와 같은 경우는 1에서 하나 줄어서 0이 됩니다.

그러면 객체는 자동으로 메모리 상에서 제거가 됩니다.


NSArray *array = [[NSArray alloc] init]; //1

[array release]; //0


Objective-C에서 메모리 누수란 이 retain count가 0이 안되고 남아있어서 벌어지는 문제입니다.


그러면 release가 있으니 반대도 있습니다.

retain이라는 함수가 그 반대 역활을 합니다.


NSArray *array = [[NSArray alloc] init]; // 1

[array retain]; //2

[array release]; //1

[array release]; //0


리테인 카운터의 개념 및 규칙은 “올린만큼 내려라” 입니다.

간단하죠?

근데 머리로는 이해 하겠는데 정작 코딩으로 하려고 하면 다시 해맵니다.

그리고 또 autorelease는 뭐고요.

autorelease의 존재는 정말 코딩을 편하게 해주는 기능입니다.

지금 이대로 autorelease없이 코딩한다면 위와 같이 전부 객체 생성을 한후 사용이 끝난 부분에서 일일이 전부 release를 날려줘야 합니다.


코딩 하다보면 사용하는 변수가 점점 많아지는데 이것들은 언제 일일이 release하나요.


오토릴리즈(autorelease)를 사용안한다면

NSArray *array = [[NSArray alloc] init];

... 한참 사용후

[array release];

이런식으로 사용하겠지만


오토릴리즈를 사용한다면

NSArray *array = [[[NSArray alloc] init] autorelease];

...사용


그리고 사용후에는 release를 신경 쓸 필요가 없습니다. 

왠지 일일이 뒤처리 할필요가 없어진듯합니다.

마치 Java같군요.

오토릴리즈라 한다면 언젠간 자동으로 해제된다는 것인데

어떤 원리 일까요?

오토릴리즈는 편한 존재이지만 그 원리는 이해 못하기 때문에 오히려 처음 배우는 사람에게는 혼란을 일으킵니다. 

언제 해제될까? 라는 궁금증이 이 함수에 대한 신뢰를 잃게 되는것이죠.

그래서 불편하더라도 확실히 생성하고 확실히 릴리즈를 날리는 코더를 많이 볼수 있었습니다.

오토릴리즈는 오토릴리즈 풀(NSAutoreleasePool)과 연결되어 있습니다.


생성된 객체에 autorelease를 날려주면 해당 객체는 자동으로 오토릴리즈 풀에 등록됩니다.

해제를 시켜주는 것이 아닌 등록입니다. 다른 말로 수영장(pool)에 빠트려 놓는것이죠

그리고 특정 시간이 지나면 그 수영장은 물을 빼고 청소를 합니다.

그때 전에 빠진것들은 다 릴리즈 처리가 되면서 해제되는 것이죠

그럼 NSAutoreleasePool이 모아서 한꺼번에 release처리해준다는 것은 알았습니다.

그러면 언제 그런일(drain)이 일어날까요?

저는 일단 프로세스가 이벤트 루프로 돌아갈때 일어난다고 보고있습니다.

앱이 실행중일때 아무것도 안하고 있다면

메인 스레드는 이벤트 루프에서 무한 루프를 돌고 있습니다.

그러다 어떠한 이벤트가 발생한다면 그 발생한 이벤트에 따라 함수를 호출합니다.

그리고 그 함수가 끝나면 다시 이벤트 루프로 돌아옵니다.

그리고 실행된 함수는 혼자서 일하고 끝나기도 하지만 작업을 위해 함수가 다른 함수를 부르는 일이 반드시 생깁니다.

예를 하나 들겠습니다.

이벤트 루프에서 돌고 있다가 유저가 화면에 터치 했습니다.

그러면 이벤트 루프는 UIApplication객체에 터치 이벤트가 있음을 알리는 메세지를 날립니다. 

*보통 Objective-C에서 메세지를 날린다고 하면 해당 객체에 함수를 호출한다고 보면 됩니다.

그럼 앱은 최상위 화면인 UIWindow에 메세지를 보내고

UIWindow는 위에 올려진 뷰에 메세지를 보낼것이고

올려진 뷰는 올려진 뷰위에 올려진 뷰에 메세지를 보내서

가장 상단에 올려진 뷰까지 메세지가 갔다올것입니다.

즉 함수가 함수를 호출하는 상황입니다.

그래서 그사이에 생성되는 오토릴리즈된 객체들은 해제되지 않고 있다가

일을 마치고 이벤트 루프로 돌아오면 전부 해제 처리되는겁니다.

결국 함수내에서 autorelease시킨 객체들이 함수 진행도중 해제 될까봐 걱정할 일은 없습니다.

그럼 하나의 함수내에서 만들어 쓰고 버리는 객체들은 release를 사용할 일이 없겠군요.

전부 오토릴리즈로 처리하면 되니까요. 

그렇습니다. 근데 더 편하게 쓰게 도와주는것들이 있습니다.

NextStep시절부터 개발되어 오던 모든 클래스들은 한가지 규칙을 지키고 있습니다.

“객체를 돌려주는 함수는 반드시 오토릴리즈된 객체를 돌려준다”는 것입니다. 

위에서 언급했던 클래스 함수들.

한가지 예를 들어보죠.

NSArray *array = [[[NSArray alloc] init] autorelease];


저처럼 [[[ <- 이거에 익숙해진 분들이라면 별 문제 없겠지만 이것들이 많아진다면

그것도 나름데로 스트레스입니다.


NSArray같은 경우는 array라는 클래스 함수를 제공합니다.


함수로 돌려주는 객체는 반드시 오토릴리즈 된 객체이다 라는 규칙을 따르기 때문에

NSArray *array = [NSArray array]; 

이 코드는 위의 alloc init autorelease를 사용한 코드와 같은 의미 입니다.


이렇게 기본적으로 Foundation에 내장되어 있는 NS계열의 객체들이나 아이폰부터 추가된 UIKit등 모든 클래스들이  alloc이 아닌 특별히 제공하는 클래스 함수들은 전부 오토릴리즈 된 객체를 생성합니다.


제가 몇번 봐드린 코드 중이 이런 코드도 있었습니다.

1: NSString *str = [[NSString alloc] initWithString:@”test”];

2: str = [str uppercaseString];

3: [str release];


이러면 에러가 나고 튕긴다는 문제였습니다.

1에서 오토릴리즈 안된 객체를 생성합니다.

2에서  str에 기존의 문자열을 대문자로 바꾼 새로운 개체를 돌려주는 함수를 실행해서 돌려준 새로운 오토릴리즈된 객체의 주소를 다시 str에 지정합니다.

이시점에서 기존의 str객체의 주소를 잃어버리게 되는겁니다. 당연 메모리 누수가 납니다.

그리고 str는 전부 대문자로 변환된 새로운 객체를 가지고 있게 됩니다

하지만 오토릴리즈된 객체라 3에서 release를 날리면 당연 문제가 됩니다.

여기서 놓친 부분은 uppercaseString함수가 새로운 오토릴리즈된 객체를 만들어서 돌려준다는 점입니다.


 그럼 alloc의외에 클래스 생성 함수에 의해 만들어지는 객체들은 전부 오토리리즈된 객체란것은 알았습니다.

그런데 아직도 이해가 안되는 부분이 있습니다.


NSArray같은 데이터 관리 객체는 어떻게 하나요?


NSArray보다 NSMutableArray를 예로 들겠습니다


NSMutableArray에는 객체를 추가하는 함수가 있습니다


addObject:인데

하나 예를 들죠


NSString *str = @”test”;

일단 문자열 객체를 준비합니다

NSMutableArray *array = [NSMutableArray array];

더이상 alloc init을 볼일이 없이 바로 오토릴리즈 된 배열 객체를 준비합니다.


[array addObject:str];


array 객체에 str을 넣었습니다.

간단히 생각하면 넣었다 라고 보겠지만

실제로 메모리 관리는 어떻게 될까요?


NSMutableArray는 객체를 새로 추가 받는 함수가 실행되면 자동으로 그 객체에게 retain 함수를 날립니다.


즉 여기서는 더이상 str을 쓸일이 없더라도 array는 str을 받은 이상 계속 가지고 있어야할 책임이 생긴겁니다.


리테인 카운트를 올려두었기 때문에 현재 코드에서 더이상 안쓴다 해도 객체는 유지됩니다.

하지만 별로 신경쓸 문제는 아닙니다.

NSMutableArray클래스는 객체를 받으면 책임 지기때문에 해제또한 책임 집니다.


즉 array가 오토 릴리즈 상태이기때문에 나중에 해제 될때

가지고 있던 모든 객체에게 다 release를 날려주게 됩니다.


근데 넣어둔 객체 메모리 관리를 한답시고 직접 release를 날려버리면

해당 str객체는 해제 되겠지만 array는 객체가 있는줄 알고

쓰레기 주소에 메세지를 때렸다가 베드 엑세스 에러가 나고맙니다.


결국 자기가 받은 객체는 자신이 책임 지는 구조입니다.


그렇다면 클래스에 선언한 전역 변수는 어떻게 될까요?


함수 내부에서 만들어 쓰고 끝나는 것들은 전부 오토릴리즈로 사용하겠지만 전역변수는 다릅니다. 더이상 안쓸때까지 retainCount를 하나 올려둬야 합니다.
그리고  객체가 해제될때 호출되는 dealloc함수에서 해당 전역 변수의 객체들을 다 해제 시켜줍니다.

처음 생성할때 alloc init으로 가지고 있어도 되고, 아니면 다른 함수를 통해 오토릴리즈된 객체를 가지고 있다면 이럴때 retain을 사용하면 됩니다.


title = [[NSString alloc] initWithString:@”tes”];

OR

title = [[커스텀클래스 getTitle] retain];


그리고 

- (void)dealloc {

[title release];

...


즉 객체가 해제될때 가지고 있던 객체들도 같이 해제 해주는겁니다.


그럼 전역 변수도 알았습니다.

프로퍼티는 무엇일까요?


여기까지 이해했다 하더라도


object.title = ?


식의 코드를 보면 또다시 해맵니다.


1. object.title = [[NSString alloc] initWithString:@”Test”];

2. object.title = @”Test”;

3. object.title = [NSString stringWithString:@”Test”];

위의 1,2,3중 어느것이 맞다고 봅니까?


2, 3번이 맞는겁니다. 보통 문자열의 경우 2번이 제일 간단하면 3번과 별차이 없기때문에 2번을 사용합니다.


위와같은 점문법은 보통 프로퍼티(속성)로 정의되어 있는 경우입니다.


프로퍼티는 C++배울때 많이 듣던 setter, getter입니다.


즉 외부에서 해당 객체의 내부 변수에 접근할수 있도록 접근용 함수를 만드는 것입니다.


프로퍼티 선언은 먼저 헤더에서 합니다.


그리고 본문에서 @synthesize 선언을 해주면 정의한 속성이 설정됩니다.


그리고 위 선언을 하면 컴파일러에서 자동으로 

- (NSString *)title이라는 getter함수와

- (void)setTitle:(NSString)이라는 setter함수가 자동 생성됩니다.


그리고 프로퍼티 설정에서 주의해야 할 부분이 있습니다.

retain이라는 부분인데 이부분이 retain, assign,  그리고 copy일수 있습니다.


getter함수는 단순히 그 변수를 돌려주면 되기때문에 특별한 조치는 없고

setter함수가 위 세가지 설정에 따라 내용이 달라집니다.


먼저 retain


retain이 설정되어 있으면 기본적으로 setter에서 기존의 title에 뭔가 있을 경우를 대비에 release를 먼저 날려주고 새로운 객체를 retain걸어주면서 받습니다.


assign의 경우


리테인 카운트에 아무런 영향 없이 객체의 주소만 지정하게 됩니다.

보통 델리게이트 등 객체의 주소만 지정할때 사용됩니다.


마지막으로 copy 는 retain과 비슷하지만 retain부분 대신 copy가 들어가서 객체를 복사할때 사용됩니다.


객체.title라고 써서 읽어 오는것은 [객체 title]로 써있는것과 같은 역활을 하고

객체.title = ? 식으로 지정하는것은 [객체 setTitle:?]식으로 설정 함수를 호출하는 것과 같습니다.


setter의 경우 앞서 다루어 봤던 NSMutableArray에서 addObject와 비슷하죠?


즉 속성으로 호출되는 것들은 다 함수입니다.

변수라고 착각을 하기때문에 위에서 1번과 같은 경우가 생기는 겁니다.


프로퍼티가 retain이나 copy인 경우 dealloc에서

  1. title = nil;
  2. [self.title release]


어느쪽이 맞다고 생각합니까?


둘다 틀렸습니다.

1번은 전역인데 release안하고 넘어가서 문제가 되고

2번은 title함수로 불러온 title객체에 release를 때려서 객체는 해제 되었지만

title객체변수는 여전히 주소를 쥐고 있어서 문제가 될 여지가 있습니다


맞는 방법은

[title release]; 

title = nil;

아니면

self.title = nil;

입니다


후자의 경우

[self setTitle:nil]; 이 실행된 경우인데


위 retain 경우 함수를 보면

먼저 기존에 가지고 있던 객체에는 release를 보냅니다

문제없이 해제되고 그다음

title = [newTitle retain];에서 newTitle이 nil값이기때문에

title = [nil retain];이 됩니다


재미있는 점은  nil에는 무슨 메세지를 날려도 아무런 문제가 없습니다

0 x 어떤 숫자든 = 0인 원리


즉 title = nil이 됩니다


자 그럼 정리해 보겠습니다.


  1. 함수내에서 쓰는 객체들은 전부 오토릴리즈(autorelease)로 한다.
    1. alloc init 과 release무더기면 코드만 해맨다
  2. 전역 변수는 지정할때 리테인카운트를 하나 올린고 안쓸때 해제한다.
    1. 계속 가지고 있을것이니 retain, 더이상 안쓸때는 release!
  3. 전역 변수에 객체를 사용하다가 해제(release)를 하면 미련없이 nil처리 (변수 = nil;)하자
    1. release하고나면 반드시 변수 = nil; 해서 이후에 실수로 접근해도 문제없다.
  4. 프로퍼티는 전역 변수가 아니다!
    1. self.title과 title, 헤깔리지 말자.
  5. 다른 객체에 전달하는거, 알아서 하겠지. 신경쓰지 말자.
    1. NSMutableArray같은 관리 객체에 넣어둔거 해제될때 알아서 내용도 해제 시킨다.
  6. retainCount함수로 일일이 확인해보지 말자 괜히 헤깔린다.
    1. 상황에 따라 예상보다 더 나오는 경우도 있습니다 그만큼 더 해제 시킨다고 해매지 마세요.
  7. 내가 만든 함수도 객체를 돌려줄땐 오토릴리즈로 돌려주자
    1. 규칙!

특의한 경우로 NSTimer같은 경우는 오토릴리즈 되어 있어도 타이머가 돌고있는 상태에는 시간이 지나도 해제를 안합니다 타이머가 정지되면 동시에 해제되어 버립니다.

리테인 했었으면 릴리즈 해주고 안했었으면 그냥 nil처리 해주면 무난합니다.


마치며...


Objective-C는 이름 그대로 객체 지향에 특화된 언어입니다.

객체간의 서로가 왠만해선 참견 안하는것이 규칙으로 되어 있습니다.

필요한 호출만 서로 해주고 내부는 어떻게 돌아가는지 신경안쓰는 것이죠.


차를 그저 운전할뿐이지 엔진 설계도 까지 읽어보나요?

Posted by happydong
, |