4

ARC에서 블록 내부에 self을 사용하는 경우 블록에 유지주기가 발생하는 것으로 의심됩니다.ARC에서 블록 기반 유지주기를 해결하기 위해 포인터를 복제하는 것이 왜 의미가 있습니까?

나는이처럼 workaround here을 본 적이 : enter image description here

방법이 해결 방법은 사이클을 유지 방지 할 수 있습니까?

weakRequestrequest에 의해 참조되는 똑같은 개체에 대한 포인터입니다. ARC가 보유 수를 weakRequest 또는 request으로 수정하면 동일한 개체에 영향을줍니다. 말에

__strong ASIHTTPRequest *strongRequest = weakRequest; 

이것은 eqivalent입니다 :

ASIHTTPRequest *strongRequest = weakRequest; 
[strongRequest retain]; 

그러나 다시 :

그런 다음 블록에, 무슨 이상한 것이 있습니다 그것은 하나의 동일한 개체입니다. 왜 이러한 모든 변수 이름을 사용합니까? 그들은 단지 포인터 일뿐입니다!

저는 블록에 대해별로 신경 쓰지 않았고 피하려고했습니다. 그러나 이제는 이것이 내가 블록이 변수를 포착한다고 말할 때 모두가 말하는 것에 대해 호기심을 샀다. 오늘까지는 이것이 단지 블록이 여러분이 사용하는 모든 포인터를 블록의 범위 밖에서 정의했음을 의미한다고 생각했습니다. 즉, 블록이 범위 내에있는 객체를 그대로 유지한다는 것을 의미합니다. 당신이 개체 자체가 정확히 동일하게 유지 볼 수 있습니다처럼

UIView *v = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)]; 
[self.view addSubview:v]; 
v.backgroundColor = [UIColor orangeColor]; 

NSLog(@"self = %p", self); // 0x6a12a40 

[UIView animateWithDuration:1.5 
         delay:0 
        options:UIViewAnimationOptionAllowUserInteraction 
       animations:^{ 
        UIViewController *my = self; 
        NSLog(@"my = %p", my); // 0x6a12a40 
        v.frame = CGRectMake(200, 200, 100, 100); 
       } 
       completion:nil]; 

:

나는이 빠른 테스트를했다. 블록은 사본을 작성하지 않습니다. 그래서 저는 C와 Objective-C에 대한 지식이 여전히 유효하다고 가정 할 수 있습니다 :

ASIHTTPRequest *strongRequest = internetRequest; 
ASIHTTPRequest *foo = strongRequest; 
ASIHTTPRequest *bar = foo; 

if (bar == internetRequest) { 
    NSLog(@"exact same thing, of course"); 
} 

그래서 어떻게 될까요? 어떻게하면 동일한 객체에 대해 다른 포인터를 만드는 경우에이 방법으로 유지 카운트를 해결할 수 있습니까? 왜 그 포인터를 만드는 여분의 마일?

완전히 똑같지 않습니까?

[request setCompletionBlock:^{ 
    NSString *respondeString = [request responseString]; 
    if ([_delegate respondsToSelector:@selector(pingSuccessful:)]) { 
     [_delegate pingSuccessful:responseString]; 
    } 
}]; 

여기서 포인터를 복제하면 메모리 관리 문제가 해결되는 이유를 설명하는 Objective-C에 대한 몇 가지 비밀이 있어야합니다. 그것은 나에게 어떤 의미가 없습니다.

답변

6

실제로는 ARC와는 아무런 관련이 없습니다. 블록이 변수를 캡처하는 방법입니다. 포인터가 복제되어 블록에 의해 캡처 된 변수가 올바른 소유권 한정자를 갖습니다.

weakRequest는 request에 의해 참조되는 동일한 객체에 대한 포인터입니다. ARC가 weakRequest 또는 요청 보유 수를 수정하면 동일한 객체에 영향을 미치게됩니다.

오른쪽, 그들은 같은 객체를 두 지점뿐만 weakRequest 그 변수 블록에 의해 포착되는 경우, 그 카운트는 변하지 유지 것을 의미 __unsafe_unretained 소유권 규정을 갖는다.

블록에 의해 request이 캡처 된 경우 ARC를 사용하는지 여부와 관계없이 유지되며주기가 유지됩니다.

포인터를 __strong 포인터로 다시 변환하면 블록 실행 기간 동안 해당 개체가 활성 상태로 유지됩니다.

+0

내가 틀릴 수도 있지만 블록 내에서 __strong 포인터가 블록 기간 동안 계속 유지한다고 생각하지 않습니다. ARC가이를 수행 할 수있는 유일한 방법은 객체를 유지하는 것입니다. ARC가 블록의 수명을 알 수 없기 때문에 (특히 나중에 실행을 위해 저장 될 가능성이 높습니다) 유지 사이클을 생성합니다.그것이 블록을 유지한다면 (그렇다고 생각하지 않습니다), 블록 내부에 __strong 변수를 추가하면 블록 외부의 __weak 변수의 목적이 완전히 상실됩니다. –

+0

