Несовместимое освобождение объекта с ARC? - PullRequest
8 голосов
/ 07 февраля 2012

Я играл с распределением памяти (de) в простом приложении командной строки для Mac OSX 10.7, построенном с использованием версии Xcode 4.2.1 с включенным ARC и настройками сборки по умолчанию. Я не могу объяснить поведение, которое я понимаю из того, что я понимаю об ARC, поэтому я надеюсь, что кто-то может объяснить, что здесь происходит.

Во-первых, в следующем коде я получаю ожидаемое поведение (обратите внимание, что вывод NLog () приведен в комментарии после соответствующего оператора)

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
    NSObject *objPtr1 = [[NSObject alloc] init];
    NSObject *objPtr2 = objPtr1; 
    __weak NSObject *weakRef = objPtr1;
    NSLog(@"%@", [objPtr1 description]); // <NSObject: 0x1001107d0>
    objPtr1 = nil;
    NSLog(@"%@", [objPtr2 description]); // <NSObject: 0x1001107d0>
    objPtr2 = nil;
    NSLog(@"%@", [weakRef description]); // (null)
    return 0;
}

Таким образом, в приведенном выше примере сразу после назначения weakRef экземпляр NSObject имеет два сильных указателя и, следовательно, счетчик сохранения 2. После обнуления objPtr1 остается еще один сохраняющий указатель на экземпляр, поэтому он все еще находится в памяти и отвечает на сообщение description . После обнуления объекта objPtr2 нет сильных указателей на объект, и он освобождается (я предполагаю, что так и есть, так как weakRef был обнулен). Пока все хорошо.

Теперь тот же код с небольшим изменением:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{
    NSObject *objPtr1 = [[NSObject alloc] init];
    NSObject *objPtr2 = objPtr1; 
    __unsafe_unretained NSObject *weakRef = objPtr1; // __unsafe_unretained instead of just __weak
    NSLog(@"%@", [objPtr1 description]); // <NSObject: 0x1001107d0>

    objPtr1 = nil;
    NSLog(@"%@", [objPtr2 description]); // <NSObject: 0x1001107d0>

    objPtr2 = nil;
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>
    //why was the object instance not deallocated and the preceding statement not crash the program?
    return 0;
}

Я ожидал, что слабый указатель станет висящим указателем, отправляющим сообщение, через которое может произойти сбой программы в третьем операторе NSLog (), но кажется, что экземпляр объекта все еще жив и здоров.

Еще одна вещь, которую я нахожу странной:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{

    NSObject *objPtr1 = [[NSObject alloc] init];
    NSObject *objPtr2 = objPtr1; 
    __weak NSObject *weakRef = objPtr1; // __weak again
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>

    objPtr1 = nil;
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>

    objPtr2 = nil;
    NSLog(@"%@", [weakRef description]); // <NSObject: 0x1001107d0>

    return 0;

}

Этот последний код похож на первый (используя обнуленный указатель __weak) единственное различие заключается в том, что сообщение описания было отправлено объекту через weakRef при каждом из трех вызовов NSLog (). Но на этот раз объект не освобождается даже после удаления двух сильных ссылок (так как он все еще отвечает на сообщения с помощью weakRef).

Так что здесь происходит?

Ответы [ 2 ]

3 голосов
/ 12 февраля 2012

Если вы дизассемблируете код, созданный A.R.C., каждый доступ к слабой переменной будет заключен в вызов этой функции:

id objc_loadWeak(id *location)
{
    return objc_autorelease(objc_loadWeakRetained(location));
}

Это проверяет, был ли объект уже освобожден, и, если нет, сохраняет и автоматически освобождает его для дополнительной защиты от преждевременного освобождения.

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

1 голос
/ 12 февраля 2012

Это кажется странным.Вы правы (в своих комментариях) в том, что второй бит кода просто потому, что память еще не была использована повторно.Но третий бит кода незнаком.Вот более упрощенный тестовый пример, который показывает эту странную проблему:

#import <Foundation/Foundation.h>

@interface SomeClass : NSObject 
@end

@implementation SomeClass
- (void)foo {
}
@end

int main (int argc, const char * argv[]) {
    @autoreleasepool {
        SomeClass *objPtr1 = [[SomeClass alloc] init];
        __weak SomeClass *weakRef = objPtr1;

//        [weakRef foo];
        [weakRef foo];

        objPtr1 = nil;

        NSLog(@"%p", weakRef);

        return 0;
    }
}

С этой закомментированной строкой получается:

$ clang -fobjc-arc -framework Foundation test.m -o test -O3 && ./test
2012-02-12 00:39:42.769 test[6684:707] 0x0

С этой строкой без комментариев выводится:

$ clang -fobjc-arc -framework Foundation test.m -o test -O3 && ./test
2012-02-12 00:42:04.346 test[6688:707] 0x100f13f50

Это кажется очень странным и выглядит для меня как ошибка.Я на самом деле не знаю, что это за ответ, но подумал, что опубликую это как ответ, чтобы заставить мяч раскручиваться, выясняя, что происходит.

Обновление:

Если вы построите это на O0, тогда, похоже, weakRef обнуляется, только если есть нет вызовов на foo.Один вызов foo будет означать, что он не обнулится.

...