ARC에서 항상 자신에 대한 약한 참조를 블록으로 전달합니까?


252

Objective-C의 블록 사용에 대해 약간 혼란 스럽습니다. 현재 ARC를 사용하고 있으며 앱에 많은 블록이 있으며 현재 self약한 참조 대신 항상 참조합니다 . 이러한 블록이 유지 self되고 할당이 해제되지 않도록 하는 원인이 될 수 있습니까? 문제는 항상 블록에서 weak참조를 사용해야 self합니까?

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}

이 주제에 대한 심층 담화를 원하시면 dhoerl.wordpress.com/2013/04/23/…를
David H

답변:


721

토론의 일부 strong또는 weak일부에 집중하지 않는 것이 좋습니다. 대신 사이클 부분 에 집중하십시오 .

A는 유지 주기가 객체 A는 객체 B를 유지, 때 일어나는 루프 두 객체가 해제되는 경우 개체 B가, 그 상황에서 개체 A를 유지한다 :

  • 오브젝트 B는 이에 대한 참조를 보유하므로 오브젝트 A는 할당 해제되지 않습니다.
  • 그러나 오브젝트 A가 참조하는 한 오브젝트 B는 할당 해제되지 않습니다.
  • 그러나 오브젝트 B는 참조를 보유하므로 오브젝트 A는 할당 해제되지 않습니다.
  • 광고 인피니티 움

따라서,이 두 객체는 ​​모든 것이 제대로 작동한다면 할당이 해제 되더라도 프로그램 수명 동안 메모리에 매달리게됩니다.

우리가 걱정하는 것은 유지하는 것입니다 주기를 ,이주기를 생성 및 자신의 블록에 대해 아무것도가 없습니다. 이것은 문제가되지 않습니다. 예를 들면 :

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

블록은 유지 self하지만 블록은 유지 self하지 않습니다. 둘 중 하나가 해제되면주기가 생성되지 않고 모든 것이 할당 해제됩니다.

문제가 생기는 곳은 다음과 같습니다.

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

이제 객체 ( self) strong에 블록에 대한 명시적인 참조가 있습니다. 그리고 블록은 암시 적 강한 참조가 self있습니다. 그것은주기이며 이제는 어느 개체도 올바르게 할당 해제되지 않습니다.

이와 같은 상황에서 self 정의에 따라 이미 strong블록에 대한 참조가 있으므로 일반적으로 self블록에서 사용할 약한 참조를 만들어서 해결하는 것이 가장 쉽습니다 .

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

그러나 이것은 호출하는 블록을 다룰 때 따르는 기본 패턴이 아니어야합니다self ! 이것은 자기 자신과 블록 사이의 유지주기를 중단시키는 데만 사용해야합니다. 이 패턴을 어디에서나 채택해야한다면 self할당이 해제 된 후에 블록을 전달할 위험이 있습니다.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];

2
A retain B, B retain A가 무한주기를 수행하는지 잘 모르겠습니다. 참조 횟수의 관점에서 A와 B의 참조 횟수는 1입니다.이 상황에서 유지주기의 원인은 외부에 A와 B에 대한 강한 참조가없는 다른 그룹이없는 경우입니다. A를 B를 해제하도록 제어 할 수 없으며 그 반대도 마찬가지입니다.) 따라서 A와 B는 서로를 유지하기 위해 서로를 참조합니다.
Danyun Liu

@Danyun 이러한 객체에 대한 다른 모든 참조가 해제 될 때까지 A와 B 사이의 유지주기를 복구 할 수 없다는 것이 사실이지만,주기가 줄어들지는 않습니다. 반대로 특정 사이클을 복구 할 수 있다고해서 코드에 포함시킬 수있는 것은 아닙니다. 유지주기는 나쁜 디자인 냄새입니다.
jemmons

@jemmons 예, 우리는 항상 가능한 한 유지주기 디자인을 피해야합니다.
Danyun Liu

1
@ 마스터 말할 수 없습니다. 그것은 당신의 -setCompleteionBlockWithSuccess:failure:방법 의 구현에 전적으로 달려 있습니다 . 그러나이 paginator소유 ViewController하고이 블록이 ViewController해제 된 후 호출되지 않으면 __weak참조를 사용하는 것이 안전한 이동이됩니다 ( self블록을 소유 한 것을 소유하고 있기 때문에 블록이 호출 할 때 여전히 주변에있을 가능성이 높습니다) 그들은 그것을 유지하지 않더라도). 그러나 그것은 많은 "if"입니다. 실제로해야 할 일에 따라 다릅니다.
jemmons

