왜 @autoreleasepool이 ARC에 여전히 필요한가요?


191

ARC (Automatic Reference Counting)의 대부분은 Objective-C 객체를 사용하여 메모리 관리를 전혀 생각할 필요가 없습니다. NSAutoreleasePool더 이상 을 만들 수 없지만 새로운 구문이 있습니다.

@autoreleasepool {
    
}

내 질문은 수동으로 릴리스 / 자동 해제하지 않아야 할 때 왜 이것이 필요할까요?


편집 : 모든 anwers 및 의견에서 간결하게 얻은 것을 요약하면 다음과 같습니다.

새로운 구문 :

@autoreleasepool { … } 에 대한 새로운 구문입니다

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[pool drain];

더 중요한 것은:

  • ARC는 autorelease물론을 사용합니다 release.
  • 이를 위해서는 자동 풀 풀이 필요합니다.
  • ARC는 자동 릴리스 풀을 생성하지 않습니다. 하나:
    • 모든 Cocoa 앱의 메인 스레드에는 이미 자동 릴리스 풀이 있습니다.
  • 다음과 같은 두 가지 경우를 활용할 수 있습니다 @autoreleasepool.
    1. 보조 스레드에 있고 자동 릴리스 풀이없는 경우 등의 누출을 방지하기 위해 자체적으로 만들어야합니다 myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. @mattjgalloway가 그의 답변에 표시된 것처럼 더 많은 로컬 풀을 만들려면.

1
UIKit 또는 NSFoundation과 관련이없는 것을 개발할 때 세 번째 경우도 있습니다. 명령 줄 도구를 사용하는 것
Garnik

답변:


215

ARC는 유지, 릴리스 및 자동 릴리스를 제거하지 않고 필요한 것을 추가합니다. 따라서 계속 유지해야하는 통화가 있고, 해제 할 통화가 있으며, 자동 릴리스에 대한 통화가 있으며 자동 릴리스 풀이 있습니다.

그들은 새로운 연타 3.0 컴파일러와 ARC로 만든 다른 변화 중 하나는 대체한다는 것입니다 NSAutoReleasePool@autoreleasepool컴파일러 지시문. NSAutoReleasePool어쨌든 항상 특별한 "객체"였고 그것들을 사용하는 구문이 객체와 혼동되지 않도록하여 일반적으로 조금 더 간단했습니다.

따라서 기본적으로 @autoreleasepool여전히 걱정할 자동 릴리스 풀이 있기 때문에 필요합니다 . autorelease통화 추가에 대해 걱정할 필요가 없습니다 .

자동 릴리스 풀을 사용하는 예 :

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

확실히 고안된 예이지만 @autoreleasepool, 외부 for루프 내부가 없다면 외부 루프를 돌 때마다 10000이 아닌 나중에 100000000 개의 객체를 릴리스 할 것 for입니다.

업데이트 : ARC와 관련이없는 이유 는 https://stackoverflow.com/a/7950636/1068248- 이 답변을 참조하십시오 @autoreleasepool.

업데이트 : 나는 여기에서 무슨 일이 일어나고 있는지 내부를 살펴보고 내 블로그에 썼습니다 . 거기에서 살펴보면 ARC가 수행하는 작업과 새로운 스타일 @autoreleasepool및 범위를 도입하는 방법이 컴파일러에서 유지, 릴리스 및 자동 릴리스가 필요한 항목에 대한 정보를 유추하는 데 어떻게 사용 되는지 정확하게 알 수 있습니다.


11
보유를 제거하지 않습니다. 그것은 당신을 위해 그들을 추가합니다. 참조 카운트는 여전히 진행 중이며 자동입니다. 따라서 자동 기준 계수 :-D.
mattjgalloway

6
그렇다면 왜 @autoreleasepool나를 위해 추가하지 않습니까? 자동 릴리스 또는 릴리스되는 항목을 제어하지 않으면 (ARC가 자동으로 풀기) 언제 자동 릴리스 풀을 설정해야하는지 어떻게 알 수 있습니까?
mk12

5
그러나 자동 릴리스 풀이 어디로 가는지 제어 할 수 있습니다. 기본적으로 전체 앱을 감싸고 있지만 더 원할 수도 있습니다.
mattjgalloway

