블록으로 '자기'사이클 유지


167

나는이 질문이 매우 기본적이라고 생각하지만, 많은 Objective-C 프로그래머와 관련이 있다고 생각합니다.

내가 들었던 것은 블록 내에서 const복사본 으로 참조되는 로컬 변수를 블록으로 캡처하기 때문에 블록 self내에서 블록을 사용 하면 해당 블록을 복사하면 유지주기가 발생할 수 있다는 것입니다. 따라서 __block블록을 self복사하지 않고 직접 처리하도록 블록 을 사용해야 합니다 .

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];

그냥 대신

[someObject messageWithBlock:^{ [self doSomething]; }];

내가 알고 싶은 것은 다음과 같습니다. 이것이 사실이라면 (GC를 사용하는 것 외에) 추함을 피할 수있는 방법이 있습니까?


3
나는 물건을 뒤집기 위해 self프록시 를 부르는 것을 좋아합니다 this. JavaScript에서는 this클로저를 호출 self하므로 멋지고 균형 잡힌 느낌입니다. :)
devios1

스위프트 블록을 사용하는 경우 동등한 조치를 취해야하는지 궁금합니다.
Ben Lu

@BenLu 절대적으로! Swift 클로저 (및 암시 적 또는 명시 적으로 언급을 전달하는 함수)에서 자체를 유지합니다. 때로는 이것이 바람직하고 다른 경우에는 사이클 자체를 생성하기도합니다 (클로저 자체가 자신이 소유 한 것 (또는 자신이 소유 한 것이기 때문에)이기 때문에 발생하는 주된 이유는 ARC 때문입니다)
Ethan

1
문제를 피하기 위해 블록에 사용될 'self'를 정의하는 적절한 방법은 '__typeof (self) __weak weakSelf = self;'입니다. 약한 참조를하기 위해.
XLE_22

답변:


169

엄밀히 말해, 그것이 const 사본이라는 사실은이 문제와 관련이 없습니다. 블록은 생성 될 때 캡처 된 모든 obj-c 값을 유지합니다. const-copy 문제에 대한 해결 방법은 유지 문제에 대한 해결 방법과 동일합니다. 즉, __block변수에 스토리지 클래스를 사용합니다 .

어쨌든 귀하의 질문에 대답하기 위해 진정한 대안은 없습니다. 자체 블록 기반 API를 디자인하고 있고 그렇게하는 것이 합리적이라면 블록의 값을 self인수로 전달할 수 있습니다 . 불행히도 이것은 대부분의 API에 적합하지 않습니다.

ivar 참조와 동일한 문제가 있습니다. 블록에서 ivar을 참조해야하는 경우 대신 속성을 사용하거나을 사용하십시오 bself->ivar.


부록 : ARC로 컴파일 할 때 __block더 이상 중단주기가 유지되지 않습니다. ARC를 컴파일하는 경우 __weak또는 __unsafe_unretained대신 사용해야 합니다.


문제 없어요! 이것이 귀하의 만족에 대한 질문에 대한 답변이라면, 귀하의 질문에 대한 정답으로 선택할 수 있다면 감사하겠습니다. 그렇지 않은 경우 귀하의 질문에 더 잘 대답 할 수있는 방법을 알려주십시오.
릴리 발라드

4
문제 없습니다, 케빈 SO는 질문에 대한 답변을 즉시 선택하지 못하게하므로 나중에 다시 돌아와야했습니다. 건배.
Jonathan Sterling

__unsafe_unretained id bself = 자기;
caleb

4
@JKLaiho : 물론 __weak입니다. 블록이 호출 될 때 객체가 범위를 벗어날 수 없다는 사실을 알고 있다면 __unsafe_unretained너무 빠르지 만 일반적으로 차이는 없습니다. 을 사용하는 경우 로컬 변수 __weak에 버리고이를 수행하기 전에이를 __strong테스트 nil하십시오.
Lily Ballard

2
@Rpranata : 그렇습니다. __block유지 및 해제하지 않은 부작용은 순전히 추론 할 수 없기 때문입니다. ARC를 통해 컴파일러는 그 능력을 얻었으며 __block이제는 유지하고 릴리스합니다. 이를 피해야하는 __unsafe_unretained경우 컴파일러에서 변수의 값에 대해 유지 또는 해제를 수행하지 않도록 지시하는 을 사용해야 합니다.
릴리 발라드

66

