assign、weak、retain、strong、copy、__weak、__strong、__block關(guān)鍵字

assign

用于對基本數(shù)據(jù)類型進行賦值操作,不更改引用計數(shù)。也可以用來修飾對象,但是,被assign修飾的對象在釋放后,指針的地址還是存在的,也就是說指針并沒有被置為nil,成為野指針。如果后續(xù)在分配對象到堆上的某塊內(nèi)存時,正好分到這塊地址,程序就會crash。之所以可以修飾基本數(shù)據(jù)類型,因為基本數(shù)據(jù)類型一般分配在棧上,棧的內(nèi)存會由系統(tǒng)自動處理,不會造成野指針。

weak

修飾Object類型,修飾的對象在釋放后,指針地址會被置為nil,是一種弱引用。在ARC環(huán)境下,為避免循環(huán)引用,往往會把delegate屬性用weak修飾;在MRC下使用assign修飾。weak和strong不同的是:當(dāng)一個對象不再有strong類型的指針指向它的時候,它就會被釋放,即使還有weak型指針指向它,那么這些weak型指針也將被清除。

weak 與 assgin 的區(qū)別
assigin 可以用非 OC 對象,而 weak 必須用于 OC 對象

strong

ARC下的strong等同于MRC下的retain都會把對象引用計數(shù)加1。

copy

會在內(nèi)存里拷貝一份對象,兩個指針指向不同的內(nèi)存地址。一般用來修飾NSString等有對應(yīng)可變類型的對象,因為他們有可能和對應(yīng)的可變類型(NSMutableString)之間進行賦值操作,為確保對象中的字符串不被修改 ,應(yīng)該在設(shè)置屬性是拷貝一份。而若用strong修飾,如果對象在外部被修改了,會影響到屬性。

strong與copy的區(qū)別

CopyStrong的區(qū)別需要了解點內(nèi)存管理的知識,Strong是ARC下引入的修飾,相當(dāng)于手動管理內(nèi)存(MRC)下的retain,在相關(guān)代碼下,常??吹接械娜擞胏opy修飾NSString,NSArray,NSDictionary等存在可變與不可變之分的對象,常常會用copy,而不是strong,下面代碼來解釋一下strong與copy的區(qū)別:

先說明一下什么叫做淺拷貝,什么叫做深拷貝;

淺Copy:可以理解為指針的復(fù)制,只是多了一個指向這塊內(nèi)存的指針,共用一塊內(nèi)存。

深Copy:理解為內(nèi)存的復(fù)制,兩塊內(nèi)存是完全不同的,也就是兩個對象指針分別指向不同的內(nèi)存,互不干涉。

舉例

首先在類延展中聲明兩個屬性變量

@property (nonatomic, strong)NSString * stringStrong;   //strong修飾的字符串對象  
@property (nonatomic, copy)NSString * stringCopy;       //copy修飾的字符串對象  

接著創(chuàng)建兩個不可變字符串(NSString)

//新創(chuàng)建兩個NSString對象  
NSString * strong1 = @"I am Strong!";  
NSString * copy1 = @"I am Copy!";  

將兩個屬性分別進行賦值

//初始化兩個字符串  
self.stringStrong = strong1;  
self.stringCopy = copy1;  

分別打印一下四個變量的內(nèi)存地址:

NSLog(@"strong1 = %p",strong1);  
NSLog(@"stringStrong = %p",self.stringStrong);  
NSLog(@"copy1 = %p",copy1);  
NSLog(@"stringCopy = %p",self.stringCopy);  

結(jié)果如下:可以看出,此時無論是strong修飾的字符串還是copy修飾的字符串,都進行了淺Copy

2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] strong1 = 0x10a0b3078  
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] stringStrong = 0x10a0b3078  
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] copy1 = 0x10a0b3098  
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] stringCopy = 0x10a0b3098  

如果創(chuàng)建兩個可變字符串對象(NSMutableString)

//新創(chuàng)建兩個NSMutableString對象  
NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"];  
NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"]; 

