API를 구현할 때 블록에서 자체 캡처를 피하려면 어떻게해야합니까?


222

작동하는 앱이 있고 Xcode 4.2에서 ARC로 변환하려고합니다. 사전 점검 경고 중 하나는 self유지 주기로 이어지는 블록에서 강력한 캡처를 포함 합니다. 문제를 설명하기 위해 간단한 코드 샘플을 만들었습니다. 나는 이것이 의미하는 바를 이해한다고 생각하지만 이러한 유형의 시나리오를 구현하는 "올바른"또는 권장되는 방법은 확실하지 않습니다.

  • self는 MyAPI 클래스의 인스턴스입니다
  • 아래 코드는 단순화되어 내 질문과 관련된 객체 및 블록과의 상호 작용 만 보여줍니다.
  • MyAPI가 원격 소스에서 데이터를 가져오고 MyDataProcessor가 해당 데이터를 처리하고 출력을 생성한다고 가정합니다.
  • 프로세서는 진행 및 상태를 전달하는 블록으로 구성됩니다.

코드 샘플 :

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

질문 : "무엇을하고 있습니까?"그리고 / 또는 ARC 규칙에 맞게 수정해야합니까?

답변:


509

짧은 답변

self직접 액세스하는 대신 유지되지 않는 참조에서 간접적으로 액세스해야합니다. ARC (Automatic Reference Counting)를 사용하지 않는 경우 다음 을 수행 할 수 있습니다.

__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

__block블록 내에서 수정 될 수 있습니다 키워드 마크 변수 (우리는 그렇게하지 않을)하지만 (당신은 ARC를 사용하지 않는 경우) 또한 자동으로 블록이 유지 될 때 유지되지 않습니다. 이 작업을 수행하면 MyDataProcessor 인스턴스가 릴리스 된 후 다른 어떤 것도 블록을 실행하려고하지 않아야합니다. (코드 구조에 문제가 없어야합니다.) 자세한 내용을 읽으십시오__block .

ARC를 사용하는 경우__block 변경 의미 및 참조 의 의미 가 유지되므로 __weak대신 선언해야 합니다.

긴 대답

다음과 같은 코드가 있다고 가정 해 봅시다.

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

여기서 문제는 자아가 블록에 대한 참조를 유지한다는 것입니다. 한편 블록은 델리게이트 속성을 가져오고 델리게이트에 메소드를 보내려면 self에 대한 참조를 유지해야합니다. 앱의 다른 모든 객체가이 객체에 대한 참조를 해제하면, 블록이 객체를 가리 키기 때문에 유지 횟수가 0이 아니고 (객체가 객체를 가리 키기 때문에) 블록이 잘못 수행하지 않으므로 객체 쌍은 힙으로 누출되어 메모리를 차지하지만 디버거 없이는 도달 할 수 없습니다. 정말 비극적입니다.

대신이 작업을 수행하면이 문제를 쉽게 해결할 수 있습니다.

id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
    [progressDelegate processingWithProgress:percentComplete];
}

이 코드에서, self는 블록을 유지하고, 블록은 델리게이트를 유지하며,주기는 없습니다 (여기에서 볼 수 있습니다. 델리게이트는 객체를 유지할 수는 있지만 지금은 손에서 벗어났습니다). 이 코드는 동일한 방식으로 누수를 발생시키지 않습니다. 델리게이트 속성의 값은 블록이 생성 될 때 조회되지 않고 블록이 생성 될 때 캡처되기 때문입니다. 부작용은이 블록을 생성 한 후 델리게이트를 변경해도 블록이 여전히 기존 델리게이트로 업데이트 메시지를 전송한다는 것입니다. 그 가능성이 있는지 여부는 응용 프로그램에 따라 다릅니다.

당신이 그 행동에 멋지더라도, 당신의 경우에는 여전히 그 트릭을 사용할 수 없습니다 :

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

여기 self에서는 메소드 호출에서 대리자로 직접 전달 되므로 어딘가에 가져와야합니다. 블록 유형의 정의를 제어 할 수있는 가장 좋은 방법은 대리자를 매개 변수로 블록에 전달하는 것입니다.

self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};

이 솔루션은 사이클을 유지 방지 하고 항상 현재 대리자를 호출합니다.

