開始之前颓鲜,我想先提幾個(gè)問題尊浪,看看大家是否對此有疑惑。唐巧已經(jīng)寫過一篇對block很有研究的文章城看,大家可以去看看(本文會(huì)部分引用巧哥文中出現(xiàn)的圖和代碼)匈棘。在巧哥的基礎(chǔ)上,我補(bǔ)充一些block相關(guān)的知識(shí)點(diǎn)和代碼析命,并且概括并修正一些觀點(diǎn)。
1.block是什么逃默?block是對象嗎鹃愤?
2.block分為哪幾種?__blcok關(guān)鍵字的作用完域?
3.block在ARC和MRC下的區(qū)別软吐?
4.block的生命周期?
5.block對于以參數(shù)形式傳進(jìn)來的對象吟税,會(huì)不會(huì)強(qiáng)引用凹耙?姿现?
block是什么?block是對象嗎肖抱?
先介紹一下什么是閉包备典。在 wikipedia 上,閉包的定義是:
In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.
翻譯過來意述,閉包是一個(gè)函數(shù)(或指向函數(shù)的指針)提佣,再加上該函數(shù)執(zhí)行的外部的上下文變量(有時(shí)候也稱作自由變量)。
block 實(shí)際上就是 Objective-C 語言對于閉包的實(shí)現(xiàn)荤崇。
block是不是對象拌屏?答案顯而易見:是的。
下圖是block的數(shù)據(jù)結(jié)構(gòu)定義术荤,顯而易見倚喂,在Block_layout里,我們看到了isa指針瓣戚,這里我們不具體對isa指針展開端圈,也不對block具體數(shù)據(jù)結(jié)構(gòu)展開,想了解詳細(xì)可以看唐巧的文章带兜。
回到上文枫笛,為什么說block是對象呢,原因就在于isa指針刚照。那么這個(gè)isa指針是何物呢刑巧?
所有對象的都有isa 指針,用于實(shí)現(xiàn)對象相關(guān)的功能无畔。
看到這啊楚,你應(yīng)該明白,block其實(shí)就是objc對于閉包的對象實(shí)現(xiàn)浑彰。
block分為哪幾種恭理?__blcok關(guān)鍵字的作用?
分為三種郭变,即NSConcreteGlobalBlock颜价、NSConcreteStackBlock、NSConcreteMallocBlock诉濒。
詳細(xì)剖析這三種block周伦,首先是NSConcreteGlobalBlock:
簡單地講,如果一個(gè)block中沒有引用外部變量未荒,就是NSConcreteGlobalBlock专挪。
如下圖所示:
需要注意的是,NSConcreteGlobalBlock是全局的block,在編譯期間就已經(jīng)決定了寨腔,如同宏一樣速侈。
什么是NSConcreteStackBlock呢:
可以這么理解,NSConcreteStackBlock就是引用了外部變量的block迫卢,上代碼:
NSConcreteStackBlock不會(huì)持有外部對象
從打印的日志可以看出倚搬,引用計(jì)數(shù)始終沒變。
NSConcreteMallocBlock:
看似最為神秘的NSConcreteMallocBlock其實(shí)就是一個(gè)block被copy時(shí)靖避,將生成NSConcreteMallocBlock(block沒有retain)潭枣。怎么樣,是不是很簡單
NSConcreteMallocBlock
需要注意的是幻捏,NSConcreteMallocBlock會(huì)持有外部對象盆犁!
通過調(diào)用Block_copy()方法或者直接向他發(fā)送OC的copy消息完成。這就是所謂的Block_copy()篡九。
看到了吧谐岁,只要這個(gè)NSConcreteMallocBlock存在,內(nèi)部對象的引用計(jì)數(shù)就會(huì)+1榛臼。
Block_copy()
首先我們來看Block.h伊佃。其中有下面的定義:
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
void *_Block_copy(const void *arg);
所以Block_copy
是一個(gè)宏,它將傳入的參數(shù)轉(zhuǎn)換為一個(gè)const void *
然后傳遞給_Block_copy()
方法沛善。_Block_copy()
的實(shí)現(xiàn)在runtime.c:
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}
所以也就是調(diào)用_Block_copy_internal
方法航揉,傳入block自己和WANTS_ONE
。為了明白這什么意思金刁,我們需要看一下實(shí)現(xiàn)代碼帅涂。也在runtime.c。下面是方法的實(shí)現(xiàn)尤蛮,已經(jīng)刪掉不想干的部分(主要是垃圾收集的部分):
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
// 1
if (!arg) return NULL;
// 2
aBlock = (struct Block_layout *)arg;
// 3
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 4
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// 5
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
// 6
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// 7
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 1;
// 8
result->isa = _NSConcreteMallocBlock;
// 9
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
主要做了以下工作:
如果傳入?yún)?shù)是
NULL
就直接返回NULL
媳友。防止傳入一個(gè)NULL
的Block。將參數(shù)轉(zhuǎn)換為一個(gè)
struct Block_layout
類型的指針产捞。你也許還記得第一篇文章中提到它醇锚。它就是block內(nèi)部一個(gè)包含了實(shí)現(xiàn)函數(shù)和一些元數(shù)據(jù)的數(shù)據(jù)結(jié)構(gòu)。如果block的
flags
字段包含BLOCK_NEEDS_FREE
坯临,那么這是一個(gè)堆block(稍后你就明白)焊唬。這里只需要增加引用計(jì)數(shù)然后返回原blcok。如果這是一個(gè)全局block(回看第一篇文章)看靠,那么不需要做任何事求晶,直接返回原block。因?yàn)槿謆lock是一個(gè)單例衷笋。
如果走到這里,那么這一定是一個(gè)棧上分配的block。那樣的話辟宗,block需要拷貝到堆上爵赵。這才是有趣的部分。第一步泊脐,調(diào)用
malloc()
創(chuàng)建一塊特定的內(nèi)存空幻。如果創(chuàng)建失敗,返回NULL
容客;否則秕铛,繼續(xù)。調(diào)用
memmove()
方法將當(dāng)前棧上分配的block按位拷貝到我們剛剛創(chuàng)建的堆內(nèi)存上缩挑。這樣可以保證所有的元數(shù)據(jù)都拷貝過來但两,比如descriptor。接下來供置,更新標(biāo)志位谨湘。第一行確保引用計(jì)數(shù)為0。注釋表明這行其實(shí)不需要——大概這個(gè)時(shí)候引用計(jì)數(shù)已經(jīng)是0了芥丧。我猜保留這行是因?yàn)橐郧坝袀€(gè)bug導(dǎo)致這里的引用計(jì)數(shù)不是0(所以說runtime的代碼也會(huì)偷懶)紧阔。下一行設(shè)置了
BLOCK_NEEDS_FREE
標(biāo)志位,表明這是一個(gè)堆block续担,一旦引用計(jì)數(shù)減為0擅耽,它所占用的內(nèi)存將被釋放。|1
操作設(shè)置block的引用計(jì)數(shù)為1物遇。block的
isa
指針被設(shè)置為_NSConcreteMallocBlock
乖仇,說明這是個(gè)堆block。最后挎挖,如果block有一個(gè)拷貝輔助函數(shù)这敬,那么它將被調(diào)用。必要的時(shí)候編譯器會(huì)生成拷貝輔助函數(shù)蕉朵。比如一個(gè)捕獲了對象的block就需要崔涂。那么拷貝輔助函數(shù)將持有被捕獲的對象。
哈哈始衅,已經(jīng)十分清晰了±渎欤現(xiàn)在你知道拷貝一個(gè)block到底發(fā)生了什么事!但那只是圖片展示的一半內(nèi)容汛闸,釋放一個(gè)block又會(huì)怎么樣呢蝙茶?
Block_release()
Block_copy()
圖的另一半是Block_release()
。實(shí)際上這又是一個(gè)宏:
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))
跟Block_copy()
一樣诸老,Block_release()
也是轉(zhuǎn)換傳入的參數(shù)然后調(diào)用一個(gè)方法隆夯。這一定程度上解放了程序員的雙手,他們不用自己做轉(zhuǎn)換。
我們來看看_Block_release()
的源碼(簡明起見蹄衷,重新整理了代碼順序忧额,并刪除了垃圾回收相關(guān)的代碼):
// 1
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
// 2
int32_t newCount;
newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
// 3
if (newCount > 0) return;
// 4
if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
_Block_deallocator(aBlock);
}
// 5
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
;
}
// 6
else {
printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
}
}
這段代碼做了這些事:
首先,參數(shù)被轉(zhuǎn)換為一個(gè)指向
struct Block_layout
的指針愧口。如果傳入NULL睦番,直接返回。標(biāo)志位部分表示引用計(jì)數(shù)減1(之前
Block_copy()
中標(biāo)志位操作代表的是引用計(jì)數(shù)置為1)耍属。如果新的引用計(jì)數(shù)值大于0托嚣,說明有其他東西在引用block,所以block不應(yīng)該被釋放厚骗。
否則示启,如果標(biāo)志位包含
BLOCK_NEEDS_FREE
,那么這是一個(gè)堆block而且引用計(jì)數(shù)為0溯捆,應(yīng)該被釋放丑搔。首先block的處理輔助函數(shù)(dispose helper)被調(diào)用,它是拷貝輔助函數(shù)(copy helper)的反義詞提揍,執(zhí)行相反的操作啤月,比如釋放被捕獲的對象。最后調(diào)用_Block_deallocator
方法釋放block劳跃。如果你查找runtime.c你就會(huì)發(fā)現(xiàn)這個(gè)方法最后就是一個(gè)free
的函數(shù)指針谎仲,釋放malloc
分配的內(nèi)存。如果到這一步且lock是全局的刨仑,什么也不做郑诺。
如果到這一步,一定是發(fā)生了未知狀況杉武,因?yàn)橐粋€(gè)棧block試圖在這里釋放辙诞,輸出一行警告。實(shí)際上轻抱,你應(yīng)該永遠(yuǎn)不會(huì)走到這一步飞涂。
下面來說說__block這個(gè)關(guān)鍵字:
先上一個(gè)例子,你們很快就會(huì)明白了
沒錯(cuò)祈搜,前文說過较店,block引用外部是以捕獲的形式來捕捉的,而沒有聲明__block容燕,則會(huì)將外部變量copy進(jìn)block梁呈,若用了__block,則是復(fù)制其引用地址來實(shí)現(xiàn)訪問蘸秘。這就是為什么聲明了__block官卡,在block內(nèi)部改變就會(huì)對外有影響的原因了蝗茁。
注意!味抖!這里需要知道的是评甜,在MRC環(huán)境下,如果沒有用__block仔涩,會(huì)對外部對象采用copy的操作,而用了__block則不會(huì)用copy的操作粘舟。
上代碼:
哈哈哈熔脂,怎么樣,所以從更底層的角度來說柑肴,在MRC環(huán)境下霞揉,__block根本不會(huì)對指針?biāo)赶虻膶ο髨?zhí)行copy操作,而只是把指針進(jìn)行的復(fù)制晰骑。而這一點(diǎn)往往是很多新手&老手所不知道的适秩!
而在ARC環(huán)境下,對于聲明為__block的外部對象硕舆,在block內(nèi)部會(huì)進(jìn)行retain秽荞,以至于在block環(huán)境內(nèi)能安全的引用外部對象,所以要謹(jǐn)防循環(huán)引用的問題抚官!
block在ARC和MRC下的區(qū)別扬跋?
首先要指正下巧哥博客的觀點(diǎn):
在 ARC 開啟的情況下,將只會(huì)有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block凌节。
在上面介紹NSConcreteStackBlock的時(shí)候钦听,是在ARC環(huán)境下跑的,而打印出來的日志明確的顯示出倍奢,當(dāng)時(shí)的block類型為NSConcreteStackBlock朴上。
而實(shí)際上,為什么大家普遍會(huì)認(rèn)為ARC下不存在NSConcreteStackBlock呢卒煞?
這是因?yàn)楸旧砦覀兂3lock賦值給變量痪宰,而ARC下默認(rèn)的賦值操作是strong的,到了block身上自然就成了copy跷坝,所以常常打印出來的block就是NSConcreteMallocBlock了酵镜。
so,在ARC下柴钻,大部分的應(yīng)用場景下淮韭,幾乎可以說是全部都為NSConcreteMallocBlock或者是NSConcreteGlobalBlock。那么問題來了贴届,我們知道NSConcreteMallocBlock是會(huì)持有外部變量的靠粪,而此時(shí)如果它所持有的外部變量正好又持有它蜡吧,就會(huì)產(chǎn)生循環(huán)引用的問題。
讓我們來聊聊block的生命周期占键!
block的生命周期昔善?
談到block生命周期,其實(shí)這是一個(gè)非常嚴(yán)肅的話題畔乙,雖然block簡單易用君仆,老少皆宜,但是一旦使用不慎容易造成“強(qiáng)擼灰飛煙滅”的后果(內(nèi)存泄露)牲距。
ps:接下來的例子都用ARC來展示了
首先展示:
不用看了返咱,這個(gè)object永遠(yuǎn)也不會(huì)被釋放,這是一個(gè)很典型的循環(huán)引用情形牍鞠。object持有了block(讀者可以想象此處為何為NSConcreteMallocBlock咖摹,提示:在ARC環(huán)境下),而block又持有了object难述,于是造成死鎖萤晴,object再也不會(huì)被釋放了。此時(shí)機(jī)智的編譯器給了你warning胁后,但是在很多復(fù)雜的情況下店读,編譯器并不能識(shí)別出循環(huán)引用的場景。而此時(shí)你就需要注意了择同!
那么两入,我是如何來處理block的生命周期相關(guān)問題的呢,首先前文提到敲才,block是一個(gè)對象裹纳,既然是一個(gè)對象,它必然有著和對象一樣的生命周期即如果沒有被引用就會(huì)被釋放紧武。
所以block的生命周期歸結(jié)起來很簡單剃氧,只要看持有block的對象是不是也被block持有,如果沒有持有阻星,就不用擔(dān)心循環(huán)引用問題了朋鞍。
但是像上面的情況,如果產(chǎn)生相互持有的情況該腫么辦妥箕!
你可以用__weak(ARC)或__block(MRC)來解決:
看滥酥,現(xiàn)在就可以愉快的釋放了。
block對于以參數(shù)形式傳進(jìn)來的對象畦幢,會(huì)不會(huì)強(qiáng)引用坎吻?
唉,不知不覺已經(jīng)快半夜2點(diǎn)了宇葱,對于這部分的話瘦真,其實(shí)也是閑著蛋疼在想這個(gè)問題刊头。
其實(shí)block與函數(shù)和方法一樣,對于傳進(jìn)來的參數(shù)诸尽,并不會(huì)持有
證據(jù)如下
總結(jié):
到這里原杂,對于block的介紹結(jié)束了。實(shí)際運(yùn)用中其實(shí)不用太關(guān)心這些原理的您机,只需要正確掌握好block的生命周期就可以靈活地運(yùn)用block了穿肄。但是對于一個(gè)資深開發(fā)者來說,block的深層次掌握還是必須的际看!
一被碗、整體介紹
- 定義:C語言的匿名函數(shù),??提前準(zhǔn)備一段代碼,在需要的時(shí)候調(diào)用。
- 底層:是一個(gè)指針結(jié)構(gòu)體,在終端下可以通過
clang -rewrite-objc 文件名
(會(huì)在當(dāng)前目錄生成.cpp文件)指令看看c++代碼仿村,它的實(shí)現(xiàn)底層。
注意:容易造成循環(huán)引用,經(jīng)常是在 block 里面使用了 self.,然后形成強(qiáng)引用,我們打斷循 環(huán)鏈即可,如果 MRC 下用__block,ARC 下用__weak(下文會(huì)有詳細(xì)介紹)兴喂。
二蔼囊、內(nèi)存位置(ARC情況)
block塊的存儲(chǔ)位置(block塊入口地址):可能存放在2個(gè)地方:代碼區(qū)(NSConcreteGlobalBlock)、堆區(qū)(NSConcreteMallocBlock)衣迷,程序分5個(gè)區(qū)畏鼓,還有常量區(qū)、全局區(qū)和棧區(qū)壶谒,對于MRC情況下代碼還可能存在棧區(qū)(NSConcreteStackBlock)云矫。關(guān)于內(nèi)存分區(qū)詳細(xì)參考:http://www.reibang.com/p/d85a5e56c505
- 情況1:代碼區(qū)
不訪問處于棧區(qū)的變量(例如局部變量),且不訪問處于堆區(qū)的變量(例如alloc創(chuàng)建的對象)汗菜。也就是說訪問全局變量也可以让禀。
/**
沒有訪問任何變量
*/
int main(int argc, char * argv[]) { void (^block)(void) = ^{
NSLog(@"===");
};
block();
}
/**
訪問了全局(靜態(tài))變量
*/
int iVar = 10; int main(int argc, char * argv[]) { void (^block)(void) = ^{
NSLog(@"===%d",iVar);
};
block();
}
- 情況2:堆區(qū)
如果訪問了處于棧區(qū)的變量(例如局部變量),或處于堆區(qū)的變量(例如alloc創(chuàng)建的對象)陨界。都會(huì)存放在堆區(qū)巡揍。(實(shí)際是放在棧區(qū),然后ARC情況下自動(dòng)又拷貝到堆區(qū))
/**
訪問局部變量
*/
int main(int argc, char * argv[]) { int iVar = 10; void (^block)(void) = ^{
NSLog(@"===%d",iVar);
};
block();
}
總結(jié)下:
- 代碼區(qū):不訪問處于棧區(qū)的變量(例如局部變量)菌瘪,且不訪問處于堆區(qū)的變量(例如alloc創(chuàng)建的對象)腮敌。也就是說訪問全局變量(靜態(tài)變量)也可以,或者是什么變量都不訪問
- 堆區(qū):如果訪問了處于棧區(qū)的變量(例如局部變量)俏扩,或處于堆區(qū)的變量(例如alloc創(chuàng)建的對象)糜工,即便也訪問了全局變量
三、注意事項(xiàng)
1 block為空
代碼存放在堆區(qū)時(shí)录淡,就需要特別注意捌木,因?yàn)槎褏^(qū)不像代碼區(qū)不變化,堆區(qū)是不斷變化的(不斷創(chuàng)建銷毀)赁咙。因此代碼有可能會(huì)被銷毀(當(dāng)沒有強(qiáng)指針指向時(shí))钮莲,如果這時(shí)再訪問此段代碼則會(huì)程序崩潰免钻。因此,對于這種情況崔拥,我們在定義一個(gè)block屬性時(shí)應(yīng)指定為strong极舔,或copy:
- @property (nonatomic, strong) void (myBlock)(void); // 這樣就有強(qiáng)指針指向它
- @property (nonatomic, copy) void (myBlock)(void); // 并不會(huì)在堆區(qū)copy一份,原因見 四
而對于block代碼存在代碼區(qū)链瓦,使用strong期贫,copy(不會(huì)復(fù)制一份到堆區(qū))也可以。因此定義block時(shí)最好指定為strong(推薦)或copy连锯。我們在使用時(shí)最后判斷下block是否為空起愈,例如:
- (void)blockTest { // 如果為空則返回
if (!block) {
NSLog(@"block is nil"); return;
}
block();
}
2 當(dāng)不在使用指向block的指針時(shí),將其置空
當(dāng)有類對象的成員變量pBlock指向block時(shí)贴膘,一方面是調(diào)用方卖子,調(diào)用pBlock調(diào)用完成后,應(yīng)將pBlock置為nil;另一方面是被調(diào)用方即block函數(shù)內(nèi)部使用到self時(shí)要__weak
聲明刑峡。其實(shí)__weak
聲明有很多注意事項(xiàng)洋闽,下面是一個(gè)經(jīng)典例子(是正確的寫法):
// 弱聲明,防止block強(qiáng)引用self突梦,造成循環(huán)引用
__weak __typeof(self) weakSelf = self;
self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"blockTest" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { // 多線程情況下(假設(shè)發(fā)出通知的代碼在另一線程下)诫舅,strong強(qiáng)引用防止后面調(diào)用strongSelf時(shí):前面的strongSelf正常,后面的strongSelf已在其它線程被釋放宫患,造成很奇怪的結(jié)果刊懈,雖然這種情況很少發(fā)生
__strong __typeof(self) strongSelf = weakSelf; //if (strongSelf == nil) { // return; //} // 下面再對strongSelf進(jìn)行訪問 // 防止block為空
if (!strongSelf.block) { return;
}
strongSelf.block(); // 如果不用應(yīng)置空,養(yǎng)成好習(xí)慣
strongSelf.block = nil;
NSLog(@"%@",strongSelf);
}];
1)我們都知道在使用通知中心時(shí)娃闲,應(yīng)在dealloc函數(shù)中釋放通知虚汛,如果上面沒有使用__weak聲明,那么:通知中心持有self.observer畜吊,observer又強(qiáng)引用 usingBlock泽疆,usingBlock又強(qiáng)引用self,self就不會(huì)被釋放玲献,那么dealloc就不會(huì)被調(diào)用(即使在dealloc中寫了
[[NSNotificationCenter defaultCenter] removeObserver:self.observer]
也不會(huì)調(diào)用殉疼,因?yàn)閐ealloc沒有被調(diào)用),就造成內(nèi)存泄露捌年;2)另外瓢娜,我們在第5行看到又使用了
__strong
聲明,是否瞬間凌亂礼预?下面給出解釋:在多線程情況下眠砾,有可能在usingBlock調(diào)用時(shí),執(zhí)行if (!strongSelf.block)
時(shí)strongSelf還沒有釋放托酸,而執(zhí)行到strongSelf.block()
的時(shí)候strongSelf就被釋放(現(xiàn)在沒有強(qiáng)引用了褒颈,又開始擔(dān)心self被釋放柒巫,真是操碎了心。谷丸。堡掏。),造成調(diào)用失斉偬邸(最大的問題是不統(tǒng)一泉唁,造成不可預(yù)知的錯(cuò)誤。用__strong
操作后保證要么都訪問成功揩慕,要么都訪問失敗或者判斷為空后直接return退出)亭畜。
而使用了__strong聲明后:
如果執(zhí)行usingBlock時(shí)self已經(jīng)被釋放則后面的strongSelf均為nil,因?yàn)閷eakSelf引用計(jì)數(shù)為0再retain一次也不會(huì)有變化迎卤;
如果執(zhí)行usingBlock時(shí)self沒有釋放拴鸵,則strongSelf會(huì)使self引用計(jì)數(shù)+1,那么self在其它線程被release -1也不會(huì)有影響蜗搔,只有到usingBlock全部執(zhí)行完畢后宝踪,strongSelf釋放,然后self引用計(jì)數(shù)-1碍扔,self才會(huì)釋放(weak–strong dance)。
上面的例子是通知中心可能造成的內(nèi)存泄露秕重,而使用block還經(jīng)常出現(xiàn)循環(huán)引用不同,如下:
3 最常出現(xiàn)的循環(huán)引用
@interface BlockViewController ()
@property (nonatomic, strong) void (^block)(void);
@property (nonatomic, copy) NSString *str; @end
@implementation BlockViewController - (void)viewDidLoad {
[super viewDidLoad];
self.block = ^{
self.str = @"123";
};
}
@end
上面的代碼,self.block強(qiáng)引用block溶耘,而block中又使用了self.str二拐,所以block強(qiáng)引用self,造成強(qiáng)引用凳兵,解決方法使用2中所說即可百新。
關(guān)于引用計(jì)數(shù)(http://www.reibang.com/p/28b074919df3)
四、關(guān)于捕獲變量
block里面捕獲的變量庐扫,都是副本饭望。看下面一段代碼
int val = 10; void (^block)(void) = ^{
NSLog(@"val = %d",val); // val = 1; //不允許
};
val = 5;
block();
它的打印結(jié)果是10形庭,而不是5铅辞。
上面代碼中val = 1
是不允許的,如果想實(shí)現(xiàn)寫操作萨醒,可以使用__block來修飾val斟珊,之后val會(huì)被拷貝(移動(dòng),便于理解)到堆上,之后無論是在block里面還是在val之前所處的作用域富纸,訪問的都是出于堆區(qū)的val囤踩。
為什么非要__block呢旨椒,因?yàn)槿绻挥胈_block,如果出了val所在的“}”,那么val就會(huì)被釋放堵漱,而block的調(diào)用時(shí)機(jī)是不定的综慎,可能調(diào)用時(shí)機(jī)已經(jīng)超出了block和val本身所處的"{}",再訪問val就可能壞地址訪問(val已經(jīng)被釋放)怔锌。所以這樣做是合理的寥粹。
但是在block里面,類似self.name = xxx,self->_val
埃元,卻是很常見的涝涤,self也沒有用__block修飾呀!你是否有過這樣的迷惑岛杀?
self.name = xxx
——>[self setName:xxx];
是發(fā)送消息阔拳,函數(shù)調(diào)用,很好理解类嗤。那self->_val
呢糊肠?因?yàn)開val本身是處于堆區(qū)的。
Block如果沒有引用外部變量
保存在全局區(qū)(MRC/ARC一樣)
Block如果引用外部變量
ARC保存在 堆區(qū)遗锣; MRC保存在 棧區(qū)必須用copy修飾block货裹;
1 簡單理解:block對應(yīng)實(shí)例化一個(gè)結(jié)構(gòu)體,里面成員有block里用到的變量精偿,準(zhǔn)備了數(shù)據(jù)弧圆;調(diào)用block時(shí),執(zhí)行對應(yīng)函數(shù)笔咽,使用這些數(shù)據(jù)搔预;
2 簡單理解:拷貝進(jìn)來時(shí),沒有__block修飾的直接字節(jié)拷貝進(jìn)來叶组,有__block修飾的拯田,為了引地址進(jìn)來,又定義一個(gè)結(jié)構(gòu)體包了一層甩十;
3 block拷貝時(shí)船庇,棧到堆上真拷貝,堆到堆上只是引用計(jì)數(shù)侣监;
4 block拷貝時(shí)溢十,block使用的變量同樣拷貝,拷貝的原則和上面說的有無__block修飾時(shí)的兩種情況分別一致达吞;
5 block里使用外部OC對象時(shí)张弛,本身就相當(dāng)于一個(gè)賦值拷貝,ARC下就會(huì)給對象加引用次數(shù),所以才有了循環(huán)引用的事吞鸭;
6 ARC下寺董,等號賦值,retain刻剥、strong遮咖、copy都會(huì)觸發(fā)block拷貝到堆上;非ARC有些不一樣造虏;
7 ARC下御吞,assign不會(huì)把棧上block拷貝到堆上,retain漓藕、strong陶珠、copy都會(huì);非ARC有些不一樣享钞;
8 __block修飾的變量被拷貝到堆上后揍诽,__forwarding指向的包裝結(jié)構(gòu)體都是堆上那個(gè),這樣無論操作棧上還是堆上的包裝結(jié)構(gòu)體栗竖,實(shí)際改變值都會(huì)是堆上那個(gè)里的暑脆;
9 一些有block參數(shù)的系統(tǒng)API都有說明,當(dāng)把棧上block作為參數(shù)給這些API時(shí)會(huì)不會(huì)拷貝block狐肢,一般都會(huì)添吗,所以才沒問題;
鏈接:
http://www.reibang.com/p/e03292674e60
http://www.reibang.com/p/93f96c6aa530
http://www.cnblogs.com/mddblog/p/4754190.html
https://blog.csdn.net/demondev/article/details/53199785