1、Block 的本質(zhì):
Block 本質(zhì)上也是一個(gè) OC 對(duì)象,它內(nèi)部也有個(gè) isa 指針
Block 是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境(參數(shù))的 OC 對(duì)象
Block 的調(diào)用即是函數(shù)的調(diào)用
struct __block_impl { // block 最底層的實(shí)現(xiàn)
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 函數(shù)
}
struct __xxxx_block_impl_0 { // 開發(fā)者創(chuàng)建的某個(gè)block
struct __block_impl impl;
struct __xxxx_block_desc_0 *Desc;
... // 其他變量
// 構(gòu)造函數(shù)
__xxxx_block_impl_0(void *fp, struct __xxxx_block_desc_0 *desc, int flags = 0) {
impl.isa = &_NSConcreateStackBlock;
impl.Flats = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}
struct __xxxx_block_desc_0 { // 開發(fā)者創(chuàng)建的某個(gè)block的表述信息
size_t reserved; // 預(yù)留字段,不考慮
size_t Block_size; // block的大小
} __xxxx_block_desc_0_DATA(0, sizeof(struct __xxxx_block_impl_0)); // 聲明了變量,并計(jì)算出了block的size
Block流程:
- 程序在編譯的時(shí)候會(huì)創(chuàng)建兩個(gè)函數(shù):
- 封裝了block執(zhí)行邏輯的函數(shù)(比如:_block_func_0)
- 封裝了block描述信息的函數(shù)(比如:_block_desc_0_DATA)
- 創(chuàng)建結(jié)構(gòu)體:
- 將函數(shù)
_block_func_0
的地址賦值給內(nèi)部的FuncPtr
變量 - 將變量
_block_desc_0_DATA
的地址復(fù)制給Desc
變量
- 將函數(shù)
- 執(zhí)行Block,通過結(jié)構(gòu)體內(nèi)部的
impl
找到函數(shù)FuncPtr
,取出函數(shù)地址并執(zhí)行(impl是struct的第一個(gè)變量,所以可以直接強(qiáng)轉(zhuǎn))
2、Block 變量捕獲:
局部變量-捕獲(因?yàn)榭绾瘮?shù)執(zhí)行)
全局變量-不捕獲
-
局部變量,離開作用域就銷毀
- auto 自動(dòng)變量,可以不寫,基本類型普通定義的變量都是 auto 的)
- static 靜態(tài)變量
- register 高速編譯器,盡量用寄存器存儲(chǔ)數(shù)據(jù),很少用
如果在類里邊,self也會(huì)被捕獲到block中,因?yàn)閟elf是局部變量
3、Block 的類型:
Block有三種類型:
- 全局Block(NSGlobalBlock):如果這個(gè)block沒有訪問auto變量,那么這個(gè)block就位于全局區(qū)
- 棧Block(NSStackBlock):
- MRC下:如果這個(gè)block訪問了auto變量,那么這個(gè)block就是棧Block,對(duì)于棧Block而言,變量的作用域結(jié)束,空間被回收
- ARC下:如果這個(gè)block訪問了auto變量且使用了weak/assign修飾,那么這個(gè)block就是棧Block
- 堆Block(NSMallocBlock):
- MRC下:如果這個(gè)block訪問了auto變量且手動(dòng)進(jìn)行了copy操作,那么這個(gè)block就是堆Block
- ARC下:如果這個(gè)block訪問了auto變量且使用了copy/strong修飾,那么這個(gè)block就是堆Block
總結(jié):
GlobalBlock: 沒有訪問auto變量(GlobalBlock->copy->GlobalBlock)
StackBlock: 訪問了auto變量 (如果StackBlock調(diào)用了copy/strong就會(huì)變成MallocBlock)
MallocBlock: 訪問了auto變量 且 strong/copy 修飾
4、Block 的 copy:
在 ARC 下某些其情況下,系統(tǒng)會(huì)自動(dòng)對(duì) block 進(jìn)行 copy,
但是只有訪問了auto對(duì)象才是MallocBloc
- block 作為函數(shù)的返回值時(shí)
- 將 block 賦值給強(qiáng)指針 __strong 時(shí)
- block 作為 Cocoa API 中方法名含有 usingBlock 的方法參數(shù)時(shí)
- block 作為 GCD API 中的方法參數(shù)時(shí)
5、Block 訪問對(duì)象類型的 auto 變量:
- 如果這個(gè)Block是棧Block: 不會(huì)對(duì) auto變量 產(chǎn)生強(qiáng)引用
- 如果這個(gè)Block是堆Block: 會(huì)對(duì) auto變量 產(chǎn)生強(qiáng)引用
原理:
當(dāng) Block 被拷貝到堆上時(shí),會(huì)調(diào)用 block 內(nèi)部的 copy 函數(shù),內(nèi)部函數(shù)會(huì)根據(jù)變量的操作符(__strong、__weak、__unsfe_unretained)做出相應(yīng)的操作,類似于 retain(形成強(qiáng)引用、若引用)
當(dāng) Block 從堆上移除時(shí),會(huì)調(diào)用 block 內(nèi)部的 Dispose 函數(shù),內(nèi)部函數(shù)會(huì)自動(dòng)釋放引用的 auto 變量,類似于 release
6、Block 訪問 __block 修飾的基礎(chǔ)類型:
修改變量方式:static修飾變量、 全局變量、__block修飾變量
- __block 可以用于解決 block 內(nèi)部無法修改 auto 變量值的問題
- __block 不能修飾全局變量、靜態(tài)變量(static)
- 編譯器會(huì)將 __block 變量包裝成一個(gè)對(duì)象
底層實(shí)現(xiàn):
如果變量使用了 __block 修飾,那么在編譯的時(shí)候,編譯器會(huì)將這個(gè)變量封裝成在一個(gè)結(jié)構(gòu)體中(結(jié)構(gòu)體中有isa指針,所以可以理解成對(duì)象),
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; // 指向__Block_byref_b_0自己本身
int __flags;
int __size;
int age; // 外部的變量
};
而在 Block 的實(shí)現(xiàn)中會(huì)生成這個(gè)結(jié)構(gòu)體的指針,并進(jìn)行內(nèi)存管理,類似于auto類型對(duì)象,
訪問:age -> __forwarding -> age = 20;
其實(shí)根據(jù)讀取age的內(nèi)存地址可以發(fā)現(xiàn),不管是在 Block內(nèi)部,還是在 Block外部,訪問的age都是生成的結(jié)構(gòu)體中的age變量
為什么要使用 __forwarding 指針?
__forwarding
指針指向生成的結(jié)構(gòu)體本身,通過這個(gè)指針來訪問內(nèi)部的變量,能保證在當(dāng)Block被拷貝到堆上后,任何情況下都是訪問的堆上的變量
- 當(dāng) Block 在棧上,使用棧Block 的 __forwarding 指針訪問的結(jié)構(gòu)體內(nèi)部的變量
-
當(dāng) Block 被拷貝到在堆上時(shí),棧上的Block中的 __forwarding 指針指向堆Block,堆上的Block中的 __forwarding 指針指向自己本身,這時(shí)可以保證不管是通過棧Block還是堆Block訪問的內(nèi)部變量,都是堆Block的內(nèi)部變量
7、Block 內(nèi)存管理:
Block會(huì)對(duì) 對(duì)象類型變量和__block 修飾的變量進(jìn)行內(nèi)存管理
__block 修飾的都會(huì)在編譯時(shí)生成一個(gè)結(jié)構(gòu)體進(jìn)行包裝
- 當(dāng) Block 在棧上的時(shí)候,并不會(huì)對(duì)它們產(chǎn)生強(qiáng)引用 (block 本身隨時(shí)會(huì)釋放)
- 當(dāng) Block 被 copy 到堆上,都會(huì)通過 Copy 函數(shù),Copy 內(nèi)部會(huì)調(diào)用
_Block_object_assign
函數(shù)來處理它們- a) __block 基礎(chǔ)變量,Block直接對(duì)生成的結(jié)構(gòu)體進(jìn)行
強(qiáng)引用
- b) 對(duì)象類型,Block 根據(jù)對(duì)象的修飾符
(__strong、__weak、__unsfe_unretained)
做出相應(yīng)的操作,形成強(qiáng)引用(retain)或弱引用(僅限于ARC時(shí)會(huì)retain,MRC下不會(huì)) - c) __block 對(duì)象類型,Block直接對(duì)生成的結(jié)構(gòu)體進(jìn)行
強(qiáng)引用
,結(jié)構(gòu)體內(nèi)部根據(jù)對(duì)象的修飾符(__strong、__weak、__unsfe_unretained)
做出相應(yīng)的操作,形成強(qiáng)引用(retain)或弱引用(僅限于ARC時(shí)會(huì)retain,MRC下不會(huì))
- a) __block 基礎(chǔ)變量,Block直接對(duì)生成的結(jié)構(gòu)體進(jìn)行
- 當(dāng) Block 從堆上移除,就會(huì)調(diào)用 block 內(nèi)部 Dispose 函數(shù),Dispose 內(nèi)部會(huì)調(diào)用
_Block_object_dispose
函數(shù)來釋放它們
8、Block 解決循環(huán)引用:
1. ARC 下使用 __weak, __unsafe_unretained, __block
- __weak 不會(huì)產(chǎn)生強(qiáng)引用,如果內(nèi)存釋放,指針會(huì)自動(dòng)置為 nil(推薦使用)
- __unsafe_unretained 不會(huì)產(chǎn)生強(qiáng)引用,不安全,如果內(nèi)存釋放,指針會(huì)變成野指針
另: - 使用 __block 也解決循環(huán)引用,但是必須調(diào)用 block,并且在 block 中必須將變量置 nil
2. MRC 下使用 __unsafe_unretained, __block (MRC 下不支持 __weak)
- __unsafe_unretained 不會(huì)產(chǎn)生強(qiáng)引用,不安全,如果內(nèi)存釋放,指針會(huì)變成野指針
- __block 它在 MRC 下不會(huì)強(qiáng)引用(推薦使用)
面試題:
1、 block 的原理是怎么樣的?本質(zhì)是什么?
封裝了函數(shù)調(diào)用以及調(diào)用環(huán)境的 OC 對(duì)象
Block 的調(diào)用即是函數(shù)的調(diào)用
2、 __block 的作用是什么?有什么使用注意點(diǎn)?
__block 可以解決 block 內(nèi)部無法修改 auto 變量值的問題
注意點(diǎn):__block 內(nèi)存管理
3、 block 的屬性修飾詞為什么是 copy?使用 block 有哪些使用注意點(diǎn)?
block 一旦沒有 copy,就會(huì)不在堆上,在堆上方便進(jìn)行內(nèi)存管理
使用注意:循環(huán)引用
4、 block 在修改 NSMutableArray,需不需要添加__block?
不需要
5、 適應(yīng)strong和copy修飾block有什么區(qū)別?
沒區(qū)別,不管是strong還是copy,在ARC下都會(huì)將block拷貝到堆上