블록을 변경할 수 없으면 처리 할 수 있습니다 . 유지주기가 오류가 아닌 경고 인 이유는 응용 프로그램에 대해 반드시 명예를 훼손하지는 않기 때문입니다. MyDataProcessor작업이 완료 될 때 블록을 해제 할 수 있으면 부모가 해제하려고 시도하기 전에주기가 중단되고 모든 것이 올바르게 정리됩니다. 이것을 확신 할 수 있다면, 올바른 #pragma코드 블록에 대한 경고를 억제 하기 위해 a 를 사용하는 것이 옳습니다 . (또는 파일 별 컴파일러 플래그를 사용하십시오. 그러나 전체 프로젝트에 대해 경고를 비활성화하지 마십시오.)

위의 비슷한 트릭을 사용하여 참조를 약하거나 유지하지 않고 블록에서 사용하는 것을 볼 수도 있습니다. 예를 들면 다음과 같습니다.

__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

위 세 가지 모두 결과를 유지하지 않고 참조를 제공하지만 모두 약간 다르게 동작합니다 __weak. 객체가 해제되면 참조를 0으로 설정하려고 시도합니다. __unsafe_unretained잘못된 포인터로 당신을 떠날 것입니다; __block실제로 다른 수준의 간접 성을 추가하고 블록 내에서 참조 값을 변경할 수 있습니다 (이 경우에는 dp다른 곳에서는 사용되지 않으므로 관련 이 없음).

최선방법 은 변경할 수있는 코드와 변경할 수없는 코드에 따라 다릅니다. 그러나 이것이 진행 방법에 대한 아이디어를 줬기를 바랍니다.


1
멋진 답변! 고마워, 나는 무슨 일이 일어나고 있는지와 이것이 어떻게 작동하는지 훨씬 더 잘 이해하고 있습니다. 이 경우 모든 것을 제어 할 수 있으므로 필요에 따라 일부 오브젝트를 다시 작성합니다.
XJones

18
O_O 나는 약간 다른 문제를 겪고 있었고, 독서를 멈추었고, 이제이 페이지를 모든 지식과 시원함을 느끼게합니다. 감사!
오크 JMR

블록 실행 순간에 어떤 이유로 든보기 실행 dp이 해제되고 (예를 들어 뷰 컨트롤러이고 [dp.delegate ...팝업 된 경우 ), 라인 은 EXC_BADACCESS를 유발합니다.
peetonn

블록을 보유한 속성 (예 : dataProcess.progress)은 strong또는 weak?
djskinner 2016 년

1
당신은 한 번 봐있을 수 있습니다 libextobjc 라는 두 개의 편리한 매크로를 제공 @weakify(..)하고 @strongify(...)사용할 수있는 self비 고정 방식으로 블록을.

25

앞으로주기가 중단 될 것이라고 확신 할 때 경고를 표시하지 않는 옵션도 있습니다.

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

#pragma clang diagnostic pop

당신이 주변에 원숭이가없는 그런 식으로 __weak, self앨리어싱 및 명시 적 바르의 추가하는 설정.


8
__weak id weakSelf = self로 대체 할 수있는 3 줄 이상의 코드를 취하는 매우 나쁜 습관처럼 들립니다.
벤 싱클레어

3
억제 된 경고로부터 혜택을받을 수있는 더 큰 코드 블록이 종종 있습니다.
zoul

2
__weak id weakSelf = self;, 경고를 억제하는 것과 근본적으로 다른 동작을합니다. 질문은 "... 유지주기가 중단 될 것이라고 긍정적 인 경우"로 시작되었습니다.
Tim

사람들은 종종 파급 효과를 이해하지 않고 맹목적으로 변수를 약하게 만듭니다. 예를 들어, 사람들이 객체를 약화시킨 다음 블록에서 수행 [array addObject:weakObject];하는 것을 보았습니다. weakObject가 해제되면 충돌이 발생합니다. 확실히 그것은 유지주기보다 선호되지 않습니다. 블록이 실제로 약화를 보증하기에 충분히 오래 사는지 여부와 블록의 동작이 약한 객체가 여전히 유효한지 여부에 의존해야하는지 이해해야합니다.
mahboudz

14

일반적인 솔루션의 경우 사전 컴파일 헤더에 정의되어 있습니다. 캡처를 피하고 사용을 피함으로써 컴파일러 도움말을 활성화합니다id

#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)

그런 다음 코드에서 다음을 수행 할 수 있습니다.

BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
    [weakSelf.delegate myAPIDidFinish:weakSelf];
    weakSelf.dataProcessor = nil;
};