그냥 사용하십시오 :

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];

자세한 정보 : WWDC 2011 – 실제 블록 및 그랜드 센트럴 파견 .

https://developer.apple.com/videos/wwdc/2011/?id=308

참고 : 그래도 작동하지 않으면 시도해 볼 수 있습니다

__weak typeof(self)weakSelf = self;

2
그리고 당신은 우연히 그것을 발견 했습니까 :)?
Tieme

2
여기에서 비디오를 확인할 수 있습니다.- developer.apple.com
videos

"someOtherMethod"내에서 자체를 참조 할 수 있습니까? 이 시점에서 자기 자신을 약점으로 참조하거나 유지주기를 만드는가?
Oren

안녕하세요 @Oren, "someOtherMethod"내부에서 self를 참조하려고하면 Xcode 경고가 표시됩니다. 내 접근 방식은 자기를 약하게 참조합니다.
3lvis

1
블록 내부에서 직접 참조 할 때 경고 만 받았습니다. someOtherMethod 내부에 자신을 넣는 것은 경고를 발생시키지 않았습니다. xcode가 충분히 똑똑하지 않거나 문제가 아니기 때문입니까? someOtherMethod 내에서 자신을 참조하는 것은 이미 weakSelf를 참조합니까?
Oren

22

이것은 명백 할 수도 있지만 self, 유지주기를 얻는다는 것을 알면 추악한 별칭 만 수행하면 됩니다. 블록이 단발 일인 경우에는에 대한 무시를 안전하게 무시할 수 있다고 생각합니다 self. 나쁜 예는 예를 들어 블록을 콜백 인터페이스로 사용하는 경우입니다. 여기처럼 :

typedef void (^BufferCallback)(FullBuffer* buffer);

@interface AudioProcessor : NSObject {…}
@property(copy) BufferCallback bufferHandler;
@end

@implementation AudioProcessor

- (id) init {
    
    [self setBufferCallback:^(FullBuffer* buffer) {
        [self whatever];
    }];
    
}

여기서 API는 의미가 없지만 수퍼 클래스와 통신 할 때는 의미가 있습니다. 우리는 버퍼 핸들러를 보유하고 버퍼 핸들러는 우리를 보유합니다. 다음과 같이 비교하십시오 :

typedef void (^Callback)(void);

@interface VideoEncoder : NSObject {…}
- (void) encodeVideoAndCall: (Callback) block;
@end

@interface Foo : NSObject {…}
@property(retain) VideoEncoder *encoder;
@end

@implementation Foo
- (void) somewhere {
    [encoder encodeVideoAndCall:^{
        [self doSomething];
    }];
}

이러한 상황에서는 self앨리어싱을 수행하지 않습니다 . 유지주기가 발생하지만 작업 수명이 짧으며 블록이 결국 메모리에서 부족해져주기가 중단됩니다. 그러나 블록에 대한 나의 경험은 매우 작으며 self장기적으로 앨리어싱이 모범 사례로 나올 수 있습니다 .


6
좋은 지적. 자체적으로 블록을 활성 상태로 유지하는 경우에만 유지주기입니다. 복사되지 않은 블록 또는 제한된 기간 (예 : UIView 애니메이션의 완료 블록)이있는 블록의 경우 걱정할 필요가 없습니다.
Lily Ballard

6
원칙적으로 맞습니다. 그러나 예제에서 코드를 실행하면 충돌이 발생합니다. 블록 속성은 항상copy 아닌 로 선언 해야합니다 retain. 그들이 단지 retain라면, 스택에서 움직일 것이라는 보장이 없습니다. 즉, 스택을 실행할 때 더 이상 존재하지 않을 것입니다. (복사 이미 복사 된 블록은 유지에 최적화되어 있습니다)
데이브 드롱

아, 물론 오타. 나는 retain잠시 전에 단계를 겪고 당신이 말하는 것을 빨리 깨달았습니다 :) 감사합니다!
zoul

나는 확신 retain(그들은 이미 스택 떨어져 이동하지 않는 한 완전히 차단 무시됩니다 copy).
Steven Fisher

@Dave DeLong, 아니요, @property (retain)은 블록이 아닌 객체 참조에만 사용되므로 충돌하지 않습니다. 여기에서 사본을 전혀 사용할 필요가 없습니다 ..
DeniziOS

20

