Implementing __weak ownership qualifier

__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.

  1. objc_release.
  2. dealloc is called because retain count becomes zero.
  3. _objc_rootDealloc.
  4. object_dispose.
  5. objc_destructInstance.
  6. objc_clear_deallocating.

objc_clear_deallocating function is called last. It does the following.

  1. From the weak table, get an entry of which the key is the object to be discarded.
  2. Set nil to all the __weak ownership qualified variables in the entry.
  3. Remove the entry from the table.
  4. 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:

{
    id __weak obj = [[NSObject alloc] init];
    NSLog(@"obj=%@", obj);
}

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];

This is equivalent to:

/* 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.

  1. objc_loadWeakRetained function retains the object referenced by the variable qualified with __weak.
  2. 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

There is the other case where you can’t use the __weak ownership qualifier.When an NSObject instance method allowsWeakReference or retainWeakReference returns NO, the object can’t be assigned to a variable qualified with __weak. These methods are not documented in the NSObject protocol. The declaration is as follows.

- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;

When an object is assigned to a variable qualified with __weak, allowsWeakReference is called. If it returns NO, your application will be aborted:

cannot form weak reference to instance (0x753e180) of class MyObject

This means you should not assign an object of such a class to a variable qualified with __weak. You should be able to know if the class has such a restriction in the class reference.

When retainWeakReference method returns NO, the user gets value nil. Let’s see it with an example.

{
    id __strong obj = [[NSObjectalloc] init];
    id __weak o = obj;
    NSLog(@"1 %@", o);
    NSLog(@"2 %@", o);
    NSLog(@"3 %@", o);
    NSLog(@"4 %@", o);
    NSLog(@"5 %@", o);
}

The object exists until the scope of the variable obj is left. Therefore the variable o is usable during that time. The result is as follows.

1 <NSObject: 0x753e180>
2 <NSObject: 0x753e180>
3 <NSObject: 0x753e180>
4 <NSObject: 0x753e180>
5 <NSObject: 0x753e180>

Let’s see what happens if retainWeakReference returns NO. The next source code illustrates how retainWeakReference can be implemented.

@interfaceMyObject : NSObject
{
    NSUInteger count;
}
@end

@implementationMyObject
- (id)init
{
    self = [super init];
    return self;
}
- (BOOL)retainWeakReference
{
    if (++count > 3)
        return NO;
    return [super retainWeakReference];
}
@end

In this example, retainWeakReference method returns NO when it is called more than three times. And the above example is modified from NSObject to MyObject.

{
    id __strong obj = [[MyObjectalloc] init];
    id __weak o = obj;
    NSLog(@"1 %@", o);
    NSLog(@"2 %@", o);
    NSLog(@"3 %@", o);
    NSLog(@"4 %@", o);
    NSLog(@"5 %@", o);
}

The result is as follows.

1 <MyObject: 0x753e180>
2 <MyObject: 0x753e180>
3 <MyObject: 0x753e180>
4 (null)
5 (null)

When retainWeakReference method returns NO, the object can’t be accessed. If some classes in Framework use this mechanism, the class reference should have a comment about that. Also, allowsWeakReference and retainWeakReference methods are called by the runtime library in the middle of __weak qualification-related procedures. So, if some runtime library APIs are called inside these methods, your application might hang. These methods are undocumented and an application developer shouldn’t implement them. But if you need to implement them for any reason, you need to have a good understanding of how they work.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值