1戈毒、Block
struct Block_layout {
void *isa;//指向所屬類的指針,也就是block的類型 NSStackBlock NSGlobalBlock NSMallocBlock
int flags;//標(biāo)志變量横堡,在實(shí)現(xiàn)block的內(nèi)部操作時(shí)會(huì)用到
int reserved;//保留變量
void (*invoke)(void *, ...);//執(zhí)行時(shí)調(diào)用的函數(shù)指針埋市,block內(nèi)部的執(zhí)行代碼都在這個(gè)函數(shù)中
struct Block_descriptor *descriptor;//block的詳細(xì)描述,包含 copy/dispose 函數(shù)命贴,處理block引用外部變量時(shí)使用
/* Imported variables. */
//variables: block范圍外的變量道宅,如果block沒有調(diào)用任何外部變量食听,該變量就不存在
};
struct Block_descriptor {
unsigned long int reserved;//保留變量
unsigned long int size;//block的內(nèi)存大小
void (*copy)(void *dst, void *src);// 拷貝block中被 __block 修飾的外部變量
void (*dispose)(void *);//和 copy 方法配置應(yīng)用,用來釋放資源
};
2污茵、Block語法
@property(nonatomic, copy) void (^NormalBlock)(void);
typedef void (^NormalBlock)(void);
@property(nonatomic, copy) NormalBlock block;
^【返回值類型】【參數(shù)列表】【表達(dá)式】
exp. ^int (int count) {return count + 1;}
注意:【返回值類型】和【參數(shù)列表】可省略
void (^blockName) (int parameter);
//void代表返回值類型樱报,后面一次是block名,參數(shù)
3泞当、Block有哪幾種類型
NSStackBlock
存儲(chǔ)于棧區(qū)
block 內(nèi)部引用外部變量迹蛤,retain、release 操作無效襟士,存儲(chǔ)于棧區(qū)盗飒,變量作用域結(jié)束時(shí),其被系統(tǒng)自動(dòng)釋放銷毀陋桂。
MRC 環(huán)境下逆趣,[[mutableAarry addObject: blockA],(在arc中不用擔(dān)心此問題嗜历,因?yàn)閍rc中會(huì)默認(rèn)將實(shí)例化的block拷貝到堆上)在其所在作用域結(jié)束也就是函數(shù)出棧后汗贫,從mutableAarry中取到的blockA已經(jīng)被回收,變成了野指針秸脱。正確的做法是先將blockA copy到堆上落包,然后加入數(shù)組。支持copy摊唇,copy之后生成新的NSMallocBlock類型對(duì)象咐蝇。
NSGlobalBlock
存儲(chǔ)于程序數(shù)據(jù)區(qū)
block 內(nèi)部沒有引用外部變量的 Block 類型都是 NSGlobalBlock 類型,存儲(chǔ)于全局?jǐn)?shù)據(jù)區(qū)巷查,由系統(tǒng)管理其內(nèi)存有序,retain、copy岛请、release操作都無效旭寿。引用static也為globalBlock
NSMallocBlock
存儲(chǔ)于堆區(qū)
如上例中的
_block
,[blockA copy]
操作后變量類型變?yōu)?NSMallocBlock
崇败,支持retain盅称、release,雖然 retainCount 始終是 1后室,但內(nèi)存管理器中仍然會(huì)增加缩膝、減少計(jì)數(shù),當(dāng)引用計(jì)數(shù)為零的時(shí)候釋放(可多次retain岸霹、release 操作驗(yàn)證)疾层。copy之后不會(huì)生成新的對(duì)象,只是增加了一次引用贡避,類似retain痛黎,盡量不要對(duì)Block使用retain操作予弧。
在ARC環(huán)境下,Block也是存在__NSStackBlock的時(shí)候的湖饱,平時(shí)見到最多的是_NSMallocBlock桌肴,是因?yàn)槲覀儠?huì)對(duì)Block有賦值操作,所以ARC下琉历,block 類型通過=進(jìn)行傳遞時(shí)坠七,會(huì)導(dǎo)致調(diào)用
objc_retainBlock->_Block_copy->_Block_copy_internal
方法鏈。并導(dǎo)致 NSStackBlock 類型的 block 轉(zhuǎn)換為 NSMallocBlock 類型
4旗笔、Block的結(jié)構(gòu)(Clang后的對(duì)應(yīng))
原代碼
-(void)blockDemo{
void (^block)(void) = ^{
};
}
clang后:
struct __block_impl {
void *isa;//指向所屬類的指針彪置,也就是block的類型
int Flags; //標(biāo)志變量,在實(shí)現(xiàn)block的內(nèi)部操作時(shí)會(huì)用到
int Reserved; //保留變量
void *FuncPtr;//block執(zhí)行時(shí)調(diào)用的函數(shù)指針
};
//block內(nèi)部實(shí)現(xiàn) func0
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
}
static struct __ViewController__blockDemo_block_desc_0 {
size_t reserved;
size_t Block_size;
}
__ViewController__blockDemo_block_desc_0_DATA = { 0, sizeof(struct __ViewController__blockDemo_block_impl_0)};
static void _I_ViewController_blockDemo(ViewController * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__ViewController__blockDemo_block_impl_0((void *)__ViewController__blockDemo_block_func_0, &__ViewController__blockDemo_block_desc_0_DATA));
}
捕獲變量 __block 源代碼
-(void)blockDemo{
__block int a = 100;
void (^block)(void) = ^{
a++;
};
block();
}
clang后
struct __Block_byref_a_0 {
void *__isa; //指向所屬類的指針蝇恶,被初始化為 (void*)0
__Block_byref_a_0 *__forwarding;//指向?qū)ο笤诙阎械目截? int __flags;//標(biāo)志變量拳魁,在實(shí)現(xiàn)block的內(nèi)部操作時(shí)會(huì)用到
int __size;//對(duì)象的內(nèi)存大小
int a;//原始類型的變量
};
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a)++;
}
可以看到 多了一個(gè)結(jié)構(gòu)體 被
__block
修飾的變量被封裝成了一個(gè)對(duì)象,類型為__Block_byref_a_0
撮弧,然后把&a作為參數(shù)傳給了block潘懊。
其中,isa贿衍、__flags 和 __size 的含義和之前類似授舟,而__forwarding
是用來指向?qū)ο笤诙阎械目截悾瑀untime.c 里有源碼說明:
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
...
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
// 堆中拷貝的forwarding指向它自己
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
// 棧中的forwarding指向堆中的拷貝
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
...
}
這樣做是為了保證在 block內(nèi) 或 block 變量后面對(duì)變量a的訪問贸辈,都是直接訪問堆內(nèi)的對(duì)象释树,而不上棧上的變量。同時(shí)擎淤,在 block 拷貝到堆內(nèi)時(shí)奢啥,它所捕獲的由 __block 修飾的局部基本類型也會(huì)被拷貝到堆內(nèi)(拷貝的是封裝后的對(duì)象),從而會(huì)有 copy 和 dispose處理函數(shù)嘴拢。
5桩盲、 Block copy的過程
Block
經(jīng)過copy
之后會(huì)在desc
里生成的2個(gè)函數(shù)
-
copy
函數(shù)
調(diào)用時(shí)機(jī) 棧上的Block復(fù)制到堆時(shí) -
dispose
函數(shù)
調(diào)用時(shí)機(jī) 堆上的Block
被廢棄時(shí)
當(dāng)Block
內(nèi)部訪問了帶有__block
修飾符的對(duì)象類型的auto變量時(shí)
- 當(dāng)
block
在棧上時(shí),并不會(huì)對(duì)__block
變量產(chǎn)生強(qiáng)引用 - 當(dāng)
block
被copy
到堆時(shí)- 會(huì)調(diào)用
block
內(nèi)部的copy
函數(shù) -
copy
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign
函數(shù) -
_Block_object_assign
函數(shù)會(huì)根據(jù)所指向?qū)ο蟮男揎椃?code>__strong, __weak, __unsafe_unretained)做出相應(yīng)的操作席吴,形成強(qiáng)引用(retain
)或者弱引用(注意:這里僅限于ARC
時(shí)會(huì)retain
赌结,MRC
時(shí)不會(huì)retain
)
- 會(huì)調(diào)用
6、__block的作用
-
__block
可以用于解決block
內(nèi)部無法修改auto
變量值的問題 -
__block
不能修飾全局變量抢腐、靜態(tài)變量(static
)
編譯器會(huì)將__block
變量包裝成一個(gè)對(duì)象 -
__block
修改變量:age->__forwarding->age
-
__Block_byref_age_0
結(jié)構(gòu)體內(nèi)部地址和外部變量age
是同一地址
7姑曙、__block的結(jié)構(gòu)(Clang后的對(duì)應(yīng))
編譯器會(huì)將 __block
變量包裝成一個(gè)對(duì)象
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;//age的地址
int __flags;
int __size;
int age;//age 的值
};
8襟交、__forwarding指針的作用
__forwarding
, 它是結(jié)構(gòu)體__Block_byref_abc_0
的組成部分, 且它的類型是__Block_byref_abc_0 *
迈倍。
- 棧上
__block
的__forwarding
指向本身- 棧上
__block
復(fù)制到堆上后,棧上block
的__forwarding
指向堆上的block
捣域,堆上block
的__forwarding
指向本身
__forwarding
指針存在的意義就是啼染,無論在任何內(nèi)存位置宴合,都可以順利地訪問同一個(gè)__block
變量。
9迹鹅、Block 釋放的過程
當(dāng)block
從堆中移除時(shí)
1卦洽、會(huì)調(diào)用
block
內(nèi)部的dispose
函數(shù)
2、dispose
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose
函數(shù)
3斜棚、_Block_object_dispose
函數(shù)會(huì)自動(dòng)釋放引用的__block
變量(release)
當(dāng)block
從堆上移除時(shí)阀蒂,都會(huì)通過dispose
函數(shù)來釋放它們
10、copy_dispose
11弟蚀、Block循環(huán)引用
三種方式:
__weak蚤霞、__unsafe_unretained、__block
解決循環(huán)引用
1义钉、__weak
:不會(huì)產(chǎn)生強(qiáng)引用昧绣,指向的對(duì)象銷毀時(shí),會(huì)自動(dòng)讓指針置為nil
2捶闸、__unsafe_unretained
:不會(huì)產(chǎn)生強(qiáng)引用夜畴,不安全,指向的對(duì)象銷毀時(shí)删壮,指針存儲(chǔ)的地址值不變
3贪绘、__block
:必須把引用對(duì)象置位nil
,并且要調(diào)用該block
12央碟、Block捕獲(多重嵌套情況)
block內(nèi)部會(huì)專門新增一個(gè)成員來外面變量的值兔簇,這個(gè)操作稱之為捕獲
13、Block hook的幾種實(shí)現(xiàn)
Hook Block 交換block的實(shí)現(xiàn)
BlockHook硬耍,fishhook垄琐,BlockHookDemo,YSBlockHook...
- 1经柴、交換
block
的實(shí)現(xiàn) aspect 原理就是在運(yùn)行期間動(dòng)態(tài)交換兩個(gè)方法所指向的IMP指針狸窘,那么換作Block也是一樣的道理。只要將invoke指針指向我們自定義的函數(shù)地址坯认,就可以交換block的實(shí)現(xiàn) - 2翻擒、
- 3、
14牛哺、Block為何會(huì)有Private Data
15陋气、如果獲取Block參數(shù)的個(gè)數(shù)及其類型
16、關(guān)于BLOCK_HAS_EXTENDED_LAYOUT的一些內(nèi)容
17引润、Block 常見題
1巩趁、
Block
的原理是怎樣的?本質(zhì)是什么淳附?
2议慰、__block
的作用是什么蠢古?有什么使用注意點(diǎn)?
3别凹、Block
的屬性修飾詞為什么是copy
草讶?使用Block
有哪些使用注意?
4炉菲、Block
在修改NSMutableArray
堕战,需不需要添加__block
?
注:
clang 用法:clang -fobjc-arc -framework Foundation HelloWord.m -o HelloWord
- -fobjc-arc表示編譯器需要支持ARC特性
- -framework Foundation表示引用Foundation框架
- HelloWord.m為需要進(jìn)行編譯的源代碼文件
- -o HelloWord表示輸出的可執(zhí)行文件的文件名
- 1、clang 生成C++
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m