第一種場(chǎng)景:用NSString直接賦值
// 第一種場(chǎng)景:用NSString直接賦值
NSString *originStr1 = [NSString stringWithFormat:@"hello,everyone"];
_strongStr = originStr1;_
copyyStr = originStr1;?
?NSLog(@"第一種場(chǎng)景:用NSString直接賦值");
NSLog(@" 對(duì)象地址 對(duì)象指針地址 對(duì)象的值 ");
NSLog(@"originStr: %p , %p , %@", originStr1, &originStr1, originStr1);
NSLog(@"strongStr: %p , %p , %@", _strongStr, &_strongStr, _strongStr);
NSLog(@" copyyStr: %p , %p , %@", _copyyStr, &_copyyStr, _copyyStr);
結(jié)論:這種情況下司抱,不管是用strong還是copy修飾的對(duì)象,其指向的地址都是originStr的地址砚哗。
當(dāng)原字符串是NSString時(shí)责静,由于是不可變字符串华坦,所以间驮,不管使用strong還是copy修飾驾凶,都是指向原來的對(duì)象,copy操作只是做了一次淺拷貝滩褥。
而當(dāng)源字符串是NSMutableString時(shí)病蛉,strong只是將源字符串的引用計(jì)數(shù)加1,而copy則是對(duì)原字符串做了次深拷貝瑰煎,從而生成了一個(gè)新的對(duì)象铺然,并且copy的對(duì)象指向這個(gè)新對(duì)象。另外需要注意的是丢间,這個(gè)copy屬性對(duì)象的類型始終是NSString探熔,而不是NSMutableString驹针,如果想讓拷貝過來的對(duì)象是可變的烘挫,就要使用mutableCopy。
所以柬甥,如果源字符串是NSMutableString的時(shí)候饮六,使用strong只會(huì)增加引用計(jì)數(shù)。
但是copy會(huì)執(zhí)行一次深拷貝苛蒲,會(huì)造成不必要的內(nèi)存浪費(fèi)卤橄。而如果原字符串是NSString時(shí),strong和copy效果一樣臂外,就不會(huì)有這個(gè)問題窟扑。
但是喇颁,我們一般聲明NSString時(shí),也不希望它改變嚎货,所以一般情況下橘霎,建議使用copy,這樣可以避免NSMutableString帶來的錯(cuò)誤殖属。
順便路過提一下assign與weak
我們都知道姐叁,assign用來修飾基本數(shù)據(jù)類型,weak用來修飾OC對(duì)象洗显。
其實(shí)照理外潜,assign也能修飾OC對(duì)象,但是assign修飾的對(duì)象在該對(duì)象釋放后挠唆,其指針依然存在处窥,不會(huì)被置為nil——這就造成了一個(gè)很嚴(yán)重的問題:出現(xiàn)了野指針。當(dāng)訪問這個(gè)野指針時(shí)损搬,指向了原地址碧库,而原地址是nil,所以會(huì)造成程序的crash巧勤。但是用weak來修飾的話嵌灰,對(duì)象釋放的時(shí)候會(huì)把指針置為nil,從而避免了野指針的出現(xiàn)颅悉。
那又有個(gè)疑問出現(xiàn)了沽瞭,憑什么基本數(shù)據(jù)類型就可以使用assign。這就要扯到堆和棧的問題了剩瓶,基本數(shù)據(jù)類型會(huì)被分配到椌岳#空間,而椦邮铮空間是由系統(tǒng)自動(dòng)管理分配和釋放的豌鹤,就不會(huì)造成野指針的問題。
一:概念
淺拷貝:指針拷貝枝缔,不會(huì)創(chuàng)建一個(gè)新的對(duì)象布疙。淺拷貝簡(jiǎn)單點(diǎn)說就是對(duì)內(nèi)存地址的復(fù)制,讓目標(biāo)對(duì)象指針和源對(duì)象指針指向同一片內(nèi)存空間
深拷貝: 內(nèi)容拷貝愿卸,會(huì)創(chuàng)建一個(gè)新的對(duì)象灵临。深拷貝就是拷貝地址中的內(nèi)容,讓目標(biāo)對(duì)象產(chǎn)生新的內(nèi)存區(qū)域趴荸,且將源內(nèi)存區(qū)域中的內(nèi)容復(fù)制到目標(biāo)內(nèi)存區(qū)域中
深拷貝和淺拷貝的本質(zhì)是內(nèi)存地址是否相同
面試時(shí)經(jīng)常問到給NSString字符串設(shè)置為strong屬性有什么風(fēng)險(xiǎn)儒溉,從上面就測(cè)試可以看出:
stong修飾的字符串無論被賦值NSString還是NSMutableString都是淺拷貝,被賦值NSString類型時(shí)沒有問題和copy效果一樣发钝,但是當(dāng)被賦值為NSMutableString類型時(shí)顿涣,它會(huì)因?yàn)楸毁x的值的改變而改變波闹,這在很多情況下是存在風(fēng)險(xiǎn)的,因?yàn)榇蟛糠智闆r下我們是不希望自己的屬性值在初始化后又莫名的改變的涛碑,當(dāng)然如果原本需求就是希望無論什么時(shí)候都隨被賦值改變而改變那就必須就用stong修飾了
第二種情況(傳遞可變數(shù)據(jù))
這里將局部的字符串改成了可變的NSMutableString舔痪,賦值用了self.語法,只有這樣才會(huì)走自動(dòng)生成的setter方法锌唾,strong锄码,copy關(guān)鍵字才會(huì)生效
第三種情況(使用copy關(guān)鍵字的字符串為NSMutableString)
使用copy關(guān)鍵字的為可變字符串,但是結(jié)果是什么呢晌涕?
打印結(jié)果:
當(dāng)執(zhí)行到上圖中的那一句時(shí)崩潰了滋捶,打印信息如下圖
因?yàn)閏opy出來的仍然是不可變字符!如果使用NSMutableString的方法余黎,就會(huì)崩潰重窟。文章開始的時(shí)候那個(gè)問題的答案就在這里了。
assign:
assign一般用來修飾基本的數(shù)據(jù)類型惧财,包括基礎(chǔ)數(shù)據(jù)類型 (NSInteger巡扇,CGFloat)和C數(shù)據(jù)類型(int, float, double, char, 等等),為什么呢垮衷?assign聲明的屬性是不會(huì)增加引用計(jì)數(shù)的厅翔,也就是說聲明的屬性釋放后,就沒有了搀突,即使其他對(duì)象用到了它刀闷,也無法留住它,只會(huì)crash仰迁。但是甸昏,即使被釋放,指針卻還在徐许,成為了野指針施蜜,如果新的對(duì)象被分配到了這個(gè)內(nèi)存地址上,又會(huì)crash雌隅,所以一般只用來聲明基本的數(shù)據(jù)類型翻默,因?yàn)樗鼈儠?huì)被分配到棧上,而棧會(huì)由系統(tǒng)自動(dòng)處理澄步,不會(huì)造成野指針冰蘑。
retain:
與assign相對(duì)和泌,我們要解決對(duì)象被其他對(duì)象引用后釋放造成的問題村缸,就要用retain來聲明。retain聲明后的對(duì)象會(huì)更改引用計(jì)數(shù)武氓,那么每次被引用梯皿,引用計(jì)數(shù)都會(huì)+1仇箱,釋放后就會(huì)-1,即使這個(gè)對(duì)象本身釋放了东羹,只要還有對(duì)象在引用它剂桥,就會(huì)持有,不會(huì)造成什么問題属提,只有當(dāng)引用計(jì)數(shù)為0時(shí)权逗,就被dealloc析構(gòu)函數(shù)回收內(nèi)存了
weak:
weak其實(shí)類似于assign,叫弱引用冤议,也是不增加引用計(jì)數(shù)斟薇。一般只有在防止循環(huán)引用時(shí)使用,比如父類引用了子類恕酸,子類又去引用父類堪滨。IBOutlet、Delegate一般用的就是weak蕊温,這是因?yàn)樗鼈儠?huì)在類外部被調(diào)用袱箱,防止循環(huán)引用。
strong:
相對(duì)的义矛,strong就類似與retain了发笔,叫強(qiáng)引用,會(huì)增加引用計(jì)數(shù)凉翻,類內(nèi)部使用的屬性一般都是strong修飾的筐咧,現(xiàn)在ARC已經(jīng)基本替代了MRC,所以我們最常見的就是strong了噪矛。
nonatomic:
在修飾屬性時(shí)量蕊,我們往往還會(huì)加一個(gè)nonatomic,這又是什么呢艇挨?它的名字叫非原子訪問残炮。對(duì)應(yīng)的有atomic,是原子性的訪問缩滨。我們知道势就,在使用多線程時(shí)為了避免在寫操作時(shí)同時(shí)進(jìn)行寫導(dǎo)致問題,經(jīng)常會(huì)對(duì)要寫的對(duì)象進(jìn)行加鎖脉漏,也就是同一時(shí)刻只允許一個(gè)線程去操作它苞冯。如果一個(gè)屬性是由atomic修飾的,那么系統(tǒng)就會(huì)進(jìn)行線程保護(hù)侧巨,防止多個(gè)寫操作同時(shí)進(jìn)行舅锄。這有好處,但也有壞處司忱,那就是消耗系統(tǒng)資源皇忿,所以對(duì)于iPhone這種小型設(shè)備畴蹭,如果不是進(jìn)行多線程的寫操作,就可以使用nonatomic鳍烁,取消線程保護(hù)叨襟,提高性能。
block本身是像對(duì)象一樣可以retain幔荒,和release糊闽。但是,block在創(chuàng)建的時(shí)候爹梁,它的內(nèi)存是分配在棧上的墓怀,而不是在堆上。他本身的作于域是屬于創(chuàng)建時(shí)候的作用域卫键,一旦在創(chuàng)建時(shí)候的作用域外面調(diào)用block將導(dǎo)致程序崩潰傀履。因?yàn)闂^(qū)的特點(diǎn)就是創(chuàng)建的對(duì)象隨時(shí)可能被銷毀,一旦被銷毀后續(xù)再次調(diào)用空對(duì)象就可能會(huì)造成程序崩潰,在對(duì)block進(jìn)行copy后,block存放在堆區(qū).
使用retain也可以,但是block的retain行為默認(rèn)是用copy的行為實(shí)現(xiàn)的莉炉,
因?yàn)閎lock變量默認(rèn)是聲明為棧變量的钓账,為了能夠在block的聲明域外使用,所以要把block拷貝(copy)到堆絮宁,所以說為了block屬性聲明和實(shí)際的操作一致梆暮,最好聲明為copy。
iOS 內(nèi)存分布绍昂,一般分為:棧區(qū)啦粹、堆區(qū)、全局區(qū)窘游、常量區(qū)唠椭、代碼區(qū)。其實(shí) Block 也是一個(gè) Objective-C 對(duì)象忍饰,常見的有以下三種 Block:
NSMallocBlock?:存放在堆區(qū)的 Block
NSStackBlock?: 存放在棧區(qū)的 Block
NSGlobalBlock?: 存放在全局區(qū)的 Block
Block 內(nèi)部沒有引用外部變量贪嫂,Block 在全局區(qū),屬于 GlobalBlock
注意:Block 引用普通外部變量艾蓝,都是在棧區(qū)創(chuàng)建的力崇,只是用 strong、copy 修飾的 Block 會(huì)把它從棧區(qū)拷貝到堆區(qū)一份赢织,而 weak 修飾的 Block 不會(huì)亮靴;
struct __block_impl {
? ? void *isa;
? ? int Flags;
? ? int Reserved;
? ? void *FuncPtr;
};
static struct __block_desc_0 {
? ? size_t reserved;
? ? size_t Block_size;
} _block_desc_0_DATA = { 0, sizeof(struct __block_desc_0)};
struct _block_impl_0 {
? ? struct __block_impl impl;
? ? struct __block_desc_0* Desc;
? ? int i; // 這個(gè)是引用外部變量 i
? ? _block_impl_0(void *fp, struct __block_desc_0 *desc, int _i, int flags=0) :i(_i){
? ? ? ? impl.Flags = flags;
? ? ? ? impl.FuncPtr = fp;
? ? ? ? Desc = desc;
? ? }
};
過分析上面源碼,我們可以得到下面幾點(diǎn)結(jié)論:
結(jié)構(gòu)體中有 isa 指針于置,證明 Block 也是一個(gè)對(duì)象
Block 底層是用結(jié)構(gòu)體來實(shí)現(xiàn)的茧吊,結(jié)構(gòu)體?_block_impl_0?包含了?__block_impl?結(jié)構(gòu)體和?__block_desc_0?結(jié)構(gòu)體。
__block_impl?結(jié)構(gòu)體中的?FuncPtr?函數(shù)指針,指向的就是我們的 Block 的具體實(shí)現(xiàn)饱狂。真正調(diào)用 Block 就是利用這個(gè)函數(shù)指針去調(diào)用的。
為什么能訪問外部變量宪彩,就是因?yàn)閷⑼獠孔兞繌?fù)制到了結(jié)構(gòu)體中(上面的 int i)休讳,即自動(dòng)變量會(huì)作為成員變量追加到 Block 結(jié)構(gòu)體中。
分析具有 __block 修飾符外部變量的 Block 源代碼
我們知道 Block 截獲外部變量是將外部變量作為成員變量追加到 Block 結(jié)構(gòu)體中尿孔,但是匿名函數(shù)存在作用域的問題俊柔,這個(gè)就是為什么我們不能在 Block 內(nèi)部去修改普通外部變量的原因。所有就出現(xiàn)了 __block 修飾符來解決這個(gè)問題活合。
//Objective-C 代碼
- (void)blockDataBlockFunction {
__block int a = 100;? ///在棧區(qū)
void (^blockDataBlock)(void) = ^{
a = 1000;
NSLog(@"%d", a);
};? ///在堆區(qū)
blockDataBlock();
}
//C++ 代碼
struct __Block_byref_a_0 {
? void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __BlockStructureViewController__blockDataBlockFunction_block_impl_0 {
? struct __block_impl impl;
? struct __BlockStructureViewController__blockDataBlockFunction_block_desc_0* Desc;
? __Block_byref_a_0 *a; // by ref
};
具有 __block 修飾的變量雏婶,會(huì)生成一個(gè) Block_byref_a_0 結(jié)構(gòu)體來表示外部變量,然后再追加到 Block 結(jié)構(gòu)體中白指,這里生成 Block_byref_a_0 這個(gè)結(jié)構(gòu)體大概有兩個(gè)原因:一個(gè)是抽象出一個(gè)結(jié)構(gòu)體留晚,可以讓多個(gè) Block 同時(shí)引用這個(gè)外部變量;另外一個(gè)好管理告嘲,因?yàn)?Block_byref_a_0 中有個(gè)非常重要的成員變量?forwarding? 指針错维,這個(gè)指針非常重要(這個(gè)指針指向 Block_byref_a_0 結(jié)構(gòu)體),這里是保證當(dāng)我們將 Block 從楅匣#拷貝到堆中赋焕,修改的變量都是同一份。
__block 變量的存儲(chǔ)域
Block 從棧復(fù)制到堆上仰楚,_block 修飾的變量也會(huì)從棧復(fù)制到堆上隆判;為了結(jié)構(gòu)體 \_block 變量無論在棧上還是在堆上,都可以正確的訪問變量僧界,我們需要 forwarding 指針侨嘀;
在 Block 從棧復(fù)制到堆上的時(shí)候,原本棧上結(jié)構(gòu)體的 forwarding 指針捂襟,會(huì)改變指向飒炎,直接指向堆上的結(jié)構(gòu)體。這樣子就可以保證之后我們都是訪問同一個(gè)結(jié)構(gòu)體中的變量笆豁,這里就是為什么 __block 修飾的變量郎汪,在 Block 內(nèi)部中可以修改的原因了。
Block 對(duì)于外部變量都會(huì)追加到結(jié)構(gòu)體中
Block 為什么用 Copy 修飾
對(duì)于這個(gè)問題闯狱,得區(qū)分 MRC 環(huán)境 和 ARC 環(huán)境煞赢;首先,通過上面小節(jié)可知哄孤,Block 引用了普通外部變量照筑,都是創(chuàng)建在棧區(qū)的;對(duì)于分配在棧區(qū)的對(duì)象,我們很容易會(huì)在釋放之后繼續(xù)調(diào)用凝危,導(dǎo)致程序奔潰波俄,所以我們使用的時(shí)候需要將棧區(qū)的對(duì)象移到堆區(qū),來延長(zhǎng)該對(duì)象的生命周期蛾默。
對(duì)于 MRC 環(huán)境懦铺,使用 Copy 修飾 Block,會(huì)將棧區(qū)的 Block 拷貝到堆區(qū)支鸡。
對(duì)于 ARC 環(huán)境冬念,使用 Strong、Copy 修飾 Block牧挣,都會(huì)將棧區(qū)的 Block 拷貝到堆區(qū)急前。
所以,Block 不是一定要用 Copy 來修飾的瀑构,在 ARC 環(huán)境下面 Strong 和 Copy 修飾效果是一樣的裆针。
總結(jié):
block內(nèi)部沒有調(diào)用外部局部變量時(shí)存放在全局區(qū)(ARC和MRC下均是)
block使用了外部局部變量,這種情況也正是我們平時(shí)所常用的方式。MRC:Block的內(nèi)存地址顯示在棧區(qū),棧區(qū)的特點(diǎn)就是創(chuàng)建的對(duì)象隨時(shí)可能被銷毀,一旦被銷毀后續(xù)再次調(diào)用空對(duì)象就可能會(huì)造成程序崩潰,在對(duì)block進(jìn)行copy后,block存放在堆區(qū).所以在使用Block屬性時(shí)使用copy修飾寺晌。但是ARC中的Block都會(huì)在堆上的据块,系統(tǒng)會(huì)默認(rèn)對(duì)Block進(jìn)行copy操作
用copy,strong修飾block在ARC和MRC都是可以的折剃,都是在堆區(qū)
補(bǔ)充:一個(gè)block要使用self另假,會(huì)處理成在外部聲明一個(gè)weak變量指向self,然而為何有時(shí)會(huì)出現(xiàn)在block里又聲明一個(gè)strong變量指向weakSelf怕犁?
原因:block會(huì)把寫在block里的變量copy一份边篮,如果直接在block里使用self,(self對(duì)變量默認(rèn)是強(qiáng)引用)self對(duì)block持有奏甫,block對(duì)self持有戈轿,導(dǎo)致循環(huán)引用,所以這里需要聲明一個(gè)弱引用weakSelf阵子,讓block引用weakSelf思杯,打破循環(huán)引用。
而這樣會(huì)導(dǎo)致另外一個(gè)問題挠进,因?yàn)閣eakSelf是對(duì)self的弱引用色乾,如果這個(gè)時(shí)候控制器pop或者其他的方式引用計(jì)數(shù)為0,就會(huì)釋放领突,如果這個(gè)block是異步調(diào)用而且調(diào)用的時(shí)候self已經(jīng)釋放了暖璧,這個(gè)時(shí)候weakSelf已就變成了nil。
當(dāng)控制器(也可以是其他的控件)pop回來之后(或者一些其他的原因?qū)е箩尫牛┚W(wǎng)絡(luò)請(qǐng)求完成澎办,如果這個(gè)時(shí)候需要控制器做出反映嘲碱,需要strongSelf再對(duì)weakSelf強(qiáng)引用一下。
但是局蚀,你可能會(huì)疑問麦锯,strongSelf對(duì)weakSelf強(qiáng)引用,weakSelf對(duì)self弱引用琅绅,最終不也是對(duì)self進(jìn)行了強(qiáng)引用扶欣,會(huì)導(dǎo)致循環(huán)引用嗎。不會(huì)的奉件,因?yàn)閟trongSelf是在block里面聲明的一個(gè)指針宵蛀,當(dāng)block執(zhí)行完畢后昆著,strongSelf會(huì)釋放县貌,這個(gè)時(shí)候?qū)⒉辉購?qiáng)引用weakSelf,所以self會(huì)正確的釋放凑懂。