5
좋은 질문. 당신은 "알아야"합니다. 왜 하나를 GC 언어로 가비지 수집기에 힌트를 추가하여 계속해서 수집주기를 실행할 수 있는지에 대해 비슷한 생각을 추가하십시오. 어쩌면 정리할 준비가 된 많은 객체가 있다는 것을 알고, 많은 임시 객체를 할당하는 루프가 있으므로 루프 주위에 릴리스 풀을 추가하면 "알고"(또는 인스트루먼트는 말할 수 있음) 좋은 생각.
Graham Perks

6
루핑 예제는 자동 해제없이 완벽하게 작동합니다. 변수가 범위를 벗어날 때 각 객체의 할당이 해제됩니다. 자동 릴리스없이 코드를 실행하면 일정한 양의 메모리가 사용되며 포인터가 재사용되고, 객체의 할당 해제에 중단 점을두면 objc_storeStrong이 호출 될 때 루프를 통해 매번 호출되는 것으로 표시됩니다. 어쩌면 OSX는 여기서 어리석은 짓을하지만 iOS에서는 자동 릴리스 풀이 완전히 필요하지 않습니다.
Glenn Maynard

16

@autoreleasepool아무것도 자동 해제하지 않습니다. 자동 릴리스 풀을 작성하여 블록 끝에 도달하면 블록이 활성 상태 인 동안 ARC에 의해 자동 릴리스 된 모든 오브젝트에 릴리스 메시지가 전송됩니다. Apple의 Advanced Memory Management Programming Guide 는 다음과 같이 설명합니다.

자동 릴리스 풀 블록의 끝에서 블록 내에서 자동 릴리스 메시지를 수신 한 개체는 릴리스 메시지를 전송합니다. 개체는 블록 내에서 자동 릴리스 메시지가 전송 될 때마다 릴리스 메시지를받습니다.


1
반드시 그런 것은 아닙니다. 오브젝트는 release메시지 를 수신 하지만 보유 횟수가 1보다 크면 오브젝트의 할당이 해제되지 않습니다.
andybons

@andybons : 업데이트; 감사. 이것이 아크 이전 행동과 다른 것입니까?
outis

이것은 올바르지 않습니다. ARC에 의해 릴리스 된 객체는 자동 릴리스 풀의 유무에 관계없이 ARC에 의해 릴리스 되 자마자 릴리스 메시지를 보냅니다.
Glenn Maynard 님

7

사람들은 종종 가비지 수집 등의 이유로 ARC를 오해합니다. 진실은 애플의 사람들 (llvm과 clang 프로젝트 덕분에)이 Objective-C의 메모리 관리 ( retains그리고 모든 releases등등)가 컴파일 타임에 완전히 자동화 될 수 있다는 것을 깨달았다 는 것이다 . 이것은 코드가 실행되기 전에도 코드를 읽는 것입니다! :)

그렇게하기 위해서는 단 하나의 조건 만 있습니다 : 규칙을 따라야합니다 . 그렇지 않으면 컴파일러가 컴파일 타임에 프로세스를 자동화 할 수 없습니다. 그래서, 우리가 수 있도록 결코 규칙을 위반하지, 우리는 명시 적으로 쓰기에 허용되지 않습니다 release, retain등, 그 전화는 자동으로 컴파일러에 의해 우리의 코드로 주입된다. 따라서 내부적으로 우리는 여전히이 autorelease들, retain, release, 등 단지 우리는 더 이상을 작성하지 않아도됩니다.

ARC의 A는 컴파일시 자동으로 실행되므로 가비지 수집과 같은 런타임보다 훨씬 좋습니다.

우리는 여전히 @autoreleasepool{...}규칙을 위반하지 않기 때문에 언제든지 풀을 생성 / 배수 할 수 있습니다. :).


1
ARC는 JavaScript 및 Java에서와 같이 마크 앤 스윕 GC가 아닌 GC를 계산하는 참조 횟수이지만 가비지 수집입니다. 이 질문은 다루지 않습니다. "당신은 할 수 있습니다"는 "왜해야합니까"라는 질문에 대답하지 않습니다. 해서는 안됩니다.
Glenn Maynard 님

