1、Block
struct Block_layout {
void *isa;//指向所屬類的指針,也就是block的類型 NSStackBlock NSGlobalBlock NSMallocBlock
int flags;//標志變量,在實現(xiàn)block的內(nèi)部操作時會用到
int reserved;//保留變量
void (*invoke)(void *, ...);//執(zhí)行時調(diào)用的函數(shù)指針,block內(nèi)部的執(zhí)行代碼都在這個函數(shù)中
struct Block_descriptor *descriptor;//block的詳細描述,包含 copy/dispose 函數(shù),處理block引用外部變量時使用
/* 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 方法配置應用,用來釋放資源
};
2、Block語法
@property(nonatomic, copy) void (^NormalBlock)(void);
typedef void (^NormalBlock)(void);
@property(nonatomic, copy) NormalBlock block;
^【返回值類型】【參數(shù)列表】【表達式】
exp. ^int (int count) {return count + 1;}
注意:【返回值類型】和【參數(shù)列表】可省略
void (^blockName) (int parameter);
//void代表返回值類型,后面一次是block名,參數(shù)
3、Block有哪幾種類型
NSStackBlock存儲于棧區(qū)
block 內(nèi)部引用外部變量,retain、release 操作無效,存儲于棧區(qū),變量作用域結(jié)束時,其被系統(tǒng)自動釋放銷毀。
MRC 環(huán)境下,[[mutableAarry addObject: blockA],(在arc中不用擔心此問題,因為arc中會默認將實例化的block拷貝到堆上)在其所在作用域結(jié)束也就是函數(shù)出棧后,從mutableAarry中取到的blockA已經(jīng)被回收,變成了野指針。正確的做法是先將blockA copy到堆上,然后加入數(shù)組。支持copy,copy之后生成新的NSMallocBlock類型對象。
NSGlobalBlock 存儲于程序數(shù)據(jù)區(qū)
block 內(nèi)部沒有引用外部變量的 Block 類型都是 NSGlobalBlock 類型,存儲于全局數(shù)據(jù)區(qū),由系統(tǒng)管理其內(nèi)存,retain、copy、release操作都無效。引用static也為globalBlock
NSMallocBlock 存儲于堆區(qū)
如上例中的
_block,[blockA copy]操作后變量類型變?yōu)?NSMallocBlock,支持retain、release,雖然 retainCount 始終是 1,但內(nèi)存管理器中仍然會增加、減少計數(shù),當引用計數(shù)為零的時候釋放(可多次retain、release 操作驗證)。copy之后不會生成新的對象,只是增加了一次引用,類似retain,盡量不要對Block使用retain操作。
在ARC環(huán)境下,Block也是存在__NSStackBlock的時候的,平時見到最多的是_NSMallocBlock,是因為我們會對Block有賦值操作,所以ARC下,block 類型通過=進行傳遞時,會導致調(diào)用
objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈。并導致 NSStackBlock 類型的 block 轉(zhuǎn)換為 NSMallocBlock 類型
4、Block的結(jié)構(gòu)(Clang后的對應)
原代碼
-(void)blockDemo{
void (^block)(void) = ^{
};
}
clang后:
struct __block_impl {
void *isa;//指向所屬類的指針,也就是block的類型
int Flags; //標志變量,在實現(xiàn)block的內(nèi)部操作時會用到
int Reserved; //保留變量
void *FuncPtr;//block執(zhí)行時調(diào)用的函數(shù)指針
};
//block內(nèi)部實現(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;//標志變量,在實現(xiàn)block的內(nèi)部操作時會用到
int __size;//對象的內(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)++;
}
可以看到 多了一個結(jié)構(gòu)體 被
__block修飾的變量被封裝成了一個對象,類型為__Block_byref_a_0,然后把&a作為參數(shù)傳給了block。
其中,isa、__flags 和 __size 的含義和之前類似,而__forwarding是用來指向?qū)ο笤诙阎械目截?,runtime.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 變量后面對變量a的訪問,都是直接訪問堆內(nèi)的對象,而不上棧上的變量。同時,在 block 拷貝到堆內(nèi)時,它所捕獲的由 __block 修飾的局部基本類型也會被拷貝到堆內(nèi)(拷貝的是封裝后的對象),從而會有 copy 和 dispose處理函數(shù)。
5、 Block copy的過程
Block經(jīng)過copy之后會在desc里生成的2個函數(shù)
-
copy函數(shù)
調(diào)用時機 棧上的Block復制到堆時 -
dispose函數(shù)
調(diào)用時機 堆上的Block被廢棄時
當Block內(nèi)部訪問了帶有__block修飾符的對象類型的auto變量時
- 當
block在棧上時,并不會對__block變量產(chǎn)生強引用 - 當
block被copy到堆時- 會調(diào)用
block內(nèi)部的copy函數(shù) -
copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù) -
_Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃?code>__strong, __weak, __unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain)
- 會調(diào)用
6、__block的作用
-
__block可以用于解決block內(nèi)部無法修改auto變量值的問題 -
__block不能修飾全局變量、靜態(tài)變量(static)
編譯器會將__block變量包裝成一個對象 -
__block修改變量:age->__forwarding->age -
__Block_byref_age_0結(jié)構(gòu)體內(nèi)部地址和外部變量age是同一地址
7、__block的結(jié)構(gòu)(Clang后的對應)
編譯器會將 __block變量包裝成一個對象
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復制到堆上后,棧上block的__forwarding指向堆上的block,堆上block的__forwarding指向本身
__forwarding指針存在的意義就是,無論在任何內(nèi)存位置,都可以順利地訪問同一個__block 變量。
9、Block 釋放的過程
當block從堆中移除時
1、會調(diào)用
block內(nèi)部的dispose函數(shù)
2、dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
3、_Block_object_dispose函數(shù)會自動釋放引用的__block變量(release)
當block從堆上移除時,都會通過dispose函數(shù)來釋放它們
10、copy_dispose
11、Block循環(huán)引用
三種方式:
__weak、__unsafe_unretained、__block解決循環(huán)引用
1、__weak:不會產(chǎn)生強引用,指向的對象銷毀時,會自動讓指針置為nil
2、__unsafe_unretained:不會產(chǎn)生強引用,不安全,指向的對象銷毀時,指針存儲的地址值不變
3、__block:必須把引用對象置位nil,并且要調(diào)用該block
12、Block捕獲(多重嵌套情況)
block內(nèi)部會專門新增一個成員來外面變量的值,這個操作稱之為捕獲
13、Block hook的幾種實現(xiàn)
Hook Block 交換block的實現(xiàn)
BlockHook,fishhook,BlockHookDemo,YSBlockHook...
- 1、交換
block的實現(xiàn) aspect 原理就是在運行期間動態(tài)交換兩個方法所指向的IMP指針,那么換作Block也是一樣的道理。只要將invoke指針指向我們自定義的函數(shù)地址,就可以交換block的實現(xiàn) - 2、
- 3、
14、Block為何會有Private Data
15、如果獲取Block參數(shù)的個數(shù)及其類型
16、關于BLOCK_HAS_EXTENDED_LAYOUT的一些內(nèi)容
17、Block 常見題
1、
Block的原理是怎樣的?本質(zhì)是什么?
2、__block的作用是什么?有什么使用注意點?
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為需要進行編譯的源代碼文件
- -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