为啥这个 ObjC 块在释放时不释放其捕获的引用?包括失败的单元测试

Posted

技术标签:

【中文标题】为啥这个 ObjC 块在释放时不释放其捕获的引用?包括失败的单元测试【英文标题】:Why doesn't this ObjC block release its captured references when it's released? Failing unit test included为什么这个 ObjC 块在释放时不释放其捕获的引用?包括失败的单元测试 【发布时间】:2014-09-22 15:54:23 【问题描述】:

我遇到了一个问题,即在块中捕获的对象似乎没有被释放,即使对对象和块的所有引用都已设置为 nil

为了说明这个问题,我整理了这个非常简单的单元测试,应该通过但没有:

/* Headers */

@interface BlockTestTests : XCTestCase

@end

// A simple class that calls a callback when it's deallocated
@interface Dummy : NSObject

@property (nonatomic, copy) void(^deallocCallback)();

@end

/* Implementation */

@implementation BlockTestTests

- (void)testExample 
    XCTestExpectation *exp = [self expectationWithDescription:@"strong reference should be deallocated when its capturing block is released"];

    Dummy *dummy = [Dummy new];

    dummy.deallocCallback = ^
        [exp fulfill];
    ;

    void(^capturingBlock)() = ^
        // Captures a strong reference to the dummy
        id capturedStrongReference = dummy;
    ;

    capturingBlock = nil;
    dummy = nil;

    // At this point we would expect that all references to the
    // object have been cleared and it should get deallocated.
    // Just to be safe, we even wait 2 seconds, but it never happens...        

    [self waitForExpectationsWithTimeout:2.0 handler:nil];


@end

@implementation Dummy

- (void)dealloc 
    _deallocCallback();


@end

你能告诉我为什么这个测试失败了吗?

【问题讨论】:

【参考方案1】:

您的capturingBlock 正在创建一个自动释放的对象(可能通过捕获,但也可能是块本身)。如果你在它周围加上@autoreleasepool,它会做你想做的事:

  @autoreleasepool 
    void(^capturingBlock)() = ^
      // Captures a strong reference to the dummy
      id capturedStrongReference = dummy;
    ;
    capturingBlock = nil;
    dummy = nil;
  

更一致的方法是在整个测试周围放置一个@autoreleasepool(在创建exp 之后和waitForExpectations... 之前)。对于任何要验证对象在池耗尽时已被释放的测试,您都可以这样做。像这样:

- (void)testExample 
  XCTestExpectation *exp = [self expectationWithDescription:@"strong reference should be deallocated when its capturing block is released"];

  @autoreleasepool 
    Dummy *dummy = [Dummy new];

    dummy.deallocCallback = ^
      [exp fulfill];
    ;

    void(^capturingBlock)() = ^
      // Captures a strong reference to the dummy
      id capturedStrongReference = dummy;
    ;
    capturingBlock = nil;
    dummy = nil;
  

  [self waitForExpectationsWithTimeout:2.0 handler:nil];

【讨论】:

啊,是的,当然!感谢您的详尽回复。

以上是关于为啥这个 ObjC 块在释放时不释放其捕获的引用?包括失败的单元测试的主要内容,如果未能解决你的问题,请参考以下文章

_swift_abortRetainUnowned 将 @objc 类捕获为无主时

ios的内存管理

ObjC:我可以使用 property = nil 而不是 self.property = nil 来释放它吗?

为啥用C#在磁盘上写入文件后内存没有释放

为啥保留/释放而不是新/删除?

75. Autorelease机制及释放时机