1
@Jai No. 이것은 블록 / 클로저의 메모리 관리 문제의 핵심입니다. 아무것도 소유하지 않으면 객체가 할당 해제됩니다. MyObject그리고 SomeOtherObject두 블록을 소유하고 있습니다. 그러나 블록의 참조 MyObject는 is weak이므로 블록 소유 하지 않습니다MyObject . 블록이만큼 존재 보장 그래서 동안 하나 MyObject 또는 SomeOtherObject 존재, 보장이 없다 MyObject한 블록이처럼 존재합니다. 블록이 MyObject완전히 할당 해제 될 수 있으며 SomeOtherObject여전히 존재하는 한 블록은 여전히 ​​존재합니다.
jemmons

26

항상 약한 참조를 사용할 필요는 없습니다. 블록이 유지되지 않고 실행 된 후 삭제되면 유지주기를 만들지 않으므로 강력하게 자체를 캡처 할 수 있습니다. 어떤 경우에는 블록이 완료 될 때까지 블록이 자체를 보유하여 블록이 조기에 할당 해제되지 않도록 할 수도 있습니다. 그러나 블록을 강력하게 캡처하고 캡처 자체 내에 있으면 유지주기가 생성됩니다.


글쎄, 그냥 블록을 콜백으로 실행하고 자체 할당을 취소하고 싶지 않습니다. 그러나 문제의 View Controller가 할당 해제되지 않기 때문에 유지주기를 생성하는 것처럼 보입니다.
the_critic

1
예를 들어,보기 컨트롤러에 의해 유지되는 막대 단추 항목이 있고 해당 블록에서 자체적으로 캡처하는 경우 유지주기가 있습니다.
레오 나탄

1
@ 마틴 코드 샘플로 질문을 업데이트해야합니다. Leo는 매우 정확하며 (+1) 반드시 강력한 참조주기를 유발하지는 않지만 이러한 블록을 사용하는 방법에 따라 달라질 수 있습니다. 코드 스 니펫을 제공하면 더 쉽게 도울 수 있습니다.
Rob

그래서 그 것은 혼란을 나에게 조금 @LeoNatan 나는 사이클을 유지의 개념을 이해하지만 나는 확신 블록에 무슨 일 아니다
the_critic

위의 코드에서 작업이 완료되고 블록이 해제되면 자체 인스턴스가 해제됩니다. 블록의 작동 방식과 범위 내에서 무엇을 언제 캡처하는지에 대해 읽어야합니다.
레오 나탄

26

나는 @jemmons에 전적으로 동의합니다.

그러나 이것은 스스로 호출하는 블록을 다룰 때 따르는 기본 패턴이 아니어야합니다! 이것은 자기 자신과 블록 사이의 유지주기를 중단시키는 데만 사용해야합니다. 이 패턴을 어디에서나 채택해야한다면, 자기 할당이 해제 된 후에 실행 된 것으로 블록을 전달할 위험이 있습니다.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

이 문제를 극복하기 위해 weakSelf블록 내부에 대한 강력한 참조를 정의 할 수 있습니다 .

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];

3
strongSelf가 weakSelf의 참조 수를 늘리지 않습니까? 따라서 유지주기를 만드는가?
mskw

12
유지주기는 객체의 정적 상태에있는 경우에만 중요합니다. 코드가 실행 중이고 상태가 유동적 인 동안 여러 개의 중복 리테 이닝이 가능합니다. 어쨌든이 패턴과 관련하여 강력한 참조를 캡처하면 블록이 실행되기 전에 자체 할당이 해제되는 경우 아무런 일이 발생하지 않습니다. 블록 을 실행 하는 동안 자체 할당이 해제되지 않도록 합니다. 이것은 블록이 비동기 작업 자체를 수행하여 창을 제공하는지 여부에 중요합니다.
Pierre Houston

@ smallduck, 설명이 훌륭합니다. 이제 나는 이것을 더 잘 이해합니다. 이 책은 다루지 않았습니다. 감사합니다.
Huibin Zhang

