assign
用于對(duì)基本數(shù)據(jù)類型進(jìn)行賦值操作,不更改引用計(jì)數(shù)铺纽。也可以用來修飾對(duì)象柬帕,但是,被assign修飾的對(duì)象在釋放后狡门,指針的地址還是存在的雕崩,也就是說指針并沒有被置為nil,成為野指針融撞。如果后續(xù)在分配對(duì)象到堆上的某塊內(nèi)存時(shí)盼铁,正好分到這塊地址,程序就會(huì)crash尝偎。之所以可以修飾基本數(shù)據(jù)類型饶火,因?yàn)榛緮?shù)據(jù)類型一般分配在棧上,棧的內(nèi)存會(huì)由系統(tǒng)自動(dòng)處理致扯,不會(huì)造成野指針肤寝。
weak
修飾Object類型,修飾的對(duì)象在釋放后抖僵,指針地址會(huì)被置為nil鲤看,是一種弱引用。在ARC環(huán)境下耍群,為避免循環(huán)引用义桂,往往會(huì)把delegate屬性用weak修飾找筝;在MRC下使用assign修飾。weak和strong不同的是:當(dāng)一個(gè)對(duì)象不再有strong類型的指針指向它的時(shí)候慷吊,它就會(huì)被釋放袖裕,即使還有weak型指針指向它,那么這些weak型指針也將被清除溉瓶。
weak 與 assgin 的區(qū)別
assigin 可以用非 OC 對(duì)象,而 weak 必須用于 OC 對(duì)象
strong
ARC下的strong等同于MRC下的retain都會(huì)把對(duì)象引用計(jì)數(shù)加1急鳄。
copy
會(huì)在內(nèi)存里拷貝一份對(duì)象,兩個(gè)指針指向不同的內(nèi)存地址堰酿。一般用來修飾NSString等有對(duì)應(yīng)可變類型的對(duì)象疾宏,因?yàn)樗麄冇锌赡芎蛯?duì)應(yīng)的可變類型(NSMutableString)之間進(jìn)行賦值操作,為確保對(duì)象中的字符串不被修改 触创,應(yīng)該在設(shè)置屬性是拷貝一份坎藐。而若用strong修飾,如果對(duì)象在外部被修改了嗅榕,會(huì)影響到屬性。
strong與copy的區(qū)別
Copy
吵聪,Strong
的區(qū)別需要了解點(diǎn)內(nèi)存管理的知識(shí)凌那,Strong是ARC下引入的修飾,相當(dāng)于手動(dòng)管理內(nèi)存(MRC)下的retain吟逝,在相關(guān)代碼下帽蝶,常常看到有的人用copy修飾NSString,NSArray,NSDictionary等存在可變與不可變之分的對(duì)象块攒,常常會(huì)用copy励稳,而不是strong,下面代碼來解釋一下strong與copy的區(qū)別:
先說明一下什么叫做淺拷貝,什么叫做深拷貝;
淺Copy:可以理解為指針的復(fù)制囱井,只是多了一個(gè)指向這塊內(nèi)存的指針驹尼,共用一塊內(nèi)存。
深Copy:理解為內(nèi)存的復(fù)制庞呕,兩塊內(nèi)存是完全不同的新翎,也就是兩個(gè)對(duì)象指針分別指向不同的內(nèi)存,互不干涉住练。
舉例
首先在類延展中聲明兩個(gè)屬性變量
@property (nonatomic, strong)NSString * stringStrong; //strong修飾的字符串對(duì)象
@property (nonatomic, copy)NSString * stringCopy; //copy修飾的字符串對(duì)象
接著創(chuàng)建兩個(gè)不可變字符串(NSString)
//新創(chuàng)建兩個(gè)NSString對(duì)象
NSString * strong1 = @"I am Strong!";
NSString * copy1 = @"I am Copy!";
將兩個(gè)屬性分別進(jìn)行賦值
//初始化兩個(gè)字符串
self.stringStrong = strong1;
self.stringCopy = copy1;
分別打印一下四個(gè)變量的內(nèi)存地址:
NSLog(@"strong1 = %p",strong1);
NSLog(@"stringStrong = %p",self.stringStrong);
NSLog(@"copy1 = %p",copy1);
NSLog(@"stringCopy = %p",self.stringCopy);
結(jié)果如下:可以看出地啰,此時(shí)無論是strong修飾的字符串還是copy修飾的字符串,都進(jìn)行了淺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)建兩個(gè)可變字符串對(duì)象(NSMutableString)
//新創(chuàng)建兩個(gè)NSMutableString對(duì)象
NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"];
NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"];
分別對(duì)屬性再次進(jìn)行賦值
self.stringStrong = mutableStrong;
self.stringCopy = mutableCopy;
分別打印一下四個(gè)變量的地址:結(jié)果如下:這時(shí)就發(fā)現(xiàn)了亏吝,用strong修飾的字符串依舊進(jìn)行了淺Copy,而由copy修飾的字符串進(jìn)行了深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
那么有什么用呢惜论,實(shí)例來看一下有什么區(qū)別:
首先是對(duì)不可變字符串進(jìn)行操作:
//新創(chuàng)建兩個(gè)NSString對(duì)象
NSString * strong1 = @"I am Strong!";
NSString * copy1 = @"I am Copy!";
//初始化兩個(gè)字符串
self.stringStrong = strong1;
self.stringCopy = copy1;
//兩個(gè)NSString進(jìn)行操作
[strong1 stringByAppendingString:@"11111"];
[copy1 stringByAppendingString:@"22222"];
分別對(duì)在字符串后面進(jìn)行拼接,當(dāng)然這個(gè)拼接對(duì)原字符串沒有任何的影響株茶,因?yàn)椴豢勺冏宰址{(diào)用的方法都是有返回值的来涨,原來的值是不會(huì)發(fā)生變化的。打印如下启盛,對(duì)結(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!
然后是對(duì)可變字符串進(jìn)行操作:
//新創(chuàng)建兩個(gè)NSMutableString對(duì)象
NSMutableString * mutableStrong = [NSMutableString stringWithString:@"StrongMutable"];
NSMutableString * mutableCopy = [NSMutableString stringWithString:@"CopyMutable"];
//初始化兩個(gè)字符串
self.stringStrong = mutableStrong;
self.stringCopy = mutableCopy;
//兩個(gè)MutableString進(jìn)行操作
[mutableStrong appendString:@"Strong!"];
[mutableCopy appendString:@"Copy!"];
再來看一下結(jié)果:對(duì)mutableStrong進(jìn)行的操作蹦掐,由于用strong修飾的stringStrong沒有進(jìn)行深Copy,導(dǎo)致共用了一塊內(nèi)存僵闯,當(dāng)mutableStrong對(duì)內(nèi)存進(jìn)行了操作的時(shí)候卧抗,實(shí)際上對(duì)stringStrong也進(jìn)行了操作; 相反,用copy修飾的stringCopy進(jìn)行了深Copy鳖粟,也就是說stringCopy與mutableCopy用了兩塊完全不同的內(nèi)存社裆,所以不管mutableCopy進(jìn)行了怎么樣的變化,原來的stringCopy都不會(huì)發(fā)生變化向图。這就在日常中避免了出現(xiàn)一些不可預(yù)計(jì)的錯(cuò)誤泳秀。
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é)
在不可變對(duì)象之間進(jìn)行轉(zhuǎn)換,strong與copy作用是一樣的榄攀,但如果在不可變與可變之間進(jìn)行操作嗜傅,strong與copy就不同了。
__weak
作為程序猿還是代碼具有說服力檩赢,上栗子:
首先定義一個(gè)類 MyObject 繼承 NSObject吕嘀,并添加一個(gè)屬性 text,重寫了description方法贞瞒,返回 text 的值偶房。這個(gè)主要是因?yàn)榫幾g器本身對(duì) NSString 是有優(yōu)化的,創(chuàng)建的 string 對(duì)象有可能是靜態(tài)存儲(chǔ)區(qū)永不釋放的军浆,為了避免使用 NSString 引起一些問題棕洋,還是創(chuàng)建一個(gè) NSObject 對(duì)象比較合適。
自定義了一個(gè) ZXLog 方法輸出對(duì)象相關(guān)值乒融,定義如下:
#define ZXLog(prefix,Obj) {NSLog(@"變量?jī)?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é)果:
變量?jī)?nèi)存地址:0x7fff510b7c78, 變量值:0x60000001a270, 指向?qū)ο笾担?lt;MyObject: 0x60000001a270>, --> obj
變量?jī)?nèi)存地址:0x7fff510b7c70, 變量值:0x60000001a270, 指向?qū)ο笾担?lt;MyObject: 0x60000001a270>, --> weakObj
變量?jī)?nèi)存地址:0x60400044cfe0, 變量值:0x60000001a270, 指向?qū)ο笾担?lt;MyObject: 0x60000001a270>, --> weakObj - block
變量?jī)?nèi)存地址:0x60400044cfe0, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj - block
從上面的結(jié)果可以看到
block 內(nèi)的 weakObj 和外部的 weakObj 并不是同一個(gè)變量
block 捕獲了 weakObj 同時(shí)也是對(duì) obj 進(jìn)行了弱引用拍冠,當(dāng)我在 block 外把 obj 釋放了之后,block 內(nèi)也讀不到這個(gè)變量了
當(dāng) obj 賦值 nil 時(shí)簇抵,block 內(nèi)部的 weakObj 也為 nil 了庆杜,也就是說 obj 實(shí)際上是被釋放了,可見 __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é)果:
變量?jī)?nèi)存地址:0x7fff517bcc78, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> obj
變量?jī)?nèi)存地址:0x7fff517bcc70, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> weakObj-0
變量?jī)?nèi)存地址:0x7fff517bcc70, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> weakObj-1
變量?jī)?nèi)存地址:0x600000259df0, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> weakObj - block
變量?jī)?nèi)存地址:0x7fff517bcb28, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> strongObj - block
變量?jī)?nèi)存地址:0x7fff517bcc70, 變量值:0x60400000a420, 指向?qū)ο笾担?lt;MyObject: 0x60400000a420>, --> weakObj-2
變量?jī)?nèi)存地址:0x600000259df0, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj - block
變量?jī)?nèi)存地址:0x7fff517bcb28, 變量值:0x0, 指向?qū)ο笾担?null), --> strongObj - block
變量?jī)?nèi)存地址:0x7fff517bcc70, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj-3
如果你看過 AFNetworking 的源碼碟摆,會(huì)發(fā)現(xiàn) AFN 中作者會(huì)把變量在 block 外面先用 __weak 聲明晃财,在 block 內(nèi)把前面 weak 聲明的變量賦值給 __strong 修飾的變量這種寫法。
從上面例子我們看到即使在 block 內(nèi)部用 strong 強(qiáng)引用了外面的 weakObj ,但是一旦 obj 釋放了之后断盛,內(nèi)部的 strongObj 同樣會(huì)變成 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é)果:
變量?jī)?nèi)存地址:0x7fff5f891c78, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> obj
變量?jī)?nèi)存地址:0x7fff5f891c70, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> weakObj-0
------ sleep 1s
變量?jī)?nèi)存地址:0x60000025a2a0, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> weakObj - block
變量?jī)?nèi)存地址:0x700000722d78, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> strongObj - block
變量?jī)?nèi)存地址:0x7fff5f891c70, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> weakObj-1
------ sleep 5s
變量?jī)?nèi)存地址:0x60000025a2a0, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> weakObj - block
變量?jī)?nèi)存地址:0x700000722d78, 變量值:0x6000000133f0, 指向?qū)ο笾担?lt;MyObject: 0x6000000133f0>, --> strongObj - block
變量?jī)?nèi)存地址:0x7fff5f891c70, 變量值:0x0, 指向?qū)ο笾担?null), --> weakObj-2
代碼中使用 sleep 來保證代碼執(zhí)行的先后順序钢猛。
從結(jié)果中我們可以看到伙菜,只要 block 部分執(zhí)行了,即使我們中途釋放了 obj命迈,block 內(nèi)部依然會(huì)繼續(xù)強(qiáng)引用它贩绕。對(duì)比上面代碼,也就是說 block 內(nèi)部的 __strong 會(huì)在執(zhí)行期間進(jìn)行強(qiáng)引用操作壶愤,保證在 block 內(nèi)部 strongObj 始終是可用的淑倾。這種寫法非常巧妙,既避免了循環(huán)引用的問題征椒,又可以在 block 內(nèi)部持有該變量娇哆。
綜合兩部分代碼,我們平時(shí)在使用時(shí)勃救,常常先判斷 strongObj 是否為空碍讨,然后再執(zhí)行后續(xù)代碼,如下方式:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong MyObject *strongObj = weakObj;
if(strongObj){
// do something ...
}
});
這種方式先判斷 Obj 是否被釋放蒙秒,如果未釋放在執(zhí)行我們的代碼的時(shí)候保證其可用性勃黍。
__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é)果:
變量?jī)?nèi)存地址:0x7fff5ddc1c78, 變量值:0x60000000ac60, 指向?qū)ο笾担?lt;MyObject: 0x60000000ac60>, --> obj
變量?jī)?nèi)存地址:0x7fff5ddc1c70, 變量值:0x60000000ac60, 指向?qū)ο笾担?lt;MyObject: 0x60000000ac60>, --> blockObj -1
變量?jī)?nèi)存地址:0x7fff5ddc1c30, 變量值:0x60400045ce00, 指向?qū)ο笾担?lt;__NSMallocBlock__: 0x60400045ce00>, --> %@
變量?jī)?nèi)存地址:0x6040004588f8, 變量值:0x60000000ac60, 指向?qū)ο笾担?lt;MyObject: 0x60000000ac60>, --> blockObj -2
變量?jī)?nèi)存地址:0x6040004588f8, 變量值:0x60000000ac60, 指向?qū)ο笾担?lt;MyObject: 0x60000000ac60>, --> blockObj - block
變量?jī)?nèi)存地址:0x7fff5ddc1ba8, 變量值:0x60000000ace0, 指向?qū)ο笾担?lt;MyObject: 0x60000000ace0>, --> obj2
變量?jī)?nèi)存地址:0x6040004588f8, 變量值:0x60000000ace0, 指向?qū)ο笾担?lt;MyObject: 0x60000000ace0>, --> blockObj - block
變量?jī)?nèi)存地址:0x6040004588f8, 變量值:0x60000000ace0, 指向?qū)ο笾担?lt;MyObject: 0x60000000ace0>, --> blockObj -3
可以看到在 block 聲明前后 blockObj 的內(nèi)存地址是有所變化的,這涉及到 block 對(duì)外部變量的內(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é)果:
變量?jī)?nèi)存地址:0x7fff57e48c78, 變量值:0x60000001b520, 指向?qū)ο笾担?lt;MyObject: 0x60000001b520>, --> obj
變量?jī)?nèi)存地址:0x604000457818, 變量值:0x60000001b520, 指向?qū)ο笾担?lt;MyObject: 0x60000001b520>, --> blockObj - block
變量?jī)?nèi)存地址:0x604000457818, 變量值:0x60000001b520, 指向?qū)ο笾担?lt;MyObject: 0x60000001b520>, --> blockObj
當(dāng)外部 obj 指向 nil 的時(shí)候溉躲,obj 理應(yīng)被釋放榜田,但實(shí)際上 blockObj 依然強(qiáng)引用著 obj益兄,obj 其實(shí)并沒有被真正釋放。因此使用 __block 并不能避免循環(huán)引用的問題箭券。
但是我們可以通過手動(dòng)釋放 blockObj 的方式來釋放 obj净捅,這就需要我們?cè)?block 內(nèi)部將要退出的時(shí)候手動(dòng)釋放掉 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 變量辩块,這其實(shí)跟 MRC 的形式有些類似了蛔六,不太適合 ARC這種形式既能保證在 block 內(nèi)部能夠訪問到 obj,又可以避免循環(huán)引用的問題废亭,但是這種方法也不是完美的国章,其存在下面幾個(gè)問題
當(dāng)在 block 外部修改了 blockObj 時(shí),block 內(nèi)部的值也會(huì)改變豆村,反之在 block 內(nèi)部修改 blockObj 在外部再使用時(shí)值也會(huì)改變液兽。這就需要在寫代碼時(shí)注意這個(gè)特性可能會(huì)帶來的一些隱患
__block 其實(shí)提升了變量的作用域,在 block 內(nèi)外訪問的都是同一個(gè) blockObj 可能會(huì)造成一些隱患掌动。
總結(jié)
__weak 本身是可以避免循環(huán)引用的問題的四啰,但是其會(huì)導(dǎo)致外部對(duì)象釋放了之后宁玫,block 內(nèi)部也訪問不到這個(gè)對(duì)象的問題,我們可以通過在 block 內(nèi)部聲明一個(gè) __strong 的變量來指向 weakObj柑晒,使外部對(duì)象既能在 block 內(nèi)部保持住欧瘪,又能避免循環(huán)引用的問題。
__block 本身無法避免循環(huán)引用的問題匙赞,但是我們可以通過在 block 內(nèi)部手動(dòng)把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題佛掖。另外一點(diǎn)就是 __block 修飾的變量在 block 內(nèi)外都是唯一的,要注意這個(gè)特性可能帶來的隱患罚屋。
但是__block有一點(diǎn):這只是限制在ARC環(huán)境下苦囱。在非arc下,__block是可以避免引用循環(huán)的脾猛。