3

자동 해제 된 객체가 범위를 벗어나는 것이 안전한시기에 대한 힌트를 컴파일러에 제공해야하기 때문입니다.


언제해야하는지 예를 들어 주시겠습니까?
mk12


예를 들어 ARC 이전에 OpenGL 앱의 보조 스레드에서 CVDisplayLink를 실행하고 있었지만 자동 루프를 수행하지 않는다는 것을 알았 기 때문에 (또는 라이브러리를 사용하는 것) runloop에서 자동 릴리스 풀을 만들지 않았습니다. @autoreleasepoolARC가 자동 릴리스를 결정할 것인지 알 수 없기 때문에 추가해야 합니까?
mk12

@ Mk12-아니요. 메인 런 루프에서 매번 배수되는 자동 릴리즈 풀이 항상 있습니다. 자동 릴리스 된 객체가 그렇지 않은 경우 예를 들어 다음 번 실행 루프 라운드와 같이 배수되도록하려면 하나만 추가하면됩니다.
mattjgalloway

2
@DougW-컴파일러가 실제로하고있는 일을 살펴보고 여기에서 블로그를 작성했습니다 -iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /. 컴파일 타임과 런타임에 무슨 일이 일어나고 있는지 잘 설명하십시오.
mattjgalloway

2

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html 에서 인용 :

오토 릴리즈 풀 블록 및 스레드

Cocoa 응용 프로그램의 각 스레드는 자체 자동 릴리스 풀 블록 스택을 유지 관리합니다. Foundation 전용 프로그램을 작성 중이거나 스레드를 분리하는 경우 자체 자동 릴리스 풀 블록을 작성해야합니다.

응용 프로그램이나 스레드가 오래 지속되고 잠재적으로 많은 자동 릴리스 개체를 생성하는 경우 자동 릴리스 풀 블록을 사용해야합니다 (예 : 기본 스레드에서 AppKit 및 UIKit처럼). 그렇지 않으면 자동 해제 된 개체가 누적되고 메모리 공간이 늘어납니다. 분리 된 스레드가 Cocoa 호출을하지 않으면 자동 릴리스 풀 블록을 사용할 필요가 없습니다.

참고 : NSThread 대신 POSIX 스레드 API를 사용하여 보조 스레드를 생성하는 경우 Cocoa가 멀티 스레딩 모드에 있지 않으면 Cocoa를 사용할 수 없습니다. Cocoa는 첫 NSThread 객체를 분리 한 후에 만 ​​멀티 스레딩 모드로 들어갑니다. 보조 POSIX 스레드에서 Cocoa를 사용하려면 응용 프로그램이 먼저 하나 이상의 NSThread 객체를 분리해야합니다.이 객체는 즉시 종료 될 수 있습니다. NSThread 클래스 메소드 isMultiThreaded를 사용하여 Cocoa가 멀티 스레딩 모드인지 여부를 테스트 할 수 있습니다.

...

자동 참조 계수 또는 ARC에서 시스템은 MRR과 동일한 참조 계수 시스템을 사용하지만 컴파일시 적절한 메모리 관리 메소드 호출을 삽입합니다. 새 프로젝트에는 ARC를 사용하는 것이 좋습니다. ARC를 사용하는 경우 일부 상황에서는 도움이 될 수 있지만 일반적으로이 문서에 설명 된 기본 구현을 이해할 필요는 없습니다. ARC에 대한 자세한 내용은 ARC 릴리스 노트로 전환을 참조하십시오.


2

메소드에서 새로 작성된 오브젝트를 리턴하려면 자동 릴리스 풀이 필요합니다. 예를 들어 다음 코드를 고려하십시오.

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

메소드에서 작성된 문자열은 보유 횟수가 1입니다. 이제 석방과 카운트 밸런스를 유지하는 사람은 누구입니까?

방법 자체? 불가능한 경우, 작성된 오브젝트를 리턴해야하므로 리턴하기 전에 해제하지 않아야합니다.

