1. 程式人生 > >strong和weak引用的講解

strong和weak引用的講解

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

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:

Figure 3.7

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.

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.

Figure 3.9

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,也就是回收了。關於這一點的解釋在上面的參考中有實際的程式碼可以測試,我就貼個圖吧:


注意看當列印[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 thelastModificationDate property is changed, the property no longer keeps a strong reference to the original date, but that date is still kept alive by theoriginalDate 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官方的原文。點選開啟連結

我的用了原文中的示例程式碼,原始碼和執行結果如下圖:



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

從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 . For more information on NSCopying, see  or the .


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

相關推薦

strongweak引用講解

由於這幾天一直在學習ViewController之間的資料傳輸方法,學著學著就有了疑問:為什麼在前向傳輸時(forward)可以使用屬性傳值,而後向傳輸(backward)時不能再使用,為了弄清楚這個問題,搜了很多文章,大部分都是在講傳輸方法的使用,沒有找到原因,但是根據蛛絲馬跡找到了strong和wea

ARC中strongweak的探究

曾幾何時, 自己也是對 strong/retain/weak等暈頭轉向, 今天看到了自己之前整理的關於ARC中的 strong指標和weak指標的 demo 和幾篇文章, 所以便來總結一下. 簡介 ARC是自iOS 5之後增加的新特性,完全消除了手動管理

ios中strongweak的解釋理解

來自stackoverflow解釋的挺有意思的 Imagine our object is a dog, and that the dog wants to run away (be deallocated). Strong pointers are like a leas

strongweak的區別

一、簡介 ARC是自iOS 5之後增加的新特性,完全消除了手動管理記憶體的煩瑣,編譯器會自動在適當的地方插入適當的retain、release、autorelease語句。你不再需要擔心記憶體管理,因為編譯器為你處理了一切 注意:ARC 是編譯器特性,而不是 iOS

iOS中用strongweak來修飾成員變數的對比

對於純程式碼佈局,用@property宣告成員變數時,我是很自然的用strong來修飾的。然後突然有人問我用weak來修飾可不可以,我第一反應是不可以,因為用weak來修飾,初始化過後就會被釋放掉,就算我第一句寫了初始化的方法,立即執行addSubView也是沒

iOS 5中的strongweak關鍵字

iOS 5 中對屬性的設定新增了strong 和weak關鍵字來修飾屬性 strong 用來修飾強引用的屬性; @property (strong) SomeClass * aObject; 對應原來的 @property (retain) SomeClass * aObj

重新理解strongweak(強引用,弱引用),以及strongcopy的區別

- (void)test: {     NSMutableString *mStr = [NSMutableStringstringWithFormat:@"hello"];     self.sStr   = mStr;     self.cStr     = mStr;     NSLog

strong and weak引用引用的區別

(weak和strong)不同的是 當一個物件不再有strong型別的指標指向它的時候 它會被釋放  ,即使還有weak型指標指向它。 一旦最後一個strong型指標離去 ,這個物件將被釋放,所有剩餘的weak型指標都將被清除。 可能有個例子形容是妥當的。 想象我們的物件是一條狗,狗想要跑掉(被釋放)。 s

《Objective-C高階程式設計:引用計數strongweak

轉載請註明出處 如果覺得文章對你有所幫助,請通過留言或關注微信公眾帳號wangzzstrive來支援我,謝謝! 一、前言 這本書由日本人Kazuki Sakamoto和Tomohiko Furumoto所著,主要講了ARC、Blocks、GCD三個模組。總

GNU的strong symbolweak symbol

一個 glib 根據 pos ati mic 例子程序 來看 int 首先,同樣的原型的兩個函數在連個不同的c文件中都有定義,把這兩個c文件編譯、連接在一起,也沒有什麽錯誤。原因就是因為,gcc中有一個strong symbol和weak symbol的概念。默認函數定義都

引用引用WeakReference,SoftReference,最簡講解,以及一個應用場景

他講的很好,但是我看了一下,有些地方講的不是很清楚,導致我當時困惑了一會。這裡簡單加點內容。 實際上, Car car = new Car(22000,"silver"); WeakReference<Car> weakCar = new WeakRefere

批量kill java進程方法-引出子shell引用

並不會 echo 父shel gre 影響 用例 先來 實現 ext 方法: kill –9 `pgrep java` 使用上述命令可以將服務器上運行的所有java進程一次性kill掉。 擴展:子shell和反應用在shell腳本中的作用 先來看

關於內存的劃分引用傳參數的區別

如果 引用 也會 對象賦值 空間 函數 代碼段 劃分 剛才 1.堆 2.棧 空間較小 3.全局 4.代碼段 要搞懂每個區域放什麽東西 堆 復雜類型棧 變量靜 代復雜類型對象沒有指針指的時候,堆中的內容垃圾回收1函數傳對象的時候,其實是首先在堆裏面開一個內存放對象屬性

java -- JVM的符號引用直接引用

不同 class文件 rep 類加載 repl 符號 ava 內存 內存地址 在JVM中類加載過程中,在解析階段,Java虛擬機會把類的二級制數據中的符號引用替換為直接引用。 1.符號引用(Symbolic References):   符號引用以一組符號來描述所引用的目標

Word2010設置題註交叉引用方法

sgx aso hmm htop osc n+1 打印 best wap 設置題註 點擊圖片-->右鍵-->插入題註-->新建標簽:“圖”-->選擇新建標簽“圖”-->修改“編號”-->勾選包含章節號-->設置章節起始樣式:標題

深入了解java虛擬機---類加載機制主動引用被動引用

沒有 put log 完成 開始 檢查 觸發 清單 場景 當類被編譯為.class文件後,如何在jvm中被加載的呢 總共七個步驟:加載,驗證,準備,解析,初始化,使用,卸載。其中加載,驗證,準備,初始化,卸載都必須按照順序來。解析可以在初始化後再開始。使用就可有可無了

iOS 開發 property,strongweak,retain,assign,copy,nomatic 的區別及使用

並且 指針變量 使用 char 導致 數據 ios5 ret int 1:ARC環境下,strong代替retain.weak代替assign,xcode 4.2(ios sdk4.3和以下版本)和之前的版本使用的是retain和assign,是不支持ARC的。xcode

Java實參形參與傳值引用

函數 有效 順序 數組 形參 div spa stat 數據 實參和形參的定義: 形參出現函數定義中,在整個函數體內都可以使用,離開函數則不能使用。 實參出現在主函數中,進入被調函數後,實參變量也不能使用。 形參和實參的功能是做數據傳送。發生函數調用時,主調函數把實參的值傳

Python中對象的引用共享引用

col 即使 列表 標簽 同一性 例子 垃圾回收 是否 垃圾 在Python中先創建一個對象,然後再將變量指向所創建的對象。 對於每個對象,都有一個頭部信息,在信息中就標記了這個對象的類型信息。每當一個變量名被賦予了一個新的對象,之前那個對象占用的空間就回被回收(如果此時這

方法傳遞參數&按值傳遞引用傳遞

傳遞參數方法傳遞參數: 方法就像一個任務要做一件事情,而參數就是一個事情裏所需要給予的條件。就像要去買東西這樣一件事情,這件事情的參數是需要購買物品的錢,需要傳遞一個“錢”的參數給這個方法它才能進行執行。例如: 釋義:method方法聲明了一個int類型的參數,參數名為a。在main方法裏調用metho