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ū)別
Copy,Strong的區(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)的。