Не довіряйте своїм очам, коли дивитесь на weak вказівники

У нас є дуже старий шматок коду. По суті, це проксі на NSTimer. NSTimer retain-ить проксю замість нашого дорогоцінного View Controller, що показує PDF файл. Код був написаний за часів ручного підрахунку посилань, потім напівавтоматично переведений на ARC і залишений як є. Виявилося, що всі ці роки, з моменту переходу на ARC, він мав баг, який призводив до витоку пам’яті.

Код виглядає приблизно так:

@interface MYAutosaveTimer ()
@property (nonatomic, weak) MYViewController *owner;
@end

@implementation MYAutosaveTimer

- (void)setOwner:(MYViewController *)owner {
    if (_owner) {
        // інвалідувати та звільнити справжній таймер
    }

    _owner = owner;

    if (_owner) {
        // запустити справжній таймер
    }
}

@end

Клієнтський код виглядає приблизно так:

- (instancetype)init {
    ...

    _autosaveTimer = [MYAutosaveTimer new];
    [_autosaveTimer setOwner:self];

    ...
}

- (void)dealloc {
    ...
    [_autosaveTimer setOwner:nil];
}

Виглядає жахливо в сучасні дні, але як я й казав – старий код 🤷‍♂️. Я його вже переписав як слід.

Річ, яка привернула мою увагу, і причина, чому я вирішив про це написати – це те, як це виглядає в LLDB. Ви заходите в метод -setOwner:. Дивитесь на змінні, і _owner виглядає абсолютно свіжим і повним сил вказівником з якоюсь адресою. А потім… ви ніколи не потрапляєте в if (_owner) { }, де ресурси мали б бути звільнені. Вказівник Шрьодінґера – він наче не nil, але він nil.

Отже, мораль така – не довіряйте своїм очам з weak вказівниками на об’єкт, який деалокується. Вказівники це не просто старі добрі адреси в наші дні.