위대한 설명! @ 아론, 나는 유지 사이클의 문제는 당신이 블록을 만들고 그것을 오랫동안 사용하지 않는다면, 블록이 실행되어 그 객체를 놓을 때까지 보존 된 객체가 사라지지 않을 수도 있다는 것입니다. 이 경우에는 이제 나에게 맞는 말이있다. 요청 객체는 블록이 시작될 때 * 블록 안에 유지되고 그 후에는 해제되고 유지 사이클이 끊어진다. 물론 이것은 블록이 지연과 함께 실행되면 블록이 실행되기 전에 요청 객체가 할당 해제 될 가능성을 열어줍니다. 권리? –

+0

__weak 참조를 사용하는 주요 이유는 나중에 실행할 블록을 저장하기 때문입니다. 블록을 가져 오는 사용자 지정 컨트롤이 있고 그 블록을 iVar로 저장합니다. 이러한 컨트롤은 일반적으로 블록을 제공하는 개체에 의해 유지되므로 블록이 해당 개체를 보유하고 있으면 보유주기가 유지됩니다 ... 또한 블록 내에 인스턴스화 된 변수가 해당 개체를 보유하면 다시 보유주기가 유지됩니다 그 변수가 존재합니다. 이것은 왜 __weak var에 전달 된 __strong var을 할당하면 객체의 보유 개수를 증가시키지 않으므로 아무 것도하지 않는다고 생각하는 이유입니다. –

2

글쎄, 변수를 __weak으로 지정하면 블록이 유지되지 않으므로 유지주기를 피할 수 있습니다. 그러나 블록 내부에 __strong Variable을 생성하고 __weak 변수를 가리키는 것은 완전히 불필요합니다. 블록이 그것을 유지하지 못하도록 약하게 지정합니다. 새로운 것을 생성하고 그것을 __strong으로 지정한다고해서 그 블록이 그것을 유지할 필요가없는 인스턴스가 없기 때문에 아무런 의미가 없습니다. __strong은 필요에 따라 값을 유지하도록 ARC에 알려주는 컴파일러 키워드 일 뿐이며 ARC는 이미 블록으로 전달 되었기 때문에 필요를 찾지 않습니다. 결국 weakRequest 변수를 사용하고 strongRequest 변수를 사용하지 않아도됩니다.

2

두 가지 다른 문제가 발생하지 않도록 두 가지 일이 일어나기 때문에 혼란 스러울 수 있습니다.

그 라인이하는
__strong ASIHTTPRequest *strongRequest = weakRequest; 

하지이 유지주기를 방지 : 당신이 줄을 인용했다.

잠재적 인 보유주기가 하나의 문제입니다. 보존주기에는 self, ASIHTTPRequest 및 블록의 세 가지 개체가 포함됩니다. 블록이 ASIHTTPRequest 개체를 소유하지 않은 weakRequest을 캡처하기 때문에 weakRequest 변수를 사용하면 해당주기가 중단됩니다. 참조 횟수 용어에서 weakRequest을 할당해도 ASIHTTPRequest의 참조 횟수가 증가하지 않습니다.

인용 한 줄은 첫 번째 문제를 해결함으로써 생성 된 다른 문제를 방지하기위한 것입니다. 다른 문제는 잠재적으로 매달려있는 포인터입니다. weakRequestASIHTTPRequest을 소유하지 않으므로 완료 블록을 실행하는 동안 ASIHTTPRequest의 모든 소유자가 완료 할 위험이 있습니다. 그런 다음 weakRequest은 할당되지 않은 객체에 대한 포인터 인 매달려있는 포인터가됩니다. 이를 사용하면 충돌이나 힙 손상이 발생할 수 있습니다.

인용 한 줄에서 weakRequest부터 strongRequest까지 블록을 복사합니다. strongRequest__strong이기 때문에 컴파일러는 ASIHTTPRequest을 유지하고 (참조 카운트를 증가 시키려면) 코드를 생성하고 블록 끝 부분에서 코드를 해제합니다. 즉, 블록이 실행되는 동안 ASIHTTPRequest의 다른 모든 소유자가 블록을 실행하더라도 블록이 일시적으로 요청의 소유자가 되었기 때문에 ASIHTTPRequest은 활성 상태를 유지합니다.

매달린 포인터 문제에 대한이 솔루션은 스레드로부터 안전하지 않습니다. 블록 소유자가 요청을 실행하는 동안 요청을 다른 스레드에서 해제 할 수있는 경우 여전히 매달린 포인터로 이어질 수있는 경쟁 조건이 있습니다. 따라서 약한 포인터는 __unsafe_unretained 대신 __weak을 사용해야합니다. __weak 참조는 경쟁 조건없이 __strong 참조로 복사 할 수 있습니다.

+0

__weak 요청 변수는 그것이 가리키는 객체가 파괴되면 댕글 링 포인터가 될 것이라고 생각하지 않습니다. 대신, 그것은 nil을 가리킬 것이다. __unsafe_unretained를 사용하는 경우에만 매달릴 것입니다. –

+0

질문에서 코드를 살펴보면, 변수가 'weakRequest'라고하더라도'__weak'이 아니라'__unsafe_unretained'를 사용한다는 것을 알 수 있습니다. –

+0

또한 '__weak' 참조가 언제든지'nil '이 될 수 있기 때문에'__unsafe_unretained '대신'__weak'을 사용하더라도 강력한 참조로 복사해야합니다. '__unsafe_unretained' 대신'__weak'을 사용하면 단순히 좀비 레퍼런스의 사용을 막고 경쟁 조건을 방지 할 수 있습니다. –