메소드의 호출자? 호출자는 해제 해야하는 객체를 검색하지 않을 것이며 메소드 이름은 새 객체가 생성되었음을 암시하지 않으며 객체가 반환 되고이 반환 된 객체는 릴리스가 필요한 새 객체 일 수 있지만 그렇지 않은 기존의 것이어야합니다. 메소드가 리턴하는 것은 일부 내부 상태에 따라 다를 수 있으므로 호출자는 해당 오브젝트를 해제해야하는지 여부를 알 수 없으므로 신경 쓸 필요가 없습니다.

호출자가 규칙에 따라 반환 된 모든 개체를 항상 해제해야하는 경우 새로 생성되지 않은 모든 개체는 메서드에서 개체를 반환하기 전에 항상 유지해야하며, 범위를 벗어나면 호출자가 해제해야합니다. 다시 반환됩니다. 호출자가 항상 리턴 된 오브젝트를 해제하지 않는 경우 많은 경우 보유 계수 변경을 완전히 피할 수 있으므로 이는 많은 경우에 매우 비효율적입니다.

그렇기 때문에 자동 릴리스 풀이 있으므로 첫 번째 방법은 실제로

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

autorelease객체를 호출 하면 자동 릴리스 풀에 추가되지만 자동 릴리스 풀에 객체를 추가한다는 것은 실제로 무엇을 의미합니까? 음, 그것은 당신의 시스템에게 " 저는 당신을 위해 그 객체를 릴리즈하고 싶지만 나중에는 아니고, 지금은 아닙니다. 릴리즈에 의해 균형을 잡아야하는 유지 횟수를 가지고 있습니다. 그렇지 않으면 메모리가 누출 될 것입니다. 현재는 현재 범위를 넘어서도 살아 남기 위해 객체가 필요하고 발신자가 나에게도 도움이되지 않기 때문에이 작업을 수행해야한다는 사실을 알지 못하므로 풀에 추가하고 정리하면 수영장에서 물을 닦아주세요. "

컴파일러는 ARC를 사용하여 객체를 보유 할시기, 객체를 출시 할시기 및 오토 릴리즈 풀에 추가 할시기를 결정하지만 메모리 누수없이 메소드에서 새로 작성된 객체를 리턴 할 수 있도록 오토 릴리즈 풀이 있어야합니다. Apple은 생성 된 코드를 약간 최적화하여 런타임 중에 자동 릴리스 풀을 제거하는 경우도있었습니다. 이러한 최적화를 위해서는 발신자와 수신자 모두 ARC를 사용해야합니다 (ARC와 비 ARC를 혼합하는 것이 합법적이고 공식적으로 지원됨을 기억하십시오). 그렇다면 실제로는 런타임에만 알려질 수 있습니다.

이 ARC 코드를 고려하십시오.

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];

시스템이 생성하는 코드는 다음 코드와 같이 작동 할 수 있습니다 (즉, ARC 코드와 비 ARC 코드를 자유롭게 혼합 할 수있는 안전한 버전).

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(발신자의 유지 / 방출은 방어적인 안전 유지에 불과합니다. 엄격하게 요구되는 것은 아니며 코드가 없으면 완벽하게 정확합니다)

또는 둘 다 런타임에 ARC를 사용하는 것으로 감지되는 경우이 코드와 같이 작동 할 수 있습니다.

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];

보시다시피 Apple은 atuorelease를 제거하여 풀이 파괴 될 때 지연 된 객체 릴리스와 안전 유지 기능을 제거합니다. 이것이 가능한 방법과 실제로 어떤 일이 벌어지고 있는지에 대해 자세히 알아 보려면 이 블로그 게시물을 확인하십시오.

이제 실제 질문에 : 왜 하나를 사용 @autoreleasepool합니까?

대부분의 개발자에게 오늘날 코드에서이 구성을 사용하는 이유는 단 하나 뿐이며, 적용 가능한 경우 메모리 공간을 작게 유지해야합니다. 예를 들어이 루프를 고려하십시오.

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

호출 할 때마다 자동 릴리스로 리턴 tempObjectForData되는 새 항목 TempObject을 작성할 수 있다고 가정하십시오 . for-loop는 현재 자동 릴리스 풀에서 모두 수집되는 백만 개의 임시 객체를 생성하고 풀이 파괴 된 후에는 모든 임시 객체도 파괴됩니다. 그렇게 될 때까지, 당신은 메모리에 이러한 임시 객체들 중 백만 개를 가지고 있습니다.

