ARC (Automatic Reference Counting)의 대부분은 Objective-C 객체를 사용하여 메모리 관리를 전혀 생각할 필요가 없습니다. NSAutoreleasePool
더 이상 을 만들 수 없지만 새로운 구문이 있습니다.
@autoreleasepool {
…
}
내 질문은 수동으로 릴리스 / 자동 해제하지 않아야 할 때 왜 이것이 필요할까요?
편집 : 모든 anwers 및 의견에서 간결하게 얻은 것을 요약하면 다음과 같습니다.
새로운 구문 :
@autoreleasepool { … }
에 대한 새로운 구문입니다
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
…
[pool drain];
더 중요한 것은:
- ARC는
autorelease
물론을 사용합니다release
. - 이를 위해서는 자동 풀 풀이 필요합니다.
- ARC는 자동 릴리스 풀을 생성하지 않습니다. 하나:
- 모든 Cocoa 앱의 메인 스레드에는 이미 자동 릴리스 풀이 있습니다.
- 다음과 같은 두 가지 경우를 활용할 수 있습니다
@autoreleasepool
.- 보조 스레드에 있고 자동 릴리스 풀이없는 경우 등의 누출을 방지하기 위해 자체적으로 만들어야합니다
myRunLoop(…) { @autoreleasepool { … } return success; }
. - @mattjgalloway가 그의 답변에 표시된 것처럼 더 많은 로컬 풀을 만들려면.
- 보조 스레드에 있고 자동 릴리스 풀이없는 경우 등의 누출을 방지하기 위해 자체적으로 만들어야합니다
답변
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
및 범위를 도입하는 방법이 컴파일러에서 유지, 릴리스 및 자동 릴리스가 필요한 항목에 대한 정보를 유추하는 데 어떻게 사용 되는지 정확하게 알 수 있습니다.
답변
@autoreleasepool
아무것도 자동 해제하지 않습니다. 자동 릴리스 풀을 작성하여 블록 끝에 도달하면 블록이 활성 상태 인 동안 ARC에 의해 자동 릴리스 된 모든 오브젝트에 릴리스 메시지가 전송됩니다. Apple의 Advanced Memory Management Programming Guide 는 다음과 같이 설명합니다.
자동 릴리스 풀 블록의 끝에서 블록 내에서 자동 릴리스 메시지를 수신 한 개체는 릴리스 메시지를 전송합니다. 개체는 블록 내에서 자동 릴리스 메시지가 전송 될 때마다 릴리스 메시지를받습니다.
답변
사람들은 종종 가비지 수집 등의 이유로 ARC를 오해합니다. 진실은 애플의 사람들 (llvm과 clang 프로젝트 덕분에)이 Objective-C의 메모리 관리 ( retains
그리고 모든 releases
등등)가 컴파일 타임에 완전히 자동화 될 수 있다는 것을 깨달았다 는 것이다 . 이것은 코드가 실행되기 전에도 코드를 읽는 것입니다! 🙂
그렇게하기 위해서는 단 하나의 조건 만 있습니다 : 규칙을 따라야합니다 . 그렇지 않으면 컴파일러가 컴파일 타임에 프로세스를 자동화 할 수 없습니다. 그래서, 우리가 수 있도록 결코 규칙을 위반하지, 우리는 명시 적으로 쓰기에 허용되지 않습니다 release
, retain
등, 그 전화는 자동으로 컴파일러에 의해 우리의 코드로 주입된다. 따라서 내부적으로 우리는 여전히이 autorelease
들, retain
, release
, 등 단지 우리는 더 이상을 작성하지 않아도됩니다.
ARC의 A는 컴파일시 자동으로 실행되므로 가비지 수집과 같은 런타임보다 훨씬 좋습니다.
우리는 여전히 @autoreleasepool{...}
규칙을 위반하지 않기 때문에 언제든지 풀을 생성 / 배수 할 수 있습니다. :).
답변
자동 해제 된 객체가 범위를 벗어나는 것이 안전한시기에 대한 힌트를 컴파일러에 제공해야하기 때문입니다.
답변
오토 릴리즈 풀 블록 및 스레드
Cocoa 응용 프로그램의 각 스레드는 자체 자동 릴리스 풀 블록 스택을 유지 관리합니다. Foundation 전용 프로그램을 작성 중이거나 스레드를 분리하는 경우 자체 자동 릴리스 풀 블록을 작성해야합니다.
응용 프로그램이나 스레드가 오래 지속되고 잠재적으로 많은 자동 릴리스 개체를 생성하는 경우 자동 릴리스 풀 블록을 사용해야합니다 (예 : 기본 스레드에서 AppKit 및 UIKit처럼). 그렇지 않으면 자동 해제 된 개체가 누적되고 메모리 공간이 늘어납니다. 분리 된 스레드가 Cocoa 호출을하지 않으면 자동 릴리스 풀 블록을 사용할 필요가 없습니다.
참고 : NSThread 대신 POSIX 스레드 API를 사용하여 보조 스레드를 생성하는 경우 Cocoa가 멀티 스레딩 모드에 있지 않으면 Cocoa를 사용할 수 없습니다. Cocoa는 첫 NSThread 객체를 분리 한 후에 만 멀티 스레딩 모드로 들어갑니다. 보조 POSIX 스레드에서 Cocoa를 사용하려면 응용 프로그램이 먼저 하나 이상의 NSThread 객체를 분리해야합니다.이 객체는 즉시 종료 될 수 있습니다. NSThread 클래스 메소드 isMultiThreaded를 사용하여 Cocoa가 멀티 스레딩 모드인지 여부를 테스트 할 수 있습니다.
…
자동 참조 계수 또는 ARC에서 시스템은 MRR과 동일한 참조 계수 시스템을 사용하지만 컴파일시 적절한 메모리 관리 메소드 호출을 삽입합니다. 새 프로젝트에는 ARC를 사용하는 것이 좋습니다. ARC를 사용하는 경우 일부 상황에서는 도움이 될 수 있지만 일반적으로이 문서에 설명 된 기본 구현을 이해할 필요는 없습니다. ARC에 대한 자세한 내용은 ARC 릴리스 노트로 전환을 참조하십시오.
답변
메소드에서 새로 작성된 오브젝트를 리턴하려면 자동 릴리스 풀이 필요합니다. 예를 들어 다음 코드를 고려하십시오.
- (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
의 이러한 두 가지 모두가 함께 할 일단 블록 / 작업을 실행하기 전에 생성 및 파괴 당신을 위해 최고 수준의 오토 릴리즈 풀을 관리 할.
답변
이 주제에 대해 많은 혼란이있는 것 같습니다 (그리고 지금은 이것에 대해 혼란스러워하고 코드 주위에 @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 전환을 완료 한 경우 자동 릴리스 풀을 잊어 버리십시오.