Problem:
I have an __unsafe_unretained id
pointer that points to an already released object. So far so good, as long as I do not "use" the pointer at all (in particular, I do not call any method through the pointer). However, when I try to return its value from a method, it crashes, even if I have explicitly specified that the return value has the type __unsafe_unretained id
. Why is that? I thought if I use __unsafe_unretained
, it would not call methods like retain
/ release
/ autorelease
at all? I thought I can use __unsafe_unretained id
pretty much as if it is a void*
(meaning that it only does simple, native assignments)?
Environment:
- Developing on
Xcode 4.4.1
- Using
iOS SDK 5.1
ARC
isenabled
- Running on
iPhone 4.3 / 5.0 / 5.1 Simulator
oriPhone 4.3 Device
- Crashes on both
Debug
andRelease
builds
Source Code:
// 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;
}
}
}
Call Stack (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
Call Stack (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
I think the behavior can be explained with the following information 3.2.3. Unretained return values in the Automatic Reference Counting documentation:
Your function
GetMember2
returns anid
, which is a retainable object type. Therefore the ARC compiler addsretain
/autorelease
calls to make sure that the returned object is still valid when the function returns. This crashes becausem_MyMember
does not point to a valid object.Declaring the return type as
(__unsafe_unretained id)
does not change this behavior, in fact I assume that the__unsafe_unretained
is ignored here.