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

작동하는 앱이 있고 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 규칙에 맞게 수정해야합니까?



답변

짧은 답변

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다른 곳에서는 사용되지 않으므로 관련 이 없음).

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


답변

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

#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앨리어싱 및 명시 적 바르의 추가하는 설정.


답변

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

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

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

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


답변

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];


답변

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

__typeof(self) __weak welf = self;

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


답변

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

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

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

__weak typeof(self) weakSelf = self;

그래서 대신에

blockname=^{
    self.PROPERTY =something;
}

우리는 사용해야한다

blockname=^{
    weakSelf.PROPERTY =something;
}

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


답변

이를 수행하는 새로운 방법은 @weakify 및 @strongify marco를 사용하는 것입니다.

@weakify(self);
[self methodThatTakesABlock:^ {
    @strongify(self);
    [self doSomething];
}];

@Weakify @Strongify Marco에 대한 추가 정보