Не ожидается странного поведения ARC во время освобождения - PullRequest
0 голосов
/ 15 сентября 2018

Я обновляю свои знания в мире Objective-C и сейчас я тестирую некоторые ARC с __weak локальными переменными.

У меня очень простой код с такими файлами GAObject.h

#import <Foundation/Foundation.h>
@interface GAObject : NSObject
+ (instancetype)create;
@end

Реализация этого интерфейса GAObject.h

#import "GAObject.h"
@implementation GAObject
+ (instancetype)create {
    return [[GAObject alloc] init];
}
- (void)dealloc {
    NSLog(@"GAObject is being deallocated");
}
@end

Итак, существует простой фабричный метод create, и я переопределяю dealloc метод, чтобы посмотреть, были ли объекты освобождены, когда я ожидал этого. Теперь забавная часть main.m:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Learning/GAObject.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"1");
        NSObject *o1 = [[GAObject alloc] init];
        NSObject * __weak weakObject = o1; // Line 1
        o1 = nil; // o1 should be deallocated because there is no strong references pointing to o1.
        NSLog(@"2");
        NSObject *o2 = [GAObject create]; // Line 2
        o2 = nil; // o2 should be deallocated here too but it is not deallocated. Why?
        NSLog(@"3");

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

В выводе я вижу это:

1
GAObject is being deallocated
2
3

Но мои ожидаемые результаты должны быть:

1
GAObject is being deallocated
2
GAObject is being deallocated
3

Если я создаю o2, используя фабричный метод, то у меня такое поведение. Если я создаю o2 следующим образом: [[GAObject alloc] init], тогда я получаю ожидаемый результат. Также я заметил, что когда я удаляю строку с weakObject, я также получаю ожидаемые результаты. Кто-нибудь может это объяснить?

1 Ответ

0 голосов
/ 15 сентября 2018

Это потому, что ARC все еще соблюдает соглашения об именах управления памятью Какао.

В соответствии с этими соглашениями метод с именем +create возвращает ссылку +0.Таким образом, при реализации метода ARC должен сбалансировать +1 ссылку пары alloc / init путем автоматического освобождения ссылки.

Затем в main() ARC должен принять этополучил +0 ссылку от звонка на +create.Если бы ему понадобилась ссылка, чтобы выжить в текущей области, она бы сохранила ее, но это не так.Второй экземпляр GAObject будет освобожден при истощении пула автоматического выпуска, но этого никогда не произойдет, потому что UIApplicationMain() никогда не вернется.Если вы используете два отдельных пула автоматического выпуска, один для кода, связанного с GAObject s, а другой для вызова UIApplicationMain(), я ожидаю, что вы получите ожидаемый результат.

Если ARC действительно понадобилсяссылка на выживание, она будет сохранена при присвоении сильной переменной и будет освобождена, когда этой переменной будет присвоено новое значение (включая nil) или она выйдет из области видимости.У ARC есть оптимизация во время выполнения, при которой авто-выпуск-возврат в вызываемом объекте и сохранение возвращенного значения в вызывающем объекте взаимно отменяют друг друга, так что объект никогда не помещается в пул авто-выпуска.Если бы это происходило, вы бы получили ожидаемые результаты.

На самом деле, я ожидаю, что компилятор первоначально будет генерировать, что сохраняет и освобождает даже в вашем случае, но последующий проход удаляет избыточные удержания и освобождения.В вашем примере выпуск сразу же следует за сохранением, что делает для компилятора еще более очевидным, что пара является избыточной.Поскольку сохранение удаляется, эта оптимизация автоматического выпуска не включается, и ссылка на ваш объект действительно помещается в пул автоматического выпуска.

Если ваш метод был назван +newGAObject, тогда соглашения о присвоении именозначает, что он возвращает +1 ссылку, и это все меняется.(Конечно, в своем нынешнем виде ваш +create метод делает то же самое, что и встроенный +new метод, за исключением автоматического выпуска, который должен добавить ARC. Таким образом, вы можете просто изменить вызывающий код для использования+new и это также обойдёт эту проблему.)

Я не знаю, почему строка с weakObject имеет значение.Но, поскольку поведение, которое вы видите, зависит от определенных оптимизаций, все, что может изменить оптимизацию, может изменить результат.

...