programing

API를 구현할 때 블록에서 셀프 캡처를 방지하려면 어떻게 해야 합니까?

golfzon 2023. 4. 29. 10:05
반응형

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)를 사용하지 않는 경우 다음 작업을 수행할 수 있습니다.

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

__block키워드는 블록 내부에서 수정할 수 있는 변수를 표시합니다(우리는 그렇게 하지 않습니다). 또한 블록이 유지될 때(ARC를 사용하지 않는 경우) 자동으로 유지되지 않습니다.이렇게 하면 MyDataProcessor 인스턴스가 릴리스된 후 다른 어떤 작업도 블록을 실행하려고 하지 않습니다.(코드의 구조를 고려하면 문제가 되지 않습니다.)에 대해 자세히 알아보십시오.

만약 당신이 ARC를 사용하고 있다면,__block변경되면 참조가 유지됩니다. 이 경우 선언해야 합니다.__weak대신.

긴 대답

다음과 같은 코드가 있었다고 가정해 보겠습니다.

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

여기서 문제는 self가 블록에 대한 참조를 유지하고 있다는 것입니다. 반면에 블록은 delegate 속성을 가져오고 delegate 메서드를 전송하려면 self에 대한 참조를 유지해야 합니다.앱의 다른 모든 항목이 이 개체에 대한 참조를 해제하면 유지 횟수가 0이 되지 않고(블록이 이 개체를 가리키고 있기 때문에) 블록이 잘못된 작업을 수행하지 않으므로(개체가 이 개체를 가리키고 있기 때문에) 개체 쌍이 힙으로 유출되어 메모리를 차지하지만 디버거 없이는 영원히 도달할 수 없습니다.정말 비극적입니다.

대신 다음을 수행하면 이 경우를 쉽게 해결할 수 있습니다.

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

이 코드에서 셀프는 블록을 유지하고, 블록은 딜러를 유지하며, 사이클은 없습니다(여기서 볼 수 있습니다. 딜러가 개체를 유지할 수 있지만 지금은 우리가 처리할 수 없습니다).이 코드는 블록이 실행될 때 조회하는 대신 블록을 만들 때 대리자 속성 값이 캡처되므로 동일한 방식으로 누출 위험이 없습니다.부작용으로는 이 블록이 생성된 후 대리인을 변경해도 해당 블록이 이전 대리인에게 업데이트 메시지를 계속 보낸다는 것입니다.그것이 일어날 가능성이 있는지 없는지는 당신의 애플리케이션에 달려 있습니다.

당신이 그 행동에 냉정하다고 해도, 당신의 경우에는 여전히 그 속임수를 사용할 수 없습니다.

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작업이 완료되면 부모가 블록을 해제하기 전에 블록을 해제할 수 있습니다. 주기가 끊어지고 모든 것이 제대로 정리됩니다.이 이것을할 수 , 은 만약당신이확수신있할면다것다, 옳일다사는입용니하음을은은이것을▁a다▁if것▁be니입▁use▁then▁to▁would▁to를 사용하는 것입니다.#pragma해당 코드 블록에 대한 경고를 표시하지 않습니다.그러나 전체 프로젝트에 대해 경고를 비활성화하지 마십시오.)

위의 유사한 트릭을 사용하여 참조가 약하거나 유지되지 않음을 선언하고 블록에서 이를 사용하는 방법도 확인할 수 있습니다.예:

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

의 세 할 수 , 합니다: 위의 세 가 지 모 결 참 다 약 동 게 르 작 합 간 니 다 모 두 지 하 만 제 공 하 를 조 지 않 지 고 유 를 과 두 ▁all ▁without ▁of 합 동 니 다 , 작 ▁you ▁give ▁a ▁will ▁they ▁retaining ▁reference 위 ▁though ▁three ▁a ▁result 게 ▁the 다 ▁behave ▁the__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별칭 및 명시적 ivar 접두사 지정.

일반적인 솔루션의 경우 사전 컴파일 헤더에 이러한 정의가 있습니다.합니다.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 릴리스 노트로 전환__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"만 입력하면 히트합니다.

경고 => "블록 내부에서 자신을 보호하는 것은 유지 사이클을 이끌 가능성이 있습니다."

위의 경고보다 자신이 강하게 보유하고 있는 블록 내부에서 자신 또는 그 속성을 언급할 때.

그래서 그것을 피하기 위해 우리는 일주일을 기준으로 삼아야 합니다.

__weak typeof(self) weakSelf = self;

그래서 사용하는 대신에

blockname=^{
    self.PROPERTY =something;
}

우리는 사용해야 합니다.

blockname=^{
    weakSelf.PROPERTY =something;
}

참고: 일반적으로 deloc 사이클은 두 객체가 서로 참조할 때 발생하며 둘 다 참조 카운트 =1을 가지며 deloc 메서드는 호출되지 않습니다.

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

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

@Weakify @Strongify Marco에 대한 자세한 정보

코드가 유지 주기를 만들지 않거나 나중에 주기가 중단될 경우 경고를 침묵시키는 가장 간단한 방법은 다음과 같습니다.

// 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 of y(할당의 왼쪽)와 y of x(오른쪽)에 의해 유지되는 것으로 간주되며, 메서드 호출은 닷 액세스와 동일한 속성 액세스 메서드 호출일 때도 동일한 분석의 대상이 되지 않습니다.

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

우측만 유지(x의 y만큼)를 생성하는 것으로 보이며 유지 사이클 경고는 생성되지 않습니다.

언급URL : https://stackoverflow.com/questions/7853915/how-do-i-avoid-capturing-self-in-blocks-when-implementing-an-api

반응형