Block 是什么集峦?
Block 是一個(gè)匿名的函數(shù),但是它能夠捕獲變量顺又,這是它跟匿名函數(shù)的區(qū)別更卒。
Block 是如何捕獲變量的,這個(gè)得要看一下 Block 的源代碼稚照,去理解Block 實(shí)現(xiàn)的原理蹂空。
Block的實(shí)現(xiàn)
struct __block_impl {
// 根據(jù) Block 的不同位置自動生成的 Block 的類型
void *isa;
int Flags;
int Reserved;
// FuncPtr 指向 Block 的最終實(shí)現(xiàn)俯萌,即 __main_block_func_0
void *FuncPtr;
// 捕獲的變量
int XXX
}
//Block的內(nèi)存管理
static struct __main_block_desc_0 {
unsigned long reserved;
unsigned long Block_size;
}
//__main_block_func_0 是 Block 的代碼
// __cself 為指向 Block 值的指針變量( _cself 類似于 self 的用法)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 被截獲的局部變量
//XXX= __ceself ->XXX
}
// Block 的對象,持有了 impl 與 Desc 結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
// 被截獲的局部變量
// Block 的構(gòu)造器上枕,...()為被截獲的局部變量咐熙,如val(_val)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, _..., int flags = 0): ...() {
impl.isa = blockType(非參數(shù),根據(jù) Block 的不同位置自動生成的默認(rèn)值);
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
}
struct __main_block_impl_0 {
// 根據(jù) Block 的不同位置自動生成的 Block 的類型
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
unsigned long reserved;
unsigned long Block_size;
// 被截獲的局部變量
// ...
}
Block 截獲變量的做法是把 Block 內(nèi)部需要用到的變量都追加到 Block 的構(gòu)造器上,通過__main_block_func_0
內(nèi)部 XXX= _ceself ->XXX
去訪問捕獲到的變量
問題一:為什么 Block 修改不了自動變量的值辨萍?
問題二:為什么 _block 變量可以被 Block 修改棋恼?
問題一相對容易理解一些
struct __block_impl {
// 根據(jù) Block 的不同位置自動生成的 Block 的類型
void *isa;
int Flags;
int Reserved;
// FuncPtr 指向 Block 的最終實(shí)現(xiàn),即 __main_block_func_0
void *FuncPtr;
// 捕獲的變量
int XXX
}
Block 捕獲變量 XXX 只是把 XXX 的值復(fù)制在 Block 锈玉,然后把復(fù)制的值追加到 Block 構(gòu)造器上爪飘。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
// 被截獲的局部變量
XXX= __ceself ->XXX
XXX = a;
}
如果在 Block 對 XXX 就行修改拉背,其實(shí)只是修改復(fù)制在 __main_block_func_0
的復(fù)制值师崎,并沒有影響到原來的 XXX。
如果想修改 XXX 的值椅棺,應(yīng)該拿到 XXX 的內(nèi)存地址犁罩,才能對XXX做修改。
Block 修改局部靜態(tài)變量就是這樣做的两疚。
問題三:為什么在 Block 修改自動變量不像修改局部靜態(tài)變量那樣做呢床估?
_block 的實(shí)現(xiàn)
_block變量不是直接追加到 Block ,而是會變成一個(gè)結(jié)構(gòu)體,然后加入在 Block 中。
//自動變量轉(zhuǎn)化成一個(gè)結(jié)構(gòu)體
struct __Block_byref_val_0 {
void *__isa;
// __forwarding 用于實(shí)現(xiàn)無論 __block 配置在棧上還是堆上都能夠正常訪問 __block 變量
__Block_byte_val_0 *__forwarding;
int __flags;
int __size;
int val;
}
// Block 持有自動變量結(jié)構(gòu)體指針
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
__Block_byref_val_0 *val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
__Block_byref_val_0 *_val,
int flags = 0): val(val -> __forwarding) {
impl.isa = blockType(非參數(shù)诱渤,根據(jù) Block 的不同位置自動生成的默認(rèn)值);
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
//Block 的代碼實(shí)現(xiàn)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//把變量的值修改成 a
__Block_byref_val_0 *val = __cself -> val;
(val -> __forwarding -> val) = a;
}
問題二的解答
Block 的__main_block_impl_0
持有__Block_byref_val_0
的指針
__Block_byref_val_0
的成員變量__forwarding
持有指向該實(shí)例自身的指針
通過__forwarding
去訪問成員變量val
(val
可以看作成自動變量)
通過上面的方式(實(shí)質(zhì)上還是通過訪問變量內(nèi)存地址的方式)去修改自動變量
問題三的解答
_block 變量變成結(jié)構(gòu)體丐巫,然后在追加到 Block 上,這樣是為了多個(gè) Block 可以訪問同一個(gè)自動變量源哩。所有Block 捕獲到該自動變量時(shí)鞋吉,加上該自動變量的結(jié)構(gòu)體就可以訪問到該自動變量鸦做。
Block的類型
- NSConcreteGlobalBlock 存在數(shù)據(jù)區(qū)域励烦,不會捕獲自動變量,能引用全局變量泼诱,全局靜態(tài)變量坛掠。
- NSConcreteStackBlock 存在棧中,當(dāng) Block 退出函數(shù)時(shí)治筒,Block 銷毀屉栓,捕獲的自動變量隨 Block 退出棧而清空。
- NSConcreteMallocBlock 存在堆耸袜,Block 不會因?yàn)橥顺龊瘮?shù)而銷毀友多,捕獲的自動變量
Block 的 copy
StackBlock 通常會 copy 到堆上,這樣做就是為了避免 StackBlock 退出作用域而被銷毀堤框。_block 也會 copy 到堆上域滥,__forwarding
的作用就是正確訪問到不論在棧還是在堆上的 val
纵柿。