分別對屬性再次進行賦值

self.stringStrong = mutableStrong;  
self.stringCopy = mutableCopy;

分別打印一下四個變量的地址:結(jié)果如下:這時就發(fā)現(xiàn)了,用strong修飾的字符串依舊進行了淺Copy,而由copy修飾的字符串進行了深Copy,所以mutableStrong與stringStrong指向了同一塊內(nèi)存,而mutableCopy和stringCopy指向的是完全兩塊不同的內(nèi)存。

2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] mutableStrong = 0x7fccba425d60  
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] stringStrong = 0x7fccba425d60  
2018-03-11 18:59:06.332 StrongOrCopy[5046:421886] mutableCopy = 0x7fccba40d7c0  
2018-03-11 18:59:06.333 StrongOrCopy[5046:421886] stringCopy = 0x7fccba4149e0  

那么有什么用呢,實例來看一下有什么區(qū)別:

首先是對不可變字符串進行操作:

//新創(chuàng)建兩個NSString對象  
NSString * strong1 = @"I am Strong!";  
NSString * copy1 = @"I am Copy!";  
  
//初始化兩個字符串  
self.stringStrong = strong1;  
self.stringCopy = copy1;  
  
//兩個NSString進行操作  
[strong1 stringByAppendingString:@"11111"];  
[copy1 stringByAppendingString:@"22222"];  

分別對在字符串后面進行拼接,當(dāng)然這個拼接對原字符串沒有任何的影響,因為不可變自字符串調(diào)用的方法都是有返回值的,原來的值是不會發(fā)生變化的。打印如下,對結(jié)果沒有任何的影響:

2018-03-11 19:15:26.729 StrongOrCopy[5146:439360] strong1 = I am Strong!  
2018-03-11 19:15:26.729 StrongOrCopy[5146:439360] stringStrong = I am Strong!  
2018-03-11 19:15:26.729 StrongOrCopy[5146:439360] copy1 = I am Copy!  
2018-03-11 19:15:26.729 StrongOrCopy[5146:439360] stringCopy = I am Copy!  

然后是對可變字符串進行操作:

//新創(chuàng)建兩個NSMutableString對象  
NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"];  
NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"];  
  
//初始化兩個字符串  
self.stringStrong = mutableStrong;  
self.stringCopy = mutableCopy;  
  
//兩個MutableString進行操作  
[mutableStrong appendString:@"Strong!"];  
[mutableCopy appendString:@"Copy!"];  

再來看一下結(jié)果:對mutableStrong進行的操作,由于用strong修飾的stringStrong沒有進行深Copy,導(dǎo)致共用了一塊內(nèi)存,當(dāng)mutableStrong對內(nèi)存進行了操作的時候,實際上對stringStrong也進行了操作; 相反,用copy修飾的stringCopy進行了深Copy,也就是說stringCopy與mutableCopy用了兩塊完全不同的內(nèi)存,所以不管mutableCopy進行了怎么樣的變化,原來的stringCopy都不會發(fā)生變化。這就在日常中避免了出現(xiàn)一些不可預(yù)計的錯誤。

2018-03-11 19:20:27.652 StrongOrCopy[5245:446189] stringStrong = StrongMutableStrong!  
2018-03-11 19:20:27.652 StrongOrCopy[5245:446189] mutableStrong = StrongMutableStrong!  
2018-03-11 19:20:27.652 StrongOrCopy[5245:446189] stringCopy = CopyMutable  
2018-03-11 19:20:27.652 StrongOrCopy[5245:446189] mutableCopy = CopyMutableCopy!  
總結(jié)

在不可變對象之間進行轉(zhuǎn)換,strong與copy作用是一樣的,但如果在不可變與可變之間進行操作,strong與copy就不同了。

__weak

作為程序猿還是代碼具有說服力,上栗子:

