天天看點

strong和weak引用的講解

由于這幾天一直在學習ViewController之間的資料傳輸方法,學着學着就有了疑問:為什麼在前向傳輸時(forward)可以使用屬性傳值,而後向傳輸(backward)時不能再使用,為了弄清楚這個問題,搜了很多文章,大部分都是在講傳輸方法的使用,沒有找到原因,但是根據蛛絲馬迹找到了strong和weak這樣的關鍵字,由于對這樣的關鍵詞還不了解,是以又專門來了解它們來了,看了下面這篇文章算是有了一個初步的認識,本來我想轉化為自己的語言再描述一遍,但發現沒有原文寫的具體,是以就隻好全文複制過來了,以防原網站無法通路,然後再貼上原連結供詳細的了解。當然了,還有其他的參考連結就都貼在這裡吧:

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html#//apple_ref/doc/uid/TP40011210-CH5-SW3

這篇文章也解釋的很清楚,并且應該算是下面這篇的補充: http://www.cocoawithlove.com/2009/07/rules-to-avoid-retain-cycles.html

參考連結:http://www.informit.com/articles/article.aspx?p=1856389&seqNum=5

Strong and Weak References

So far, we’ve said that anytime a pointer variable stores the address of an object, that object has an owner and will stay alive. This is known as a strong reference. However, a variable can optionally not take ownership of an object it points to. A variable that does not take ownership of an object is known as a weak reference.

A weak reference is useful for an unusual situation called a retain cycle. A retain cycle occurs when two or more objects have strong references to each other. This is bad news. When two objects own each other, they will never be destroyed by ARC. Even if every other object in the application releases ownership of these objects, these objects (and any objects that they own) will continue to exist by virtue of those two strong references.

Thus, a retain cycle is a memory leak that ARC needs your help to fix. You fix it by making one of the references weak. Let’s introduce a retain cycle in 

RandomPossessions

 to see how this works. First, we’ll give BNRItem instances the ability to hold another BNRItem (so we can represent things like backpacks and purses). In addition, a BNRItem will know which BNRItemholds it. In BNRItem.h, add two instance variables and accessors

@interface BNRItem : NSObject
{
    NSString *itemName;
    NSString *serialNumber;
    int valueInDollars;
    NSDate *dateCreated;
    BNRItem *containedItem;
    BNRItem *container;
}

+ (id)randomItem;

- (id)initWithItemName:(NSString *)name
        valueInDollars:(int)value
          serialNumber:(NSString *)sNumber;

- (void)setContainedItem:(BNRItem *)i;
- (BNRItem *)containedItem;

- (void)setContainer:(BNRItem *)i;
- (BNRItem *)container;
      

Implement the accessors in BNRItem.m.

- (void)setContainedItem:(BNRItem *)i
{
    containedItem = i;

    // When given an item to contain, the contained
    // item will be given a pointer to its container
    [i setContainer:self];
}

- (BNRItem *)containedItem
{
    return containedItem;
}

- (void)setContainer:(BNRItem *)i
{
    container = i;
}

- (BNRItem *)container
{
    return container;
}
      

In main.m, remove the code that populated the array with random items. Then create two new items, add them to the array, and make them point at each other.

#import <Foundation/Foundation.h>
#import "BNRItem.h"

int main (int argc, const char * argv[])
{
    @autoreleasepool {
        NSMutableArray *items = [[NSMutableArray alloc] init];

        for (int i = 0; i < 10; i++) {
            BNRItem *p = [BNRItem randomItem];
            [items addObject:p];
        }

        for (BNRItem *item in items)
            NSLog(@"%@", item);

        BNRItem *backpack = [[BNRItem alloc] init];
        [backpack setItemName:@"Backpack"];
        [items addObject:backpack];

        BNRItem *calculator = [[BNRItem alloc] init];
        [calculator setItemName:@"Calculator"];
        [items addObject:calculator];

        [backpack setContainedItem:calculator];

        NSLog(@"Setting items to nil...");
        items = nil;
    }
    return 0;
}
      

Here’s what the application looks like now:

strong和weak引用的講解

Figure 3.7 RandomPossessions with retain cycle

Per our understanding of memory management so far, both BNRItems should be destroyed along with their instance variables when items is set to nil. Build and run the application. Notice that the console does not report that these objects have been destroyed.

This is a retain cycle: the backpack and the calculator have strong references to one another, so there is no way to destroy these objects. Figure 3.8 shows the objects in the application that are still taking up memory once items has been set to nil.

strong和weak引用的講解

Figure 3.8 A retain cycle!

The two BNRItems cannot be accessed by any other part of the application (in this case, themain function), yet they still exist in their own little world doing nothing useful. Moreover, because they cannot be destroyed, neither can the other objects that their instance variables point to.

To fix this problem, one of the pointers between the BNRItems needs to be a weak reference. To decide which one should be weak, think of the objects in the cycle as being in a parent-child relationship. In this relationship, the parent can own its child, but a child should never own its parent. In our retain cycle, the backpack is the parent, and the calculator is the child. Thus, the backpack can keep its strong reference to the calculator (containedItem), but the calculator’s reference to the backpack (container) should be weak.

