Возвращение указателя __unsafe_unretained на освобожденный объект в Objective C вызывает сбой

Проблема:

у меня есть __unsafe_unretained id указатель, который указывает на уже освобожденный объект. Пока все хорошо, пока я вообще не "использую" указатель (в частности, я не вызываю какой-либо метод через указатель). Однако, когда я пытаюсь вернуть его значение из метода, он падает, даже если я явно указал, что возвращаемое значение имеет тип __unsafe_unretained id, Это почему? Я думал, что если я буду использовать __unsafe_unretained, это не будет вызывать методы, такие как retain / release / autorelease совсем? Я думал, что могу использовать __unsafe_unretained id почти как будто это void* (имеется ввиду, что он выполняет только простые, нативные задания)?

Окружающая среда:

  • Разработка на Xcode 4.4.1
  • С помощью iOS SDK 5.1
  • ARC является enabled
  • Работает на iPhone 4.3 / 5.0 / 5.1 Simulator или же iPhone 4.3 Device
  • Аварии на обоих Debug а также Release строит

Исходный код:

// Declare my class with 1 member.
@interface MyClass : NSObject
{
    __unsafe_unretained id      m_MyMember;
}
@end

// **************************************************************************************************** //

// Implement my class.
@implementation MyClass

// Setter
-(void)SetMember:(__unsafe_unretained id)member
{
    m_MyMember = member;
}

// Getter: by passing parameter by reference
-(void)GetMember1:(__unsafe_unretained id*)member
{
    *member = m_MyMember;   // No problem.
}

// Getter: by return value
-(__unsafe_unretained id)GetMember2
{
    return m_MyMember;  // Crashed in here!
}

@end

// **************************************************************************************************** //

//! Application entry point.
int main(int argc, char *argv[])
{
    @autoreleasepool
    {       
        {
            // Create an object that dies immediately. deadObj is a dangling pointer.
            __unsafe_unretained id deadObj = [[NSMutableString alloc] initWithFormat:@"%d", 12];

            // Create my object.
            MyClass* myObject = [[MyClass alloc] init];

            // Assign my member.
            [myObject SetMember:deadObj];

            // Get back my member: by passing parameter by reference
            __unsafe_unretained id unsafePointer1;
            [myObject GetMember1:&unsafePointer1];  // No problem.

            // Get back my member: by return value
            __unsafe_unretained id unsafePointer2;
            unsafePointer2 = [myObject GetMember2]; // Crashed in here!

            int BreakpointHere = 0;
        }
    }
}

Стек вызовов (iPhone 4.3 Simulator/iOS 4.3 Device):

#0  0x011db09b in objc_msgSend ()
#1  0x00106712 in __arclite_objc_retainAutoreleaseReturnValue at /SourceCache/arclite_host/arclite-29.1/source/arclite.m:259
#2  0x00001fec in -[MyClass GetMember2] at /Users/user/SourceCode/main.m:28
#3  0x00002147 in main at /Users/user/SourceCode/main.m:56

Стек вызовов (iPhone 5.0/5.1 Simulator):

#0  0x014f6d25 in objc_retain ()
#1  0x014f7fe3 in objc_retainAutoreleaseReturnValue ()
#2  0x00001fec in -[MyClass GetMember2] at /Users/user/SourceCode/main.m:28
#3  0x00002147 in main at /Users/user/SourceCode/main.m:56

1 ответ

Решение

Я думаю, что поведение может быть объяснено следующей информацией 3.2.3. Нераспределенные возвращаемые значения в документации по автоматическому подсчету ссылок:

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

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

Ваша функция GetMember2 возвращает id, который является сохраняемым типом объекта. Поэтому компилятор ARC добавляет retain / autorelease вызывает, чтобы убедиться, что возвращаемый объект все еще действителен, когда функция возвращается. Это падает, потому что m_MyMember не указывает на действительный объект.

Объявление типа возврата как (__unsafe_unretained id) не меняет это поведение, на самом деле я предполагаю, что __unsafe_unretained здесь игнорируется.

Другие вопросы по тегам