首先定義一個類 MyObject 繼承 NSObject,并添加一個屬性 text,重寫了description方法,返回 text 的值。這個主要是因為編譯器本身對 NSString 是有優(yōu)化的,創(chuàng)建的 string 對象有可能是靜態(tài)存儲區(qū)永不釋放的,為了避免使用 NSString 引起一些問題,還是創(chuàng)建一個 NSObject 對象比較合適。

自定義了一個 ZXLog 方法輸出對象相關(guān)值,定義如下:

#define ZXLog(prefix,Obj) {NSLog(@"變量內(nèi)存地址:%p, 變量值:%p, 指向?qū)ο笾担?@, --> %@",&Obj,Obj,Obj,prefix);}

代碼:

MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
ZXLog(@"obj", obj);
   
__weak MyObject *weakObj = obj;
ZXLog(@"weakObj", weakObj);
   
void(^testBlock)(void) = ^(){
    ZXLog(@"weakObj - block", weakObj);
};
testBlock();
obj = nil;
testBlock();

打印結(jié)果:

變量內(nèi)存地址:0x7fff510b7c78, 變量值:0x60000001a270, 指向?qū)ο笾担?lt;MyObject: 0x60000001a270>, --> obj
變量內(nèi)存地址:0x7fff510b7c70, 變量值:0x60000001a270, 指向?qū)ο笾担?lt;MyObject: 0x60000001a270>, --> weakObj
變量內(nèi)存地址:0x60400044cfe0, 變量值:0x60000001a270, 指向?qū)ο笾担?lt;MyObject: 0x60000001a270>, --> weakObj - block
變量內(nèi)存地址:0x60400044cfe0, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj - block

從上面的結(jié)果可以看到

  • block 內(nèi)的 weakObj 和外部的 weakObj 并不是同一個變量

  • block 捕獲了 weakObj 同時也是對 obj 進行了弱引用,當(dāng)我在 block 外把 obj 釋放了之后,block 內(nèi)也讀不到這個變量了

  • 當(dāng) obj 賦值 nil 時,block 內(nèi)部的 weakObj 也為 nil 了,也就是說 obj 實際上是被釋放了,可見 __weak 是可以避免循環(huán)引用問題的

接下來看第二段代碼:

MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
ZXLog(@"obj", obj);
    
__weak MyObject *weakObj = obj;
ZXLog(@"weakObj-0", weakObj);
    
void(^testBlock)(void) = ^(){
   __strong MyObject *strongObj = weakObj;
   ZXLog(@"weakObj - block", weakObj);
   ZXLog(@"strongObj - block", strongObj);
};
    
ZXLog(@"weakObj-1", weakObj);
testBlock();
ZXLog(@"weakObj-2", weakObj);
obj = nil;
testBlock();
ZXLog(@"weakObj-3", weakObj);

打印結(jié)果:

變量內(nèi)存地址:0x7fff517bcc78, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> obj
變量內(nèi)存地址:0x7fff517bcc70, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> weakObj-0
變量內(nèi)存地址:0x7fff517bcc70, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> weakObj-1
變量內(nèi)存地址:0x600000259df0, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> weakObj - block
變量內(nèi)存地址:0x7fff517bcb28, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> strongObj - block
變量內(nèi)存地址:0x7fff517bcc70, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> weakObj-2
變量內(nèi)存地址:0x600000259df0, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj - block
變量內(nèi)存地址:0x7fff517bcb28, 變量值:0x0, 指向?qū)ο笾担?null), --> strongObj - block
變量內(nèi)存地址:0x7fff517bcc70, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj-3

如果你看過 AFNetworking 的源碼,會發(fā)現(xiàn) AFN 中作者會把變量在 block 外面先用 __weak 聲明,在 block 內(nèi)把前面 weak 聲明的變量賦值給 __strong 修飾的變量這種寫法。

從上面例子我們看到即使在 block 內(nèi)部用 strong 強引用了外面的 weakObj ,但是一旦 obj 釋放了之后,內(nèi)部的 strongObj 同樣會變成 nil,那么這種寫法又有什么意義呢?

下面再看一段代碼:

MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
ZXLog(@"obj", obj);

