본문 바로가기
프로그래밍 놀이터/iOS

[Effective Objective-C] #34 오토릴리스 풀을 사용하여 최고 메모리 사용량을 낮춰라

by 돼지왕 왕돼지 2017. 9. 28.
반응형

 [Effective Objective-C] #34 오토릴리스 풀을 사용하여 최고 메모리 사용량을 낮춰라


출처 : Effective Objective-C

@autoreleasepool, autorelease pool, autoreleased with no pool in place, cath-all-pool, for 문, GCD, implicit autorelease pool, IOS, main thread, memory footprint, nsautoreleasepool, OSX, reference count, reference count architecture, [Effective Objective-C] #34 오토릴리스 풀을 사용하여 최고 메모리 사용량을 낮춰라, 메모리 사용량 모니터링, 메모리 사용량 제어, 스레드 종료, 스택, 암묵적 오토릴리스 풀, 앱의 메모리 사용량, 오토릴리스 풀, 오토릴리즈 풀, 이벤트 루프, 코코아, 코코아 터치


-

오브젝티브-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 문법을 사용하여 오토릴리스 풀을 쓰면 비용이 훨씬 저렴한다.




반응형

댓글