歡迎閱讀iOS探索系列(按序閱讀食用效果更加)c++
- iOS探索 alloc流程
- iOS探索 內存對齊&malloc源碼
- iOS探索 isa初始化&指向分析
- iOS探索 類的結構分析
- iOS探索 cache_t分析
- iOS探索 方法的本質和方法查找流程
- iOS探索 動態方法解析和消息轉發機制
- iOS探索 淺嘗輒止dyld加載流程
- iOS探索 類的加載過程
- iOS探索 分類、類拓展的加載過程
- iOS探索 isa面試題分析
- iOS探索 runtime面試題分析
- iOS探索 KVC原理及自定義
- iOS探索 KVO原理及自定義
- iOS探索 多線程原理
- iOS探索 多線程之GCD應用
- iOS探索 多線程之GCD底層分析
- iOS探索 多線程之NSOperation
- iOS探索 多線程面試題分析
- iOS探索 細數iOS中的那些鎖
- iOS探索 全方位解讀Block
今天遇到一道方法交換面試題跟你們分享下...我仍是太年輕了面試
相信你們對本文的主角block
都有必定的瞭解,平常開發中也常常能看到它的身影。本文會從block概念
、blcok循環引用
、block底層
三方面進行講解bash
帶有自動變量(局部變量)的匿名函數叫作block
,又叫作匿名函數
、代碼塊
多線程
在不一樣語言中的叫法不一樣app
程序語言 | Block的名稱 |
---|---|
C | Block |
Smalltalk | Block |
Ruby | Block |
Python | Lambda |
C++ | Lambda |
JS | Anonymous function |
__NSGlobalBlock__
void (^block)(void) = ^{
NSLog(@"111");
};
NSLog(@"%@", block);
--------------------輸出結果:-------------------
<__NSGlobalBlock__: 0x10a870050>
--------------------輸出結果:-------------------
複製代碼
__NSMallocBlock__
int a = 0;
void (^block)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", block);
--------------------輸出結果:-------------------
<__NSMallocBlock__: 0x600002dca2b0>
--------------------輸出結果:-------------------
複製代碼
__NSStackBlock__
int a = 0;
NSLog(@"%@", ^{
NSLog(@"%d", a);
});
--------------------輸出結果:-------------------
<__NSStackBlock__: 0x7ffeec41e1b8>
--------------------輸出結果:-------------------
複製代碼
總結:框架
__NSGlobalBlock__
類型__NSMallocBlock__
類型__NSStackBlock__
類型除此以外,還有三種系統級別的block類型(能在libclosure源碼中看到)iphone
_NSConcreteAutoBlock
_NSConcreteFinalizingBlock
_NSConcreteWeakBlockVariable
循環引用
,與此同時編譯器也發出了警告
Capturing 'self' strongly in this block is likely to lead to a retain cycle
複製代碼
那麼就來分析一下循環引用的問題所在:函數
self
持有了block
block
持有了self
(self.name)這樣就造成了self -> block -> self
的循環引用佈局
接下來不得不提到內存管理問題了(A引用B)post
正常釋放時:A發送dealloc信號讓Bdealloc
循環引用時:A、B互相引用,引用計數不能爲0,dealloc不會被調用
接下來就介紹一下解決循環引用的幾種辦法
__weak typeof(self) weakSelf = self;
self.name = @"Felix";
self.block = ^{
NSLog(@"%@", weakSelf.name);
};
複製代碼
使用 中介者模式 __weak typeof(self) weakSelf = self
將循環引用改成weakself -> self -> block -> weakself
表面看上去仍是一個「引用圈」,可是weakself -> self
這一層是弱引用——引用計數不處理,使用weak表
管理。因此此時在頁面析構時self
就能正常的調用dealloc
了
但並非最終的解決方案,此時仍存在着問題
__weak typeof(self) weakSelf = self;
self.name = @"Felix";
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakSelf.name);
});
};
複製代碼
如同這種延時狀況,如若調用block以後立馬返回上一頁進行頁面釋放,3秒後weakself
指向的self
已經爲nil
了,此時的打印就只能打印出null
因而就有了強持有
這麼一說法
__weak typeof(self) weakSelf = self;
self.name = @"Felix";
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf.name);
});
};
複製代碼
再加一層臨時的強持有
,此時的引用就變成了strongself -> weakself -> self -> block -> strongself
看上去又是一個循環引用,但實際上strongSelf
是個臨時變量,當block做用域結束後就會釋放,從而打破循環引用進行釋放(讓釋放延後了3秒)
既然有「自動置空」,那麼也能夠「手動置空」
__block ViewController *vc = self;
self.name = @"Felix";
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
vc = nil;
});
};
複製代碼
上述代碼也是使用 中介者模式 打破循環應用的——使用vc
做爲中介者代替self
從而打破循環引用
此時的引用狀況爲vc -> self -> block -> vc
(vc在用完以後手動置空)
可是隻要不調用block,仍然存在着循環應用
解決循環引用還有一種方式——不引用
self.name = @"Felix";
self.block = ^(ViewController *vc) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", vc.name);
vc = nil;
});
}
複製代碼
上述代碼使用當前vc
做爲參數傳入block時拷貝一份,就不會出現持有的狀況,同時還能使用self
的內存空間,可以完美避免循環引用
Masonry
中是否存在循環引用?Monsary
使用的block是當作參數傳遞的,即使block內部持有self,設置佈局的view持有block,可是block不持有view,當block執行完後就釋放了,self的引用計數-1,因此block也不會持有self,因此不會致使循環引用
UIView動畫
是類方法,不被self持有(即self持有了view,但view沒有實例化)因此不會循環引用
- (void)checkLeaks {
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"%@", retainCycles);
}
複製代碼
int main(){
__block int a = 10;
void(^block)(void) = ^{
a++;
printf("Felix - %d",a);
};
block();
return 0;
}
複製代碼
用clang
將上述代碼輸出成cpp文件來查看底層實現
clang -rewrite-objc main.c -o main.cpp
複製代碼
int main(){
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
複製代碼
main函數
中能夠看到block的賦值是__main_block_impl_0
類型,它是C++中的構造函數全局搜索__main_block_impl_0
能看到它的定義
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
從上述代碼能夠看出block的本質是個 __main_block_impl_0 的結構體對象,這就是爲何能用 %@ 打印出block的緣由了
fp
傳遞了具體的block實現__main_block_func_0
,而後保存在block結構體的impl
中這就說明了block聲明只是將block實現保存起來,具體的函數實現須要自行調用
int main(){
int a = 10;
void(^block)(void) = ^{
printf("Felix %d ", a);
};
block();
return 0;
}
複製代碼
此時的block構造函數
中就會多出一個參數a
,而且在block結構體中
也會多出一個屬性a
接着把目光轉向__main_block_func_0
實現
__cself
是__main_block_impl_0
的指針,即block自己int a = __cself->a
即int a = block->a
a++
會報錯int main(){
int a = 10;
void(^block)(void) = ^{
printf("Felix %d ", a);
};
block();
return 0;
}
複製代碼
__block
修飾的屬性在底層會生成響應的結構體,保存原始變量的指針,並傳遞一個指針地址給block——所以是指針拷貝
接下來就來到libclosure源碼
中仔細看一看瞧一瞧
首先來看block結構體對象Block_layout
(等同於clang編譯出來的__Block_byref_a_0
)
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
複製代碼
其中Block_layout
是基礎的block結構空間,而部分block則擁有Block_descriptor_2
和Block_descriptor_3
結構,其中的flags
標識記錄了一些信息
但部分block則擁有Block_descriptor_2
和Block_descriptor_3
結構這句話又該怎麼去理解呢?請看下面的解釋
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock) {
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock) {
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
複製代碼
aBlock->flags & BLOCK_HAS_COPY_DISPOSE
知足,則_Block_descriptor_2
存在,反之則block沒有_Block_descriptor_2
這個結構
_Block_descriptor_2
能夠經過Block_descriptor_1
內存偏移獲得aBlock->flags & BLOCK_HAS_SIGNATURE
知足,則_Block_descriptor_3
存在
_Block_descriptor_3
能夠經過Block_descriptor_2
內存偏移獲得決定這兩個結構是否存在的絕對因素其實就是Block_layout
的flags
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
複製代碼
接下來就用匯編來看看block中的簽名
__NSGlobalBlock__簽名(_Block_copy進入時)
__NSStackBlock__簽名(_Block_copy進入時)
__NSMallocBlock__簽名(_Block_copy返回時)
@?
,表明着不明對象接下來就來研究下棧block
轉換成到堆block
的過程——_Block_copy
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
複製代碼
整段代碼主要分紅三個邏輯分支
flags
標識位——存儲引用計數的值是否有效block的引用計數不受runtime處理的,是由本身管理的
static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return old_value+2;
}
}
}
複製代碼
這裏可能有個疑問——爲何引用計數是 +2 而不是 +1 ?
——由於flags的第一號位置已經存儲着釋放標記
是不是全局block——是的話直接返回block
棧block
-> 堆block
的過程
malloc
在堆區開闢一片空間memmove
將數據從棧區拷貝到堆區invoke
、flags
同時進行修改_NSConcreteMallocBlock
爲了更好地進行探究,咱們在OC
的main文件
中進行clang編譯
xcrun -sdk iphonesimulator clang -rewrite-objc main.m
複製代碼
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
__block NSString *name = [NSString stringWithFormat:@"Felix"];
void (^fxBlock)(void) = ^{ // block_copy
name = @"Feng Felix";
};
fxBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
複製代碼
block中的第一層拷貝其實已經講過了——_Block_copy
將block從棧拷貝到堆
__main_block_desc_0_DATA結構體
,在裏面又會去調用
__main_block_copy_0
函數,
__main_block_copy_0
裏面會調用
_Block_object_assign
——這就是第二層拷貝的調用入口
接下來就來看看_Block_object_assign
在底層都作了什麼(注意傳參)
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/******* id object = ...; [^{ object; } copy]; ********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/******* void (^object)(void) = ...; [^{ object; } copy]; ********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/******* // copy the onstack __block container to the heap // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __block ... x; __weak __block ... x; [^{ x; } copy]; ********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/******* // copy the actual field held in the __block container // Note this is MRC unretained __block only. // ARC retained __block is handled by the copy helper directly. __block id object; __block void (^object)(void); [^{ object; } copy]; ********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/******* // copy the actual field held in the __block container // Note this __weak is old GC-weak/MRC-unretained. // ARC-style __weak is handled by the copy helper directly. __weak __block id object; __weak __block void (^object)(void); [^{ object; } copy]; ********/
*dest = object;
break;
default:
break;
}
}
複製代碼
根據flags & BLOCK_ALL_COPY_DISPOSE_FLAGS
進到不一樣分支來處理捕獲到的變量
枚舉值 | 數值 | 含義 |
---|---|---|
BLOCK_FIELD_IS_OBJECT | 3 | 對象 |
BLOCK_FIELD_IS_BLOCK | 7 | block變量 |
BLOCK_FIELD_IS_BYREF | 8 | __block修飾的結構體 |
BLOCK_FIELD_IS_WEAK | 16 | __weak修飾的變量 |
BLOCK_BYREF_CALLER | 128 | 處理block_byref內部對象內存的時候 會加的一個額外的標記,配合上面的枚舉一塊兒使用 |
此時捕獲到的變量是被__block
修飾的BLOCK_FIELD_IS_BYREF
類型,就會調用*dest = _Block_byref_copy(object);
static struct Block_byref *_Block_byref_copy(const void *arg) {
// 臨時變量的保存
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
// 用原目標的大小在堆區生成一個Block_byref
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// 原來的區域和新的區域都指向同一個對象,使得block具有了修改能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
複製代碼
name
的大小在堆區生成一個Block_byrefcopy->forwarding = copy; & src->forwarding = copy;
——原來的區域和新的區域都指向同一個對象,使得block具有了修改能力(*src2->byref_keep)(copy, src)
開始第三層拷貝(*src2->byref_keep)(copy, src)
跟進去會來到Block_byref
結構來,而byref_keep
是Block_byref
的第5個屬性
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
複製代碼
__block
修飾的變量在底層其實調用了以下的構造方法——此時的第5位就等於byref_keep
,因此在第二層拷貝時會調用__Block_byref_id_object_copy_131
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
複製代碼
第五個函數會去調用_Block_object_assign
函數
這個(char*)dst + 40
看着以爲好莫名其妙啊...其實看到__Block_byref_name_0
就頓悟了,恰好取得變量name
對象
struct __Block_byref_name_0 {
void *__isa;
__Block_byref_name_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *name;
};
複製代碼
而_Block_object_assign
在對BLOCK_FIELD_IS_OBJECT
狀況時會作出以下操做:
case BLOCK_FIELD_IS_OBJECT:
/******* id object = ...; [^{ object; } copy]; ********/
_Block_retain_object(object);
*dest = object;
break;
複製代碼
_Block_retain_object
是個空函數,由於block捕獲的外接變量由ARC自動管理name
進行拷貝看完了三層拷貝,再來看一下釋放函數_Block_object_dispose
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
複製代碼
__block
修飾,就將指向指回原來的區域並使用free
釋放__main_block_impl_0
的結構體對象,因此能用%@
打印自行調用
自動生成一個屬性
來保存變量__block
修飾的屬性在底層會生成響應的結構體,保存原始變量的指針,並傳遞一個指針地址
給blockblock還有hook
一塊也是須要去學習瞭解的
小小的block也是有不少底層知識須要研究的,越學會發現本身越眇小,其實否則,只是你的視角開闊了,正是如此纔會進步