이것은 나에게도 문제가 되었기 때문에 다른 답변을 게시하는 중입니다. 원래 블록 내부에 자체 참조가있는 곳이라면 어디에서나 blockSelf를 사용해야한다고 생각했습니다. 이것은 사실이 아니며, 객체 자체에 블록이있는 경우에만 해당됩니다. 사실,이 경우 blockSelf를 사용하면 블록에서 결과를 다시 가져 오기 전에 객체가 할당 해제 될 수 있으며 호출하려고 할 때 충돌이 발생하므로 응답 할 때까지 분명히 자신을 유지하기를 원합니다 돌아오다.

첫 번째 경우는 블록에서 참조되는 블록을 포함하므로 보유주기가 발생하는시기를 보여줍니다.

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface ContainsBlock : NSObject 

@property (nonatomic, copy) MyBlock block;

- (void)callblock;

@end 

@implementation ContainsBlock
@synthesize block = _block;

- (id)init {
    if ((self = [super init])) {

        //__block ContainsBlock *blockSelf = self; // to fix use this.
        self.block = ^{
                NSLog(@"object is %@", self); // self retain cycle
            };
    }
    return self;
}

- (void)dealloc {
    self.block = nil;
    NSLog (@"ContainsBlock"); // never called.
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 

@end 

 int main() {
    ContainsBlock *leaks = [[ContainsBlock alloc] init];
    [leaks callblock];
    [leaks release];
}

두 번째 경우에는 blockSelf가 필요하지 않습니다. 호출하는 객체에는 자체를 참조 할 때 유지주기가 발생하는 블록이 없기 때문입니다.

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface BlockCallingObject : NSObject 
@property (copy, nonatomic) MyBlock block;
@end

@implementation BlockCallingObject 
@synthesize block = _block;

- (void)dealloc {
    self.block = nil;
    NSLog(@"BlockCallingObject dealloc");
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 
@end

@interface ObjectCallingBlockCallingObject : NSObject 
@end

@implementation ObjectCallingBlockCallingObject 

- (void)doneblock {
    NSLog(@"block call complete");
}

- (void)dealloc {
    NSLog(@"ObjectCallingBlockCallingObject dealloc");
    [super dealloc];
} 

- (id)init {
    if ((self = [super init])) {

        BlockCallingObject *myobj = [[BlockCallingObject alloc] init];
        myobj.block = ^() {
            [self doneblock]; // block in different object than this object, no retain cycle
        };
        [myobj callblock];
        [myobj release];
    }
    return self;
}
@end

int main() {

    ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init];
    [myObj release];

    return 0;
} 

이것은 일반적인 오해이며 위험 할 수 있습니다. 유지 해야하는 블록은 self이 수정을 과도하게 적용하는 사람들이 아니기 때문일 수 있습니다. 이것은 비 ARC 코드에서 유지주기를 피하는 좋은 예입니다. 게시 덕분입니다.
Carl Veazey

9

또한 블록이 유지하는 다른 객체를 참조하는 경우 유지주기가 발생할 수 있습니다 self.

가비지 콜렉션이 이러한 유지주기에 도움이 될지 확실하지 않습니다. 블록을 유지하는 객체 (서버 객체라고 함)가 수명을 초과 self하면 (클라이언트 객체) self블록 내부에 대한 참조 는 유지 객체 자체가 해제 될 때까지 주기적으로 간주되지 않습니다. 서버 개체가 클라이언트보다 훨씬 오래 사용하면 상당한 메모리 누수가 발생할 수 있습니다.

깨끗한 솔루션이 없으므로 다음 해결 방법을 권장합니다. 문제를 해결하기 위해 하나 이상을 자유롭게 선택하십시오.

  • 개방형 이벤트가 아닌 완료시 에만 블록을 사용하십시오 . 예를 들어, 같은 메소드에는 블록을 사용하고 같은 메소드에는 사용 doSomethingAndWhenDoneExecuteThisBlock:하지 마십시오 setNotificationHandlerBlock:. 완료에 사용 된 블록의 수명은 한정되어 있으며 평가 후 서버 개체에서 해제해야합니다. 이렇게하면 유지주기가 발생하더라도 너무 오래 유지되지 않습니다.
  • 당신이 묘사 한 약한 참조 춤을하십시오.
  • 객체가 해제되기 전에 객체를 정리하는 방법을 제공하십시오. 객체는 객체에 대한 참조를 보유 할 수있는 서버 객체와 객체를 "연결 해제"합니다. 객체에서 release를 호출하기 전에이 메소드를 호출하십시오. 이 방법은 객체에 클라이언트가 하나만 있거나 일부 컨텍스트 내에서 싱글 톤 인 경우에는 완벽하지만 클라이언트가 여러 개인 경우 분류됩니다. 당신은 기본적으로 리 테인 카운팅 메커니즘을 물리 치고 있습니다. 이것은 dealloc대신에 호출 하는 것과 유사합니다 release.

서버 오브젝트를 작성하는 경우 완료를 위해서만 블록 인수를 사용하십시오. 와 같은 콜백에 대한 블록 인수를 허용하지 마십시오 setEventHandlerBlock:. 대신, 전형적인 델리게이트 패턴으로 돌아가서 공식적인 프로토콜을 만들고 setEventDelegate:메소드를 광고하십시오 . 대리인을 유지하지 마십시오. 공식적인 프로토콜을 만들지 않으려면 선택기를 대리인 콜백으로 수락하십시오.

마지막으로이 패턴은 알람을 울립니다.

-(void) dealloc {
    [myServerObject releaseCallbackBlocksForObject : self];
    ...
}

selfinside에서 참조 할 수있는 블록을 분리하려고 dealloc하면 이미 문제가있는 것입니다. dealloc블록의 참조로 인한 유지 주기로 인해 호출되지 않을 수 있습니다. 즉, 서버 개체의 할당이 해제 될 때까지 개체가 누수 될 것입니다.


__weak적절하게 사용하면 GC가 도움을줍니다 .
tc.

가비지 수집 추적은 물론 유지주기를 처리 할 수 ​​있습니다. 유지주기는 참조 계산 환경에서만 문제가됩니다.
newacct

모두가 알다시피, 가비지 콜렉션은 ARC (Automatic Reference Counting)를 위해 OS X v10.8에서 더 이상 사용되지 않으며 향후 OS X 버전 ( developer.apple.com/library/mac/#releasenotes) 에서 제거 될 예정 입니다. / ObjectiveC /… ).
Ricardo Sanchez-Saez

1

__block __unsafe_unretainedKevin의 게시물 에서 제안 된 수정자는 다른 스레드에서 블록이 실행될 경우 액세스 예외가 발생할 수 있습니다. 임시 변수에 __block 한정자를 사용하는 것이 좋으며 사용 후에는 nil을 만드는 것이 좋습니다.

__block SomeType* this = self;
[someObject messageWithBlock:^{
  [this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with
                      //  multithreading and self was already released
  this = nil;
}];

변수를 사용한 후 닐링하지 않아도되도록 __block 대신 __weak을 사용하는 것이 실제로 안전하지 않습니까? 다른 유형의주기를 중단하려는 경우이 솔루션이 훌륭하지만 "자체"유지주기에 대한 특별한 이점은 없습니다.
ale0xB

플랫폼 대상이 iOS 4.x 인 경우 __weak을 사용할 수 없습니다. 또한 때로는 블록의 코드가 nil이 아닌 유효한 객체에 대해 실행되어야합니다.
b1gbr0

1

libextobjc 라이브러리를 사용할 수 있습니다. 그것은 매우 인기가 있으며, 예를 들어 ReactiveCocoa에서 사용됩니다. https://github.com/jspahrsummers/libextobjc

@weakify 및 @strongify의 2 개의 매크로를 제공하므로 다음을 수행 할 수 있습니다.

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

이렇게하면 직접적인 강력한 참조가 방지되므로 자체 유지주기에 들어 가지 않습니다. 또한, 자신이 반쯤 무용지물이되는 것을 방지하지만 여전히 보유 수를 적절하게 감소시킵니다. 이 링크에서 더 많은 것 : http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html


1
단순화 된 코드를 보여주기 전에 그 뒤에 무엇이 있는지 아는 것이 좋습니다. 모두가 실제 두 줄의 코드를 알아야합니다.
Alex Cio

0

이건 어때요?

- (void) foo {
     __weak __block me = self;

     myBlock = ^ {
        [[me someProp] someMessage];
     }
     ...
 }

컴파일러 경고가 더 이상 표시되지 않습니다.


-1

블록 : 보유 사이클은 블록에서 참조되는 블록을 포함하기 때문에 발생합니다. 블록 복사를 수행하고 멤버 변수를 사용하면 자체가 유지됩니다.

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