__weak MyObject *weakObj = obj;
ZXLog(@"weakObj-0", weakObj);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    __strong MyObject *strongObj = weakObj;
    ZXLog(@"weakObj - block", weakObj);
    ZXLog(@"strongObj - block", strongObj);
    
    sleep(3);
    
    ZXLog(@"weakObj - block", weakObj);
    ZXLog(@"strongObj - block", strongObj);
});
NSLog(@"------ sleep 1s");
sleep(1);
obj = nil;
ZXLog(@"weakObj-1", weakObj);
NSLog(@"------ sleep 5s");
sleep(5);
ZXLog(@"weakObj-2", weakObj);

打印結(jié)果:

變量內(nèi)存地址:0x7fff5f891c78, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> obj
變量內(nèi)存地址:0x7fff5f891c70, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> weakObj-0
------ sleep 1s
變量內(nèi)存地址:0x60000025a2a0, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> weakObj - block
變量內(nèi)存地址:0x700000722d78, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> strongObj - block
變量內(nèi)存地址:0x7fff5f891c70, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> weakObj-1
------ sleep 5s
變量內(nèi)存地址:0x60000025a2a0, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> weakObj - block
變量內(nèi)存地址:0x700000722d78, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> strongObj - block
變量內(nèi)存地址:0x7fff5f891c70, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj-2

代碼中使用 sleep 來保證代碼執(zhí)行的先后順序。

從結(jié)果中我們可以看到,只要 block 部分執(zhí)行了,即使我們中途釋放了 obj,block 內(nèi)部依然會繼續(xù)強引用它。對比上面代碼,也就是說 block 內(nèi)部的 __strong 會在執(zhí)行期間進行強引用操作,保證在 block 內(nèi)部 strongObj 始終是可用的。這種寫法非常巧妙,既避免了循環(huán)引用的問題,又可以在 block 內(nèi)部持有該變量。

綜合兩部分代碼,我們平時在使用時,常常先判斷 strongObj 是否為空,然后再執(zhí)行后續(xù)代碼,如下方式:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    __strong MyObject *strongObj = weakObj;
    if(strongObj){
        // do something ...
    }
});

這種方式先判斷 Obj 是否被釋放,如果未釋放在執(zhí)行我們的代碼的時候保證其可用性。

__block

直接上代碼:

MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object-1";
ZXLog(@"obj",obj);

__block MyObject *blockObj = obj;
obj = nil;
ZXLog(@"blockObj -1",blockObj);

void(^testBlock)(void) = ^(){
    ZXLog(@"blockObj - block",blockObj);
    MyObject *obj2 = [[MyObject alloc]init];
    obj2.text = @"my-object-2";
    ZXLog(@"obj2",obj2);
    blockObj = obj2;
    ZXLog(@"blockObj - block",blockObj);
};
ZXLog(@"%@",testBlock);
ZXLog(@"blockObj -2",blockObj);
testBlock();
ZXLog(@"blockObj -3",blockObj);

打印結(jié)果:

變量內(nèi)存地址:0x7fff5ddc1c78, 變量值:0x60000000ac60, 指向?qū)ο笾担?lt;MyObject: 0x60000000ac60>, --> obj
變量內(nèi)存地址:0x7fff5ddc1c70, 變量值:0x60000000ac60, 指向?qū)ο笾担?lt;MyObject: 0x60000000ac60>, --> blockObj -1
變量內(nèi)存地址:0x7fff5ddc1c30, 變量值:0x60400045ce00, 指向?qū)ο笾担?lt;__NSMallocBlock__: 0x60400045ce00>, --> %@
變量內(nèi)存地址:0x6040004588f8, 變量值:0x60000000ac60, 指向?qū)ο笾担?lt;MyObject: 0x60000000ac60>, --> blockObj -2
變量內(nèi)存地址:0x6040004588f8, 變量值:0x60000000ac60, 指向?qū)ο笾担?lt;MyObject: 0x60000000ac60>, --> blockObj - block
變量內(nèi)存地址:0x7fff5ddc1ba8, 變量值:0x60000000ace0, 指向?qū)ο笾担?lt;MyObject: 0x60000000ace0>, --> obj2
變量內(nèi)存地址:0x6040004588f8, 變量值:0x60000000ace0, 指向?qū)ο笾担?lt;MyObject: 0x60000000ace0>, --> blockObj - block
變量內(nèi)存地址:0x6040004588f8, 變量值:0x60000000ace0, 指向?qū)ο笾担?lt;MyObject: 0x60000000ace0>, --> blockObj -3