동의하면 블록 내부에 문제가 발생할 수 있습니다. ReactiveCocoa에는이 문제에 대한 또 다른 흥미로운 해결책이 있습니다 self. @weakify (self); id 블록 = ^ {@strongify (self); [self.delegate myAPIDidFinish : self]; };
Damien Pontifex

@dmpontifex 그것은 libextobjc의 매크로입니다 github.com/jspahrsummers/libextobjc
Elechtron

11

ARC가없는 솔루션은 __block키워드를 사용하여 ARC 와도 작동한다고 생각합니다 .

편집 : ARC 릴리스 노트로전환에 따라 __block스토리지로 선언 된 객체 는 여전히 유지됩니다. __weak(선호) 또는 __unsafe_unretained(이전 버전과의 호환성을 위해 )를 사용하십시오 .

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

// Use this inside blocks
__block id myself = self;

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [myself.delegate myAPIDidFinish:myself];
    myself.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

__block키워드가 참조를 유지 하지 않는 것을 몰랐 습니다. 감사! 나는 모 놀리 식 답변을 업데이트했습니다. :-)
benzado

3
Apple의 문서에 따르면 "수동 참조 카운팅 모드에서 __block id x; x를 유지하지 않는 효과가 있습니다. ARC 모드에서는 __block id x; 기본적으로 x를 유지합니다 (다른 모든 값과 동일)."
XJones

11

몇 가지 다른 답변을 결합하여 블록에서 사용할 약한 자기 자신을 위해 지금 사용합니다.

__typeof(self) __weak welf = self;

메소드 / 함수에서 "welf"의 완성 접두사를 가진 XCode 코드 스 니펫 으로 설정했는데 , "we"만 입력 한 후에 발생합니다.


확실합니까? 이 링크와 clang 문서는 둘 다 객체에 대한 참조를 유지하는 데 사용될 수 있고 사용해야하지만 유지주기를 유발하는 링크는 아닙니다. stackoverflow.com/questions/19227982/using-block-and-weak
Kendall Helmstetter Gelner

clang.cs에서 : clang.llvm.org/docs/BlockLanguageSpec.html "Objective-C 및 Objective-C ++ 언어에서 __weak 지정자를 객체 유형의 __block 변수에 사용할 수 있습니다. 가비지 수집을 사용하지 않으면이 한정자가 원인입니다. 이 변수는 메시지가 전송되지 않고 유지됩니다. "
Kendall Helmstetter Gelner


6

warning => "블록 내부에서 자신을 캡처하면 유지주기가 진행될 수 있습니다"

블록 내에서 자체 또는 그 속성을 참조 할 때 위의 경고보다 자체적으로 강력하게 유지됩니다.

그것을 피하기 위해 우리는 그것을 일주일 심판으로 만들어야합니다

__weak typeof(self) weakSelf = self;

그래서 대신에

blockname=^{
    self.PROPERTY =something;
}

우리는 사용해야한다

blockname=^{
    weakSelf.PROPERTY =something;
}

참고 : 유지주기는 일반적으로 참조 카운트가 1이고 델 로크 메소드가 호출되지 않는 두 객체가 서로를 참조하는 방법에 따라 발생합니다.



-1

코드에서 유지주기를 만들지 않거나 나중에주기가 중단 될 것이라고 확신하는 경우 경고를 끄는 가장 간단한 방법은 다음과 같습니다.

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

[self dataProcessor].progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

[self dataProcessor].completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

이것이 작동하는 이유는 속성의 도트 액세스가 Xcode의 분석에 의해 고려되기 때문에

x.y.z = ^{ block that retains x}

x의 y (지정 왼쪽)와 y의 x (오른쪽)에 의해 유지되는 것으로 보이면, 메소드 호출은 특성 액세스 메소드 호출 인 경우에도 동일한 분석의 대상이 아닙니다. 속성 액세스 메소드가 컴파일러에서 생성 된 경우에도 도트 액세스와 동일합니다.

[x y].z = ^{ block that retains x}

오른쪽 만 유지 (y의 x로)를 작성하는 것으로 보이며 유지주기 경고가 생성되지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.