To declare a variable as a weak reference, we use the __weak attribute. In BNRItem.h, change thecontainer instance variable to be a weak reference.

__weak BNRItem *container;
      

Build and run the application again. This time, the objects are destroyed properly.

Every retain cycle can be broken down into a parent-child relationship. A parent typically keeps a strong reference to its child, so if a child needs a pointer to its parent, that pointer must be a weak reference to avoid a retain cycle.

A child holding a strong reference to its parent’s parent also causes a retain cycle. So the same rule applies in this situation: if a child needs a pointer to its parent’s parent (or its parent’s parent’s parent, etc.), then that pointer must be a weak reference.

It’s good to understand and look out for retain cycles, but keep in mind that they are quite rare. Also, 

Xcode

 has a 

Leaks

 tool to help you find them. We’ll see how to use this tool in Chapter 21.

An interesting property of weak references is that they know when the object they reference is destroyed. Thus, if the backpack is destroyed, the calculator automatically sets its containerinstance variable to nil. In main.m, make the following changes to see this happen.

NSMutableArray *items = [[NSMutableArray alloc] init];

BNRItem *backpack = [[BNRItem alloc] init];
[backpack setItemName:@"Backpack"];
[items addObject:backpack];

BNRItem *calculator = [[BNRItem alloc] init];
[calculator setItemName:@"Calculator"];
[items addObject:calculator];

[backpack setContainedItem:calculator];

NSLog(@"Setting items to nil...");
items = nil;

backpack = nil;

NSLog(@"Container: %@", [calculator container]);

calculator = nil;
      

Build and run the application. Notice that after the backpack is destroyed, the calculator reports that it has no container without any additional work on our part.

A variable can also be declared using the __unsafe_unretained attribute. Like a weak reference, an unsafe unretained reference does not take ownership of the object it points to. Unlike a weak reference, an unsafe unretained reference is not automatically set to nil when the object it points to is destroyed. This makes unsafe unretained variables, well, unsafe. To see an example, change container to be unsafe unretained in BNRItem.h.

__unsafe_unretained BNRItem *container;
      

Build and run the application. It will most likely crash. The reason? When the calculator was asked for its container within the NSLog function call, it obligingly returned its value – the address in memory where the non-existent backpack used to live. Sending a message to a non-existent object resulted in a crash. Oops.

As a novice iOS programmer, you won’t use __unsafe_unretained. As an experienced programmer, you probably won’t use it, either. It exists primarily for backwards compatibility: applications prior to iOS 5 could not use weak references, so to have similar behavior, they must use __unsafe_unretained.

Be safe. Change this variable back to __weak.

__weak BNRItem *container;
      

Here’s the current diagram of 

RandomPossessions

. Notice that the arrow representing thecontainer pointer variable is now a dotted line. A dotted line denotes a weak (or unsafe unretained reference). Strong references are always solid lines.

strong和weak引用的講解

Figure 3.9 RandomPossessions with retain cycle avoided

----------------------------分割線-----------------------

下面是我的了解:

在了解retain cycle形成的過程中有幾個原則需要特别注意一下:

1.當Object的referenced counter為0時它會調用自己的dealloc()來企圖銷毀自己,也就是釋放這塊記憶體;

2.在調用dealloc()中會調用它的child object的release方法,目的是将它的child object的referenced counter減一,否則它把自己釋放了,但是它曾經使用過的child object的referenced counter一直不能跟着更新,那麼它的child object就會一直不能釋放,因為referenced counter不能為0,而這是不能允許存在的情況,這叫做memory leak。

其實在上面的這篇原文中也提及到了一點,就是當使用關鍵字__weak來修改一個變量時,除了不會影響referenced object‘s counter(即它所指向的那個對象的引用計數。如果使用__strong的話就會被所指向的對象中的ounter加1)之外,還有一個智能的地方,就是當這個變量本身的referenced counter變為0時,我們偉大的ARC會自動給這個變量賦上nil,也就是回收了。關于這一點的解釋在上面的參考中有實際的代碼可以測試,我就貼個圖吧:

strong和weak引用的講解

注意看當列印[calculator container]的時候,實際的列印資訊顯示了Container:(null)。

關于這一點在apple的官方文檔中也有描述,連結位址為:點選打開連結, 具體的内容為下面的描述,我好想什麼都沒幹,就光copy和plast了。

use strong references for their synthesized instance variables. To declare a weak reference, add an attribute to the property, like this:

@property (weak) id delegate;      

Note: The opposite to 

weak

 is 

strong

. There’s no need to specify the 

strong

 attribute explicitly, because it is the default.

Local variables (and non-property instance variables) also maintain strong references to objects by default. This means that the following code will work exactly as you expect:

NSDate *originalDate = self.lastModificationDate;      
self.lastModificationDate = [NSDate date];      
NSLog(@"Last modification date changed from %@ to %@",      
originalDate, self.lastModificationDate);      

In this example, the local variable 

