为什么这个ObjC块在它发布时不会释放它被捕获的引用?包括失败的单元测试

时间:2014-09-22 15:54:24

标签: objective-c unit-testing objective-c-blocks xctest

我遇到了一个问题,即即使所有对象和块的引用都设置为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 个答案:

答案 0 :(得分:2)

你的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];
}