[Effective Objective-C] #34 오토릴리스 풀을 사용하여 최고 메모리 사용량을 낮춰라
출처 : Effective Objective-C
-
오브젝티브-C 의 Reference cout 아키텍처의 기능 중 하나는 오토릴리스 풀이다.
객체를 릴리스한다는 것은 release 를 호출하여 객체의 리테인 수를 즉시 줄이거나 autorelease 를 호출해 객체를 오토릴리스 풀에 추가하는 것을 말한다.
오토릴리스 풀은 미래의 특정 시점에 릴리스해야 할 객체의 집합으로 사용된다.
풀이 마르면, 그 때 풀의 모든 객체에 release 메시지를 보낸다.
@autoreleasepool {
// ...
}
-
객체에 autorelease 메시지를 보냈을 때 적절한 오토릴리스 풀이 없다면 콘솔(console)에서 다음과 같은 메시지를 보게 될 것이다.
Object 0xabcd0123 of class __NSCFString autoreleased with no pool in place - just leaking - break on objc_autoreleaeNoPool() to debug
-
그러나 보통은 오토릴리스 풀의 존재 유무를 걱정할 필요가 없다.
맥 OS X 또는 iOS 에서 동작하는 앱 모두 코코아 환경(iOS 의 경우 코코아 터치)에서 동작한다.
시스템이 여러분을 위해 생성한 스레드, 즉 메인 스레드(main thread) 또는 GCD 의 일부분으로 생성된 스레드는 암묵적으로 오토릴리스 풀을 가지고 있다.
이 오토릴리스 풀은 각 스레드가 이벤트 루프에서 벗어나면 ( 즉 스레드가 종료하면 ) 마른다.
그렇기 때문에 자신만의 오토릴리스 풀 블록을 만들 필요가 없지만 main 함수의 메인 앱 진입점을 감싸는 오토릴리스 블록을 가끔 볼 수 있다.
-
iOS 앱의 main 함수는 일반적으로 다음과 같다.
int main(int argc, char *argv[]){
@autoreleasepool{
return UIApplicationMain(argc, argv, nil, @“EOCAppDelegate”);
}
}
기술적으로 이 오토릴리스 풀 블록은 필요 없다.
블록이 끝나는 동시에 앱도 종료된다.
이 지점에서 운영 체제는 앱이 사용한 모든 메모리를 릴리스한다.
이 오토릴리스 풀이 없으면 UIApplicationMain 이 오토릴리스한 객체가 들어갈 풀이 없을 것이다.
그렇기 때문에 이 사실을 경고하는 로그를 보게 될 것이다.
그래서 이 풀은 외부 개치올 풀(catch-all-pool: 내부에서 잡히지 않은 나머지 모든 오토릴리스 객체를 담는 풀)로 여겨진다.
-
괄호는 오토릴리스 풀의 범위를 정의한다.
풀은 괄호 시작에서 생성되고 범위 끝(괄호 끝)에서 자동으로 마른다.
해당 범위의 모든 오토릴리스된 객체는 범위 마지막에 release 메시지를 받게 된다.
오토릴리스 풀은 중첩될 수 있다.
객체가 오토릴리스될 때 가장 안쪽에 있는 풀에 추가된다.
-
이 중첩 오토릴리스 풀은 앱의 최고 메모리 사용량을 제어할 수 있는 이점이 있다.
-
메서드가 수행이 끝난 뒤에도 이런 임시 객체들은 사용되지 않는다고 해도 계속 살아 있다.
그것들이 오토릴리스 풀 안에 있기 때문이다.
풀 안에 있는 객체들은 릴리스될 준비가 되었고 조만간 할당 해제될 것들이다.
그러나 풀은 스레드의 이벤트 루프를 벗어나기 전에는 마르지 않는다.
이는 for 루프를 실행하면 할수록 더 많은 객체가 생성되어 오토릴리스 풀에 추가된다는 이야기다.
최종적으로 루프가 끝나면 객체는 릴리스될 것이다.
그러나 루프가 실행되는 도중에는 앱의 메모리 사용량(memory footprint:프로그램이 실행 중에 직접 사용하거나 참조해서 사용하는 메인 메모리의 총량)이 끊임없이 증가할 것이다.
그리고 갑자기 모든 임시 객체가 릴리스되어 원래대로 줄어들 것이다.
이 상황은 이상적이지 않다.
특히 루프 횟수가 임의적이고 사용자 입력에 따라 달라진다면 더욱 그렇다.
-
내부에 오토릴리스 풀을 추가하면 루프가 실행되는 동안에도 앱의 최고 메모리 사용량은 낮게 유지될 것이다.
최고 메모리 사용량은 특정 기간동안 앱이 가장 많이 사용한 메모리 사용량을 나타낸다.
오토릴리스 풀 블록을 추가하면 메모리 사용량을 줄일 수 있다.
일부 객체들이 블록이 끝남과 동시에 할당 해제되기 때문이다.
-
오토릴리스 풀들은 스택으로 유지된다.
오토릴리스 풀이 생성되면 스택으로 넣어진다.
풀이 마르면 스택에서 꺼내진다.
객체가 오토릴리스되면 스택의 가장 위에 있는 풀에 넣어진다.
-
풀을 추가해서 최적화를 할 필요가 있는지 여부는 앱마다 다르다.
반드시 앱의 문제를 밝혀내기 위해 메모리 사용량을 모니터링하는 일을 우선적으로 해야 한다.
오토릴리스 풀 블록은 부하를 많이 발생시키지 않지만 부하가 완전히 없지도 않다.
그렇기 때문에 오토릴리스 풀이 필요 없는 상황이면 쓰지 않는 것이 더 좋다.
-
ARC 를 사용하지 않는 오브젝티브-C 프로그래머라면 NSAutoreleasePool 이라는 객체를 사용하는 예전 문법을 기억할 것이다.
이 특별한 객체는 일반 객체와는 다르고 블록 문법 같은 오토릴리스 풀을 표현하기 위해 만들어졌다.
그리고 주기적으로 말리는 방법으로 흔히 사용되었다.
이런 방식의 코딩은 더는 필요하지 않다.
ARC 는 새로운 문법과 함께 좀 더 가벼운 오토릴리스 풀을 도입했다.
그래서 루프의 n 번째 횟수마다 풀을 말리는 코드를 사용했다면 for 루프문을 감싸는 오토릴리스 풀 블록으로 대체할 수 있다.
이는 풀이 생성되고 매 반복마다 풀이 마르는 것을 의미한다.
-
@autoreleasepool 문법을 사용함으로써 생기는 또 다른 이점은 모든 오토릴리스 풀이 관련된 범위를 갖게 된 것이다.
이는 오토릴리스 풀이 말라서 할당 해제된 객체를 실수로 사용하는 것을 막는 데 도움을 준다.
기억할 점
오토릴리스 풀은 스택에 저장(arrange)된다.
오토릴리스 메시지를 받은 객체는 스택의 가장 위에 있는 풀에 추가된다.
오토릴리스 풀들을 올바르게 사용하면 앱의 최고 메모리 사용량을 낮추는 데 도움이 된다.
새로운 @autoreleasepool 문법을 사용하여 오토릴리스 풀을 쓰면 비용이 훨씬 저렴한다.
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Effective Objective-C] #36 retainCount 를 사용하지 말라 (0) | 2017.09.30 |
---|---|
[Effective Objective-C] #35 좀비를 이용해 메모리 관리 오류를 디버깅하라 (0) | 2017.09.29 |
[Effective Objective-C] #33 weak 참조를 사용하여 리테인 순환을 피하라 (0) | 2017.09.27 |
[Effective Objective-C] 목차와 요약을 통해 한 눈에 알아보는 Effective Objective-C #25 ~ #32 (0) | 2017.09.26 |
[Effective Objective-C] #32 안전한 예외 처리 코드를 작성하려면 메모리 관리를 주의 깊게 다루라 (0) | 2017.09.25 |
댓글