5
strongSelf를 명시 적으로 추가하는 것은 런타임에서 어쨌든 정확히 수행 할 것이기 때문에 이것은 strongSelf의 좋은 예가 아닙니다. doSomething 행에서 메소드 호출 기간 동안 강력한 참조가 수행됩니다. weakSelf가 이미 무효화 된 경우 강한 참조는 nil이고 메소드 호출은 작동하지 않습니다. strongSelf가 도움이되는 곳은 일련의 작업이 있거나 멤버 필드에 액세스하는 경우 ( ->), 실제로 유효한 참조를 확보하고 전체 작업 세트에서 지속적으로 유지하려는 경우입니다.if ( strongSelf ) { /* several operations */ }
Ethan

19

Leo가 지적한 것처럼 질문에 추가 한 코드는 강력한 참조주기 (일명 유지주기)를 나타내지 않습니다. 강력한 참조주기를 유발할 수있는 작업 관련 문제 중 하나는 작업이 릴리스되지 않은 경우입니다. 코드 스 니펫은 작업을 동시에 수행하도록 정의하지 않았지만 제안한 경우 게시하지 않았 isFinished거나 순환 종속성이있는 경우 해제되지 않습니다 . 그리고 작업이 해제되지 않으면 뷰 컨트롤러도 해제되지 않습니다. 중단 점을 추가하거나 NSLog작업을 수행 하는 것이 좋습니다.dealloc 방법 호출되는지 확인하는 .

당신은 말했다 :

유지주기의 개념을 이해하지만 블록에서 어떤 일이 발생하는지 잘 모르겠으므로 조금 혼란 스럽습니다.

블록에서 발생하는 유지주기 (강한 참조주기) 문제는 익숙한 유지주기 문제와 같습니다. 블록은 블록 내에 나타나는 객체에 대한 강력한 참조를 유지하며 블록 자체가 해제 될 때까지 이러한 강력한 참조를 해제하지 않습니다. 따라서 블록 참조 self또는 인스턴스 변수를 참조하는 경우 self자체에 대한 강력한 참조를 유지하면 블록이 해제 될 때까지 (또는이 경우에는NSOperation 서브 클래스가 해제 .

자세한 내용 은 Objective-C를 사용한 프로그래밍 : 블록 작업자체 캡처시 강력한 참조주기 방지 섹션을 참조하십시오 . 문서의 .

뷰 컨트롤러가 여전히 릴리스되지 않으면 해결되지 않은 강력한 참조가있는 위치를 식별하면됩니다 ( NSOperation할당이 해제 되었다고 가정 한 경우 ). 일반적인 예는 반복을 사용하는 것입니다 NSTimer. 또는 delegate잘못 strong참조를 유지 관리하는 일부 사용자 지정 또는 기타 개체입니다 . 다음과 같이 계측기를 사용하여 객체가 강한 참조를 얻는 위치를 추적 할 수 있습니다.

Xcode 6의 레코드 참조 카운트

또는 Xcode 5에서 :

Xcode 5의 레코드 참조 카운트


1
또 다른 예는 작업이 블록 생성자에 유지되고 완료된 후에 해제되지 않는 경우입니다. 좋은 쓰기에 +1!
레오 나탄

@LeoNatan 동의하지만 코드 스 니펫은 ARC를 사용하는 경우 해제 될 로컬 변수로 표시합니다. 하지만 당신 말이 맞아요!
Rob

1
예, OP가 다른 답변에서 요청한 것처럼 예를 들었습니다.
레오 나탄

그런데 Xcode 8에는 "디버그 메모리 그래프"가 있는데, 이는 릴리스되지 않은 객체에 대한 강력한 참조를 찾는 훨씬 쉬운 방법입니다. stackoverflow.com/questions/30992338/…를 참조하십시오 .
Rob

0

일부 설명은 유지주기에 대한 조건을 무시합니다. [객체 그룹이 강력한 관계의 원으로 연결되어 있으면 그룹 외부에서 강한 참조가없는 경우에도 서로를 유지합니다.] 자세한 내용은 문서를 읽으십시오 .


-2

다음은 블록 내부에서 자기를 사용하는 방법입니다.

// 블록 호출

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


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