__weak ownership qualifier
Next, we learn about the __weak ownership qualifier. We show what happens when an object is disposed of or when a newly created object is assigned and how an object is added to the autorelease pool automatically.
As described previously in Chapter 2, the __weak ownership qualifier provides the following features.
- Nil is assigned to any variables qualified with __weak when referencing object is discarded.
- When an object is accessed through a __weak qualified variable, the object is added to the autorelease pool.
Let’s start with the following example to see what is going on behind the scenes.
{
id __weak obj1 = obj;
}
Here, assume that the variable obj is stored in some variables qualified with __strong.
/* pseudo code by the compiler */
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);
The variable qualified with __weak is initialized by the objc_initWeak function. When the variable scope is left, the objc_destroyWeak function destroys it.
The objc_initWeak function is implemented as the following source code. It clears the variable qualified with __weak, and then calls the objc_storeWeak function with the object to be assigned.
obj1 = 0;
objc_storeWeak(&obj1, obj);
The objc_destroyWeak function calls objc_storeWeak function with zero as argument as follows.
objc_storeWeak(&obj1, 0);
So, the example code is equivalent to:
/* pseudo code by the compiler */
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
objc_storeWeak function registers a key-value to a table, called a weak table. The key is the second argument, the address of the object to be assigned. The value is the first argument, the address of a variable that qualified with __weak. If the second argument is zero, the entry is removed from the table.
The weak table is implemented as a hash table as a reference count table (seeChapter 1, Section “The Implementation by Apple”). With that, variables qualified with __weak can be searched from a disposing object with reasonable performance. When the function is called with the same object for key, multiple __weak qualified variables will be registered for the same object.
Looking Under the Hood When an Object Is Discarded
Next, let’s see the function calls when an object is disposed of when no one has ownership of it. Objects are relinquished by objc_release function.
- objc_release.
- dealloc is called because retain count becomes zero.
- _objc_rootDealloc.
- object_dispose.
- objc_destructInstance.
- objc_clear_deallocating.
objc_clear_deallocating function is called last. It does the following.
- From the weak table, get an entry of which the key is the object to be discarded.
- Set nil to all the __weak ownership qualified variables in the entry.
- Remove the entry from the table.
- For the object to be disposed of, remove its key from the reference table.
This is the implementation and it lets us know how nil is assigned to any variables qualified with __weak when the referencing object is discarded.Also, it tells us that if too many __weak ownership qualified variables are used, it consumes CPU resources at some level. So, __weak ownership qualified variables should be used only to avoid circular references.
Assigning a Newly Created Object
As described previously, the following source code causes a warning.
{
id __weak obj = [[NSObject alloc] init];
}
It tries to assign a new object to a variable. But no one can have ownership because the variable is qualified with __weak. So, the object is immediately released and discarded. That is the reason for the warning.
warning: assigning retained obj to weak variable; obj will be
released after assignment [-Warc-unsafe-retained-assign]
id __weak obj = [[NSObject alloc] init];
^ ~~~~~~~~~~~~~~
Let’s see how the compiler handles the source code.
/* pseudo code by the compiler */
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&object);
objc_initWeak function assigns the new object to the variable, which is qualified with __weak. Also, the compiler detects that no one has ownership and inserts the objc_release function.
When objc_release is called, the object will be discarded and the variable qualified with __weak becomes nil. Let’s check the result with NSLog:
The result is as follows. It shows nil formatted with “%@”.
obj=(null)
What will happen if an object is created and just discarded? Let’s see it in the next section with its implementation.
Immediate Disposal of Objects
As we described, the following source code causes a compiler warning.
id __weak obj = [[NSObject alloc] init];
This is because the compiler detects that no one has ownership of the new object. How about __unsafe_unretained?
id __unsafe_unretained obj = [[NSObject alloc] init];
The same as __weak, the compiler detects that no one has ownership of the object.
warning: assigning retained object to unsafe_unretained variable;
obj will be released after assignment [-Warc-unsafe-retained-assign]
id __unsafe_unretained obj = [[NSObject alloc] init];
^ ~~~~~~~~~~~~~
The source code is equivalent to:
/* pseudo code by the compiler */
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
The new object is released by the objc_release function, and the variable obj still has a dangling pointer. What will happen if the object is not assigned to any variables? Without ARC, it just causes a memory leak.
[[NSObject alloc] init];
With ARC, the compiler gives a warning because the returned value is never used.
warning: expression result unused [-Wunused-value]
[[NSObject alloc] init];
^~~~~~~~~~~~~
You can hide the warning by casting it to void.
(void)[[NSObject alloc] init];
This source code is converted to the following, with or without cast.
/* pseudo code by the compiler */
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_release(tmp);
Unless the object is not assigned to a variable, the code is the same as with __unsafe_unretained. Because no one has ownership, the compiler generates an objc_release function call. Because of ARC, no memory leak occurs.
One more question: can we call instance methods for these immediately disposed of objects as follows?
(void)[[[NSObject alloc] init] hash];
/* pseudo code by the compiler */
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_msgSend(tmp, @selector(hash));
objc_release(tmp);
The object is released after the method is called. The compiler handles memory management properly as Apple says!
Adding to autorelease pool Automatically
As described previously, when an object is accessed through a __weak qualified variable, the object has been added to the autorelease pool. Let’s see how it works.
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
The source code is equivalent to the following.
/* pseudo code by the compiler */
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
objc_loadWeakRetained and objc_autorelease function calls are newly inserted, which are not in the previous examples. This is because the variable is used somehow. These functions do the following.
- objc_loadWeakRetained function retains the object referenced by the variable qualified with __weak.
- objc_autorelease function adds the object to the autorelease pool.
This means that an object assigned to a variable with __weak, is added to the autorelease pool so that the variable can be used safely until @autoreleasepool block is left.
NOTE: If you use __weak qualified variables too often, too many objects will be stored in autorelease pool.
To avoid this storage issue, I recommend that when you use a __weak qualified variable, you should assign the object to a __strong qualified variable as well. For example, the next example uses variable o five times, which is qualified with __weak.
{
id __weak o = obj;
NSLog(@"1 %@", o);
NSLog(@"2 %@", o);
NSLog(@"3 %@", o);
NSLog(@"4 %@", o);
NSLog(@"5 %@", o);
}
The object is added to the autorelease pool five times:
objc[14481]: ##############
objc[14481]: AUTORELEASE POOLS for thread 0xad0892c0
objc[14481]: 6 releases pending.
objc[14481]: [0x6a85000] ................ PAGE (hot) (cold)
objc[14481]: [0x6a85028] ################ POOL 0x6a85028
objc[14481]: [0x6a8502c] 0x6719e40 NSObject
objc[14481]: [0x6a85030] 0x6719e40 NSObject
objc[14481]: [0x6a85034] 0x6719e40 NSObject
objc[14481]: [0x6a85038] 0x6719e40 NSObject
objc[14481]: [0x6a8503c] 0x6719e40 NSObject
objc[14481]: ##############
You can avoid that by assigning the object to a variable qualified with __strong.
{
id __weak o = obj;
id tmp = o;
NSLog(@"1 %@", tmp);
NSLog(@"2 %@", tmp);
NSLog(@"3 %@", tmp);
NSLog(@"4 %@", tmp);
NSLog(@"5 %@", tmp);
}
In this case, the object is added to the autorelease pool just once at the line “tmp = o;”
objc[14481]: ##############
objc[14481]: AUTORELEASE POOLS for thread 0xad0892c0
objc[14481]: 2 releases pending.
objc[14481]: [0x6a85000] ................ PAGE (hot) (cold)
objc[14481]: [0x6a85028] ################ POOL 0x6a85028
objc[14481]: [0x6a8502c] 0x6719e40 NSObject
objc[14481]: ##############
As described previously, a __weak qualifier can’t be used for iOS4 or OS X Snow Leopard. Also, in some other cases, a __weak ownership qualifier can’t be used because some classes don’t support __weak qualified variables.
For example, an NSMachPort class can’t be assigned to any variables qualified with __weak. These classes override retain/release and have their own reference counts in their original implementation. When an object is assigned to a __weak qualified variable, the compiler has to insert objc4 functions properly, so many of these classes can’t support a __weak qualification. These unsupported classes have an attribute __attribute__((objc_arc_weak_reference_unavailable)) in class declaration, which is defined as NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE. Even if you assign an object of these classes to a variable, qualified with __weak, the compiler properly generates an error. These classes are very rare, so you don’t need to worry about it too much.
ALLOWSWEAKREFERENCE AND RETAINWEAKREFERENCE METHODS