可以看到在 block 聲明前后 blockObj 的內(nèi)存地址是有所變化的,這涉及到 block 對外部變量的內(nèi)存管理問題。

下面來看看 __block 能不能避免循環(huán)引用的問題:

MyObject *obj = [[MyObject alloc]init];
obj.text = @"11111111111111";
ZXLog(@"obj",obj);

__block MyObject *blockObj = obj;
obj = nil;
void(^testBlock)(void) = ^(){
    ZXLog(@"blockObj - block",blockObj);
};
obj = nil;
testBlock();
ZXLog(@"blockObj",blockObj);

打印結(jié)果:

變量內(nèi)存地址:0x7fff57e48c78, 變量值:0x60000001b520, 指向?qū)ο笾担?lt;MyObject: 0x60000001b520>, --> obj
變量內(nèi)存地址:0x604000457818, 變量值:0x60000001b520, 指向?qū)ο笾担?lt;MyObject: 0x60000001b520>, --> blockObj - block
變量內(nèi)存地址:0x604000457818, 變量值:0x60000001b520, 指向?qū)ο笾担?lt;MyObject: 0x60000001b520>, --> blockObj

當(dāng)外部 obj 指向 nil 的時候,obj 理應(yīng)被釋放,但實際上 blockObj 依然強引用著 obj,obj 其實并沒有被真正釋放。因此使用 __block 并不能避免循環(huán)引用的問題。

但是我們可以通過手動釋放 blockObj 的方式來釋放 obj,這就需要我們在 block 內(nèi)部將要退出的時候手動釋放掉 blockObj ,如下這種形式:

MyObject *obj = [[MyObject alloc]init];
obj.text = @"11111111111111";
ZXLog(@"obj",obj);

__block MyObject *blockObj = obj;
obj = nil;
void(^testBlock)(void) = ^(){
    ZXLog(@"blockObj - block",blockObj);
    blockObj = nil;
};
obj = nil;
testBlock();
ZXLog(@"blockObj",blockObj);

必須記住在 block 底部釋放掉 block 變量,這其實跟 MRC 的形式有些類似了,不太適合 ARC這種形式既能保證在 block 內(nèi)部能夠訪問到 obj,又可以避免循環(huán)引用的問題,但是這種方法也不是完美的,其存在下面幾個問題

當(dāng)在 block 外部修改了 blockObj 時,block 內(nèi)部的值也會改變,反之在 block 內(nèi)部修改 blockObj 在外部再使用時值也會改變。這就需要在寫代碼時注意這個特性可能會帶來的一些隱患
__block 其實提升了變量的作用域,在 block 內(nèi)外訪問的都是同一個 blockObj 可能會造成一些隱患。

總結(jié)

__weak 本身是可以避免循環(huán)引用的問題的,但是其會導(dǎo)致外部對象釋放了之后,block 內(nèi)部也訪問不到這個對象的問題,我們可以通過在 block 內(nèi)部聲明一個 __strong 的變量來指向 weakObj,使外部對象既能在 block 內(nèi)部保持住,又能避免循環(huán)引用的問題。

__block 本身無法避免循環(huán)引用的問題,但是我們可以通過在 block 內(nèi)部手動把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題。另外一點就是 __block 修飾的變量在 block 內(nèi)外都是唯一的,要注意這個特性可能帶來的隱患。

但是__block有一點:這只是限制在ARC環(huán)境下。在非arc下,__block是可以避免引用循環(huán)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容