originalDate

 maintains a strong reference to the initial 

lastModificationDate

 object. When the

lastModificationDate

 property is changed, the property no longer keeps a strong reference to the original date, but that date is still kept alive by the

originalDate

 strong variable.

Note: A variable maintains a strong reference to an object only as long as that variable is in scope, or until it is reassigned to another object or 

nil

.

看完後得知,本地變量其實就是預設的strong類型,如果正巧需要一個strong類型的就沒有必要再用"__strong"來修改了,直接寫就得了。

隻有當需要__weak時是需要特别修改的。

對上面的示例代碼解釋一下,以防我日後哪天給忘了。

定義了一個originalDate的指針,預設是strong類型的,就是說它會把被它引用的對象的referenced counter加1.

定義完了之後被self.lastModificationDate初始化,而lastModificationDate其實也是指向一個具體的對象的,比如叫xxDate吧,它描述的應該是時間日期這些東西。這樣初始化一下子之後,xxDate的referenced counter就會因為originalDate的引用被加1。假設此時xxDate隻有它們兩個引用,那麼referenced counter就是2。然後呢。lastModficicationDate被重新指派了,重新指派的意思就是說它不再指向xxDate了,而是指向了一個新的yyDate,這個yyDate代表的是另一個時間日期的描述,這樣呢,原來的xxDate的referenced counter就由2減為1了,就是說此時隻有originalDate在引用它關心它。如果此時originalDate不是strong類型,而是weak類型的,那麼一旦lastModificationDate被重新指派,那麼xxDate的referenced counter就變為0了,因為weak類型的指針不會增加它引用的對象的referenced counter,即在一開始給originalDate指派的時候,xxDate的referenced counter就一直是1,沒有因為多了個引用它的originalDate而增加1,應為originalDate是weak類型的。

明白了上面的這個特點之後,接着就又看到了一個有意思的情況,就是如何給weak類型的變量初始化。可能你會想當然的覺得很簡單,上例中初始化代碼為:

NSDate *originalDate = self.lastModificationDate;      

修改為weak後為:

NSDate __weak *originalDate = self.lastModificationDate;      

注意了,如果此時lastModificationDate指向的那個object的referenced counter不是0,那麼originalDate是能夠指向lastModificationDate指向的那個object的。如果用一個referenced counter是0的object來初始化__weak修改的指針會怎麼樣?

示例代碼:

NSObject * __weak someObject = [[NSObject alloc] init];      

當alloc出來一個示例之後發現沒人能使它的referenced counter由0增加1,它又會自動被ARC銷毀的,且someObject也會被ARC指派nil。其實質就是someObject就眼睜睜的看着到嘴的鴨子又飛了,這是一種怎麼的樣感受呢,我們不得而知。

是以呢,人們就想到了一種簡單的辦法,就是不管someObject能不能引用别人,它自己得先保證自己别被ARC滅了,referenced counter變為0就會被滅,不讓它為0就行了,如果不為0呢?就是得找個人假裝引用它就行了。

NSObject *cachedObject = self.someWeakProperty;           // 1      
if (cachedObject) {                                       // 2      
[someObject doSomethingImportantWith:cachedObject];   // 3      
}                                                         // 4      
cachedObject = nil;        

定義一個strong類型的cachedObject來引用someWeakProperty,這樣就保證後者的referenced counter不為0了,自保完成,接下來再去幹革命。革命完成後就自殺吧。

好了,就先寫到這裡吧,目前剛看到這裡。

-------------分割線-----------

好吧,再接着編輯一下,接下來是使用copy屬性的情況,參考的文檔還是apple官方的原文。點選打開連結

我的用了原文中的示例代碼,源碼和運作結果如下圖:

strong和weak引用的講解
strong和weak引用的講解

原諒我沒有把源碼以文本的方式貼在文章裡,主要是太懶了,而且沒有這樣有說服力。

從h檔案可以看到聲明了兩個property,一個是strong屬性的firstName,哦對了,strong是預設的,可以不寫的;另一個是copy屬性的cfirstName;

補充一點,就是不是任何類型都可以使用copy屬性的,能夠使用它的需要遵從NSCopying協定:

Note: Any object that you wish to set for a 

copy

 property must support 

NSCopying

, which means that it should conform to the 

NSCopying

 protocol. Protocols are described in Protocols Define Messaging Contracts. For more information on 

NSCopying

, see 

NSCopying

 or the Advanced Memory Management Programming Guide.

當使用nameString給它倆指派的時候可以看出來,它們指向的object位址不同:firstName指向了與nameString相同的對象,而cfirstName則指向了另外一個位址,這個位址應該就是nameString的副本的位址,即nameString的copy的位址,這樣一來的話就随便nameString變成什麼樣了,sfirstName的值都不會改變了。我想等我多個月份以後再回來看看我寫篇日記我肯定會說:你行不行啊,連這樣的内容都要記下來寫成一篇日記,跟每天記三餐都吃什麼有什麼差別。但是現在的能力不夠,隻能記一下這些基本的知識。

繼續閱讀