Почему этот блок ObjC не выпускает свои захваченные ссылки, когда он выпущен? Провал тестовый блок включен

Я столкнулся с проблемой, когда объект, захваченный в блоке, кажется, не освобождается, даже после того, как все ссылки на объект и блок были установлены в 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];
}
Другие вопросы по тегам