block本質(zhì)
- block本質(zhì)上是一個(gè)OC對(duì)象(內(nèi)部有個(gè)isa指針)
- block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象
可以通過clang去編譯成c++源碼來驗(yàn)證
block的變量捕獲
- 局部變量
靜態(tài)局部變量驼抹,捕獲指針还棱,即屬于指針傳遞饮亏;
auto的基本數(shù)據(jù)類型局部變量慨绳,捕獲其值(直接拷貝值),屬于值傳遞被环;
auto的對(duì)象類型連同所有權(quán)修飾符(引用修飾符)一起捕獲 - 全局變量
不捕獲糙及,直接訪問
block的類型
block有3種類型,可以通過調(diào)用class方法或者isa指針查看具體類型筛欢,最終都是繼承自NSBlock類型
-
NSGlobalBlock ( _NSConcreteGlobalBlock )存在數(shù)據(jù)區(qū)
沒有捕獲auto變量(局部非static變量)浸锨,對(duì)其copy,什么都不做 -
NSStackBlock ( _NSConcreteStackBlock )存在棧區(qū)
捕獲auto變量版姑,對(duì)其copy柱搜,會(huì)由棧復(fù)制到堆 -
NSMallocBlock ( _NSConcreteMallocBlock )存在堆區(qū)
由NSStackBlock復(fù)制而來的,對(duì)其copy剥险,引用計(jì)數(shù)+1
block的copy
在ARC環(huán)境下聪蘸,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況
- block作為函數(shù)返回值時(shí)
- 將block賦值給__strong指針時(shí)
- block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
- block作為GCD API的方法參數(shù)時(shí)
在MRC環(huán)境下需要自己管理表制,自己實(shí)現(xiàn)copy才不會(huì)因?yàn)閎lock退棧銷毀導(dǎo)致的崩潰
// MRC下block屬性的建議寫法
@property (copy, nonatomic) void (^block)(void);
// ARC下block屬性也可以使用strong關(guān)鍵字
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
__block本質(zhì)
編譯器會(huì)將__block修飾的變量包裝成一個(gè)對(duì)象健爬;
當(dāng)block在棧上時(shí),forwarding是指向自身的指針么介;當(dāng)block被拷貝到堆上時(shí)娜遵,棧上的forwarding指針會(huì)指向堆上的block,堆上的forwarding指針還是指向自身夭拌。這樣不論訪問的是棧上的指針還是堆上的指針最終都能訪問到堆上的真正需要操作的變量魔熏。
使用 __block可以用于解決block內(nèi)部無法修改auto變量值的問題。 __block不能修飾全局變量鸽扁、靜態(tài)變量(static)蒜绽。全局變量能直接訪問修改,而靜態(tài)局部變量值指針訪問桶现,也能修改躲雅。
如何解決循環(huán)引用
- __weak
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog("%p",weakSelf);
}
使用上述方法時(shí),block內(nèi)部執(zhí)行時(shí)間比較長(zhǎng)骡和,在執(zhí)行時(shí)相赁,self突然被釋放了(例如self是控制器相寇,控制器返回了),而block是在堆空間上钮科,并不會(huì)被釋放唤衫,當(dāng)block內(nèi)部繼續(xù)訪問self,這個(gè)時(shí)候會(huì)出現(xiàn)野指針, 也就是說weakSelf變成了nil绵脯,極有可能導(dǎo)致崩潰佳励。
解決方案就是使用__strong
在block內(nèi)部對(duì)weakSelf進(jìn)行強(qiáng)引用
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog("%p",strongSelf);
}
block引用的外部變量的是__weak修飾的weakSelf對(duì)象,
所以block初始化并copy到堆上蛆挫,不會(huì)強(qiáng)引用self赃承。
但是執(zhí)行block的時(shí)候,其實(shí)是執(zhí)行一個(gè)靜態(tài)函數(shù)悴侵,
在執(zhí)行的過程中瞧剖,生成了strongSelf對(duì)象,這個(gè)時(shí)候可免,產(chǎn)生了閉環(huán)抓于。
但是這個(gè)strongSelf在棧空間上浇借,在函數(shù)執(zhí)行結(jié)束后毡咏,strongSelf會(huì)被系統(tǒng)回收,此時(shí)閉環(huán)被打破逮刨。
內(nèi)容來自:http://www.reibang.com/p/24c7e8563c56呕缭,未驗(yàn)證
- __unsafe_unretained
__unsafe_unretained id weakSelf = self;
self.block = ^{
NSLog("%p",weakSelf);
}
缺點(diǎn):weakSelf被釋放之后指針不會(huì)被設(shè)置為nil,訪問將引起崩潰
- __block
__block id weakSelf = self;
self.block = ^{
NSLog("%p",weakSelf);
weakSelf = nil;
}
缺點(diǎn):block必須執(zhí)行之后weakSelf = nil修己,才能打破循環(huán)引用恢总。
以上是ARC前提之下的解決方法,那么在MRC之下仍然可以使用__unsafe_unretained
,但要注意weakSelf被釋放的時(shí)機(jī)睬愤。MRC下__block修飾的變量片仿,并不改變引用計(jì)數(shù),同時(shí)block內(nèi)部并不對(duì)引入的外部對(duì)象尤辱,更改引用計(jì)數(shù)砂豌。所以也可以使用__block來解決。
面試題
什么是block
block是將函數(shù)及其執(zhí)行上下文(調(diào)用環(huán)境)封裝起來的對(duì)象光督。下面代碼的打印結(jié)果是什么阳距?分析一下
int multiplier = 6;
int (^Block)(int) = ^int(int num) {
return num * multiplier;
}
multiplier = 2;
NSLog(@"result is %d", Block(3));
局部基本數(shù)據(jù)類型,block直接誒捕獲其值结借,后續(xù)值的修改對(duì)block已經(jīng)捕獲的值沒影響筐摘。所以是結(jié)果是result is 18
什么場(chǎng)景下需要使用_block修飾符
一般情況下,對(duì)捕獲的 局部變量 進(jìn)行賦值操作需要添加__block修飾符。(當(dāng)且僅當(dāng)對(duì)變量本身進(jìn)行修改時(shí)需要添加咖熟,比如被捕獲的變量是數(shù)組圃酵,對(duì)數(shù)組進(jìn)行增刪改數(shù)組元素則不需要,修改數(shù)組本身這個(gè)對(duì)象才需要添加馍管。)對(duì)于靜態(tài)局部變量郭赐、全局變量則不需要。靜態(tài)局部變量通過指針訪問确沸,全局變量則是直接訪問堪置,都能做到修改其值。下面代碼的打印結(jié)果是什么张惹?分析一下
__block int multiplier = 6;
int (^Block)(int) = ^int(int num) {
return num * multiplier;
}
multiplier = 2;
NSLog(@"result is %d", Block(3));
編譯器會(huì)將__block修飾的變量包裝成一個(gè)對(duì)象;
multiplier = 2; => 編譯之后變成 multiplier.__forwarding.multiplier = 2;
也就是說block執(zhí)行之前能對(duì)multiplier的值修改成功岭洲,結(jié)果是6
- 下面的代碼存在問題么宛逗?為什么?
__block MyBlockViewController* blockSelf = self
_myBlock = ^int(int num) {
return num *blockSelf.multiplier
}
_myBlock(2)盾剩;
ARC下會(huì)產(chǎn)生循環(huán)引用雷激,MRC下則不會(huì)。
上述代碼中告私,block被賦值給『_block』實(shí)例變量屎暇,block被復(fù)制到了堆上,而堆上的block會(huì)對(duì)__block修飾的變量產(chǎn)生強(qiáng)引用驻粟,也就是對(duì)self產(chǎn)生了間接強(qiáng)引用根悼,self本身對(duì)『_block』實(shí)例變量是強(qiáng)引用故而導(dǎo)致了循環(huán)引用。解決方法是蜀撑,在block內(nèi)部使用完blockSelf之后釋放掉挤巡。但是如果block一直不被執(zhí)行的話,強(qiáng)引用就會(huì)一直存在酷麦。
MRC下矿卑,block自行管理,編譯器不會(huì)將block'復(fù)制到堆上沃饶,而棧上的block并不會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用(因block也可能被隨時(shí)釋放)母廷,故而沒有循環(huán)引用問題。