대신 다음과 같은 코드를 작성하면

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

그런 다음 for-loop가 실행될 때마다 새 풀이 만들어지고 각 루프 반복이 끝나면 파괴됩니다. 이렇게하면 루프가 백만 번 실행 되더라도 최대 하나의 임시 객체가 언제든지 메모리에 걸려 있습니다.

과거 NSThread에는 메인 스레드 만 자동으로 Cocoa / UIKit 앱을위한 자동 릴리스 풀을 가지고 있기 때문에 스레드를 관리 할 때 (예 :을 사용할 때 ) 자동 릴리스 풀을 직접 관리해야하는 경우가있었습니다 . 그러나 오늘은 아마도 스레드를 사용하지 않을 것이므로 오늘날에는 거의 레거시입니다. 당신이 사용하는 거라고 GCD DispatchQueue의 또는 NSOperationQueue의 이러한 두 가지 모두가 함께 할 일단 블록 / 작업을 실행하기 전에 생성 및 파괴 당신을 위해 최고 수준의 오토 릴리즈 풀을 관리 할.


-4

이 주제에 대해 많은 혼란이있는 것 같습니다 (그리고 지금은 이것에 대해 혼란스러워하고 코드 주위에 @autoreleasepool을 뿌려야한다고 생각하는 80 명 이상).

프로젝트 (종속성 포함)가 ARC를 독점적으로 사용하는 경우 @autoreleasepool을 사용할 필요가 없으며 아무 것도 수행하지 않습니다. ARC는 정확한 시간에 객체 방출을 처리합니다. 예를 들면 다음과 같습니다.

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

표시합니다 :

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

자동 릴리스 풀이 종료 될 때까지 기다리지 않고 값이 범위를 벗어나 자마자 각 테스트 개체의 할당이 해제됩니다. (NSNumber 예제에서도 마찬가지입니다. 이것은 단지 할당 해제를 관찰 할 수있게합니다.) ARC는 자동 릴리스를 사용하지 않습니다.

@autoreleasepool이 여전히 허용되는 이유는 아직 ARC로 완전히 전환되지 않은 혼합 ARC 및 비 ARC 프로젝트에 대한 것입니다.

이 아닌 ARC 코드로 호출하는 경우, 그것은 오토 릴리즈 객체를 반환 할 수 있습니다. 이 경우 현재 자동 릴리스 풀이 종료되지 않으므로 위의 루프가 누출됩니다. 여기에서 코드 블록 주위에 @autoreleasepool을 넣고 싶습니다.

그러나 ARC 전환을 완료 한 경우 자동 릴리스 풀을 잊어 버리십시오.


4
이 답변은 잘못되었으며 ARC 문서에도 위배됩니다. 컴파일러가 자동 릴리스하지 않기로 결정한 할당 방법을 사용하고 있기 때문에 증거는 일화입니다. 사용자 정의 클래스에 대한 새 정적 초기화 프로그램을 작성하면 이것이 작동하지 않는 것을 쉽게 알 수 있습니다. 이 이니셜 라이저를 만들고 루프에서 사용하십시오 + (Testing *) testing { return [Testing new] }. 그러면 dealloc이 나중에까지 호출되지 않는 것을 볼 수 있습니다. 루프 내부를 @autoreleasepool블록으로 감싸면 고정됩니다 .
Dima

@Dima iOS10에서 시도, 오브젝트 주소를 인쇄 한 직후에 dealloc이 호출됩니다. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC

@ KudoCC-나도 그렇고, 당신과 같은 행동을 보았습니다. 그러나 내가 [UIImage imageWithData]방정식에 던져졌 을 때 갑자기 갑자기 전통적인 autorelease행동을 보았 기 때문에 @autoreleasepool피크 메모리를 합리적인 수준으로 유지 해야 했습니다.
Rob

@Rob 나는 나 자신이 링크를 추가하는 것을 도울 수 없다 .
KudoCC
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.