- Block又稱為代碼塊,匿名函數(shù),函數(shù)指針,下面來詳細介紹Block的相關內容;
Block的聲明定義
【第一種:在property屬性中定義block】
- 定義的是具有特定參數(shù)與返回值類型的block變量,屬于定義block變量;
@interface ViewController ()
//定義了一個block變量testOne,類型為void(^)(void)
@property(nonatomic,copy)void(^testOne)(void);
//定義了一個block變量testTwo,類型為void(^)(int)
@property(nonatomic,copy)void(^testTwo)(int a);
//定義了一個block變量testThree,類型為NSString *(^)(int,int)
@property(nonatomic,copy)NSString *(^testThree)(int a,int b);
@end
【第二種:使用typedef定義block類型】
- 定義的是具有特定參數(shù)與返回值類型的block,屬于定義block類型;
//定義了一個YYBlock類型
typedef void(^YYBlock)(void);
//定義了一個XXBlock類型
typedef void(^XXBlock)(int a);
//定義了一個ZZBlock類型
typedef NSString *(^ZZBlock)(int a,int b);
【第三種:在函數(shù)內部定義局部block變量并初始化其代碼塊的實現(xiàn)】
- 定義的是block變量且初始化了代碼塊;
- (void)viewDidLoad {
[super viewDidLoad];
//定義了一個block變量add,類型為void(^)(void)
void(^add)(void) = ^{
NSLog(@"add");
};
//定義了一個block變量minus,類型為void(^)(int)
void(^minus)(int) = ^(int a){
NSLog(@"%d",a);
};
//定義了一個block變量multiply,類型為int(^)(int,int)
int(^multiply)(int,int) = ^(int a,int b){
int c = a * b;
NSLog(@"%d",c);
return c;
};
//block代碼塊的調用
add();
minus(10);
multiply(10,5);
}
- 等號左邊是block變量名+block類型;
- 等號右邊是block代碼塊初始化實現(xiàn);
- 等號左邊在定義block類型時,形參的參數(shù)名可以省略,只保留形參的類型,從這里可以看出block與函數(shù)指針十分相似;
【第四種:block作為函數(shù)方法的參數(shù)】
- 這里引用
AFNetworking中發(fā)送網絡請求的函數(shù)定義;
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure;
-
success與failure是block變量名,其作為函數(shù)參數(shù);success的block類型為
(void (^)(NSURLSessionDataTask *task, id responseObject)) - 外界業(yè)務層調用此網絡層函數(shù),業(yè)務層會初始化block變量success的代碼塊,網絡層在發(fā)送網絡請求獲取網絡數(shù)據(jù)之后,會調用success代碼塊,并傳入task與responseObject兩個參數(shù);
- block作為函數(shù)參數(shù)時,block中的參數(shù)名通常要寫上,便于理解參數(shù)的含義;
Block類型
-
block通常有三種類型,分別如下:
- 【第一種:
__NSGlobalBlock__】:全局區(qū)block,存儲在全局區(qū); - 【第二種:
__NSMallocBlock__】:堆區(qū)block,因為block既是函數(shù),也是對象; - 【第三種:
__NSStackBlock__】:棧區(qū)block,存儲在棧區(qū);
- 【第一種:
新建
C語言工程,配置成MRC環(huán)境:設置target --> Build Setting --> 搜索gar --> Objective-C Automatic Reference Counting - NO,LLDB調試如下:



- 在MRC環(huán)境下的總結如下:
- 全局block變量 訪問外界變量 NSGlobalBlock
- 全局block變量 未訪問外界變量 NSGlobalBlock
- 局部block變量 未訪問外界變量 NSGlobalBlock
- 局部block變量 訪問外界局部變量 NSStackBlock
- 局部block變量 訪問外界全局變量 NSGlobalBlock
- 將C語言工程設置成ARC環(huán)境,LLDB調試如下:



-
在ARC環(huán)境下的總結如下:
- 全局block變量 訪問外界變量 NSGlobalBlock
- 全局block變量 未訪問外界變量 NSGlobalBlock
- 局部block變量 未訪問外界變量 NSGlobalBlock
- 局部block變量 訪問外界局部變量 NSMallocBlock
- 局部block變量 訪問外界全局變量 NSGlobalBlock
比較MRC與ARC兩種環(huán)境,可以看出
當局部 block變量在訪問外界局部變量時,block變量會從棧區(qū)拷貝(copy)到堆區(qū)即__NSStackBlock__ --> __NSMallocBlock__;我們知道一個引用在默認情況下是強引用即strong,若在ARC環(huán)境下用弱引用weak修飾block變量時,其訪問外界局部變量,依然是棧區(qū)block,并沒有拷貝到堆區(qū);

-
最終總結針對ARC環(huán)境,局部block變量(最常見):
- 創(chuàng)建的block為空實現(xiàn)時,默認存儲在全局區(qū),即全局區(qū)block;
- 如果block訪問外界局部變量時:
- 如果此時的block是強引用(默認強引用),則block存儲在堆區(qū),即堆區(qū)block;
- 如果此時的block通過__weak修飾即弱引用,則block存儲在棧區(qū),即棧區(qū)block;
現(xiàn)看一案例代碼,在MRC情況下:
#import <Foundation/Foundation.h>
void(^block)(void);
void test(){
int age = 10;
block = ^{
NSLog(@"age = %d",age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
- 在test函數(shù)中定義全局的block變量,且block訪問了外界局部變量a,由于是在MRC環(huán)境下,所以此block是在棧區(qū)分配內存;
- 當test函數(shù)執(zhí)行完,block就會被回收,當下面再調用block時,會出現(xiàn)數(shù)據(jù)錯亂的問題;
- 若在ARC環(huán)境下,系統(tǒng)默認會將訪問了外界局部變量的block從棧區(qū)拷貝到堆區(qū),那么再調用block時,就不會出現(xiàn)數(shù)據(jù)錯亂的問題;
Block的copy操作
- 不同類型block的copy操作如下:

- 在ARC環(huán)境下,系統(tǒng)會對MRC環(huán)境下的棧區(qū)block做默認的copy操作;
- 在ARC環(huán)境下,用強指針指向棧區(qū)block,會將其copy到堆區(qū);
- 在ARC環(huán)境下,棧區(qū)block作為返回值時,會將其copy到堆區(qū);
- 在ARC環(huán)境下,棧區(qū)block作為方法名含有
usingBlock的方法參數(shù)時,會將其copy到堆區(qū); - 在ARC環(huán)境下,棧區(qū)block作為GCD函數(shù)的參數(shù)時,會將其copy到堆區(qū);
Block循環(huán)引用
- 廢話不多說先上案例如下所示:

第一個例子:控制器NextVC持有block,對block強引用,block內部出現(xiàn)self,即對NextVC強引用,NextVC與block彼此之間強引用,形成強引用環(huán)即循環(huán)引用,兩者都不能釋放,造成內存泄漏;
第二個例子:animation的block對self(NextVC)強引用,只是單方面的強引用,沒有形成循環(huán)引用,不會造成內存泄漏;
-
解決循環(huán)引用的常見方案有以下幾種方式:
- 【方式一】使用弱引用__weak修飾符;
- 【方式二】__block修飾對象(需要注意的是在block內部需要置空對象,而且block必須調用);
- 【方式三】傳遞對象self作為block的參數(shù),提供給block內部使用;
- 【方式四】使用NSProxy;
下面來詳細介紹著幾種方式:
方式一:使用弱引用__weak修飾符
- 當block內部為嵌套block時,直接使用__weak修飾符即可;

- self與weakSelf指向的是同一塊內存,即兩者是同一個對象;
- block對weakSelf不會強引用,從而打破了強引用環(huán),不會造成內存泄漏;
- 當block內部嵌套block,需要同時使用__weak 和 __strong;

- self.block內部一定不能出現(xiàn)self,出現(xiàn)就會對self強引用,造成循環(huán)引用;
- strongSelf是一個臨時變量,在self.block的作用域內,即內部block執(zhí)行完就釋放strongSelf;
方式二:__block修飾變量

- 注意的是這里的block必須調用,如果不調用block,vc就不會置空,那么依舊是循環(huán)引用,self和block都不會被釋放;
方式三:傳遞對象self作為block的參數(shù)

- 將對象self作為block參數(shù),提供給block內部使用,不會有引用計數(shù)問題;
方式四:使用NSProxy(這里只做簡單介紹)
- 首先來介紹一下NSProxy類;
- NSProxy 和 NSObject是同級的一個類,也可以說是一個虛擬類,只是實現(xiàn)了NSObject的協(xié)議;
- NSProxy 其實是一個消息重定向封裝的一個抽象類,類似一個代理人,中間件,可以通過繼承它,并重寫下面兩個方法來實現(xiàn)消息轉發(fā)到另一個實例;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
NSProxy的使用場景
實現(xiàn)多繼承功能;
解決了NSTimer&CADisplayLink創(chuàng)建時對self強引用問題,參考YYKit的YYWeakProxy;
下面通過代碼實例來實現(xiàn)上述的兩種場景:
定義三個類
YYProxy,YYStudent,YYTeacher代碼實現(xiàn)如下:
#import <Foundation/Foundation.h>
@interface YYProxy : NSProxy
- (id)transformObjc:(NSObject *)objc;
+ (instancetype)proxyWithObjc:(id)objc;
@end
#import "YYProxy.h"
@interface YYProxy ()
@property(nonatomic,weak,readonly)NSObject *objc;
@end
@implementation YYProxy
+ (instancetype)proxyWithObjc:(id)objc{
return [[self alloc] transformObjc:objc];
}
- (id)transformObjc:(NSObject *)objc{
_objc = objc;
return self;
}
//1.查詢該方法的方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
NSMethodSignature *signature;
if (self.objc) {
signature = [self.objc methodSignatureForSelector:sel];
}else{
signature = [super methodSignatureForSelector:sel];
}
return signature;
}
//2.有了方法簽名之后就會調用方法實現(xiàn)
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.objc respondsToSelector:sel]) {
[invocation invokeWithTarget:self.objc];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.objc respondsToSelector:aSelector];
}
@end
#import <Foundation/Foundation.h>
@interface YYStudent : NSObject
- (void)study;
@end
#import "YYStudent.h"
@implementation YYStudent
- (void)study{
NSLog(@"%s",__func__);
}
@end
#import <Foundation/Foundation.h>
@interface YYTeacher : NSObject
- (void)edcuate;
@end
#import "YYTeacher.h"
@implementation YYTeacher
- (void)edcuate{
NSLog(@"%s",__func__);
}
@end
- 通過YYProxy實現(xiàn)多繼承功能;

YYProxy類獲取了YYStudent與YYTeacher類中的功能方法,主要是通過消息的慢速轉發(fā)實現(xiàn)的,原理在 iOS底層系列14 -- 消息流程的動態(tài)方法決議與轉發(fā)這篇文章中有詳細闡述,YYProxy由于沒有目標方法實現(xiàn),將其轉發(fā)給YYStudent與YYTeacher類;
通過YYProxy解決定時器中self的強引用問題;

- 將定時器的target指定為YYProxy對象,然后YYProxy對象再將定時器的回調消息函數(shù)轉發(fā)給當前控制器;
- 在當前控制器銷毀時,一定要銷毀定時器,從而釋放YYProxy對象;
Block導致循環(huán)引用的總結
- 循環(huán)應用的解決方式從根本上來說就兩種,以self -> block -> self為例:
-
打斷self 對 block的強引用,block屬性修飾符使用weak,但是這樣會導致block還未創(chuàng)建完就釋放了,所以從這里打斷強引用行不通; -
打斷block對self的強引用,主要就是self的作用域和block作用域的通訊,通訊有代理、傳值、通知、傳參等幾種方式,用于解決循環(huán),常見的解決方式如下:- weak-strong-修飾符;
- __block(block內對象置空,且調用block);
- 將對象self作為block的參數(shù);
- 通過NSProxy的子類代替self;
Block的底層結構
- 在C語言工程的main.文件中,定義一個block變量,且調用block;

- cd 到指定文件夾 輸入
clang -rewrite-objc main.m -o main.cpp即將main.m文件編譯成main.cpp文件,底層C++中的block的定義與調用如下所示:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block中代碼塊 封裝在當前函數(shù)中
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//bound by copy
int a = __cself->a;
printf("YY a = %d", a);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
int a = 10;
//block的定義
void(*TestTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
//block的調用
((void (*)(__block_impl *))((__block_impl *)TestTwo)->FuncPtr)((__block_impl *)TestTwo);
}
return 0;
}

block中定義的代碼塊在底層是封裝在
__main_block_func_0C語言函數(shù)中;可以看到block本質是
__main_block_impl_0結構體;定義block時,底層是調用
__main_block_impl_0結構體的同名函數(shù)__main_block_impl_0(),第一個參數(shù)傳入是__main_block_func_0函數(shù)指針,也就是block中代碼塊實現(xiàn),然后將參數(shù)的函數(shù)指針賦值給成員變量impl的FuncPtr成員;block的調用,最終也是通過
__main_block_impl_0結構體的成員impl的FuncPtr函數(shù)指針來調用block代碼塊;由于
impl是__main_block_impl_0結構體的第一個成員,那么impl的內存地址就是__main_block_impl_0結構體的內存地址,所以在調用block時,可以將__main_block_impl_0結構體強轉成impl然后調用FuncPtr函數(shù)指針指向的函數(shù),也就是block中的代碼塊;block捕獲外界局部變量時,從底層我們看到在
__main_block_impl_0結構體中有一個int類型成員變量a,其就是用來接收捕獲的外界變量值的,在__main_block_func_0函數(shù)中完成接受賦值操作,也就是說block在捕獲外界變量時,在其底層結構體內部會自動生成同名成員變量來保存;上述捕獲的外界局部變量,在block底層結構體中會自動生成一個同名成員變量保存其值,
屬于值傳遞,也就是說block內部捕獲的變量與外界的變量是兩塊不同的內存地址,所以在外界更改變量的值是不會影響到block內部捕獲到的變量的值的;
其次在block內部是不能更改捕獲變量的值的,因為內外變量的內存地址不同;
下面嘗試捕獲全局變量,靜態(tài)變量,看看底層結構體如何操作:


block訪問全局變量,其底層結構體并
沒有自動生成同名的成員變量,表明block不會捕獲全局變量,至于block代碼塊中出現(xiàn)全局變量的賦值,是因為全局變量可以在當前文件中如何地方都可訪問到;block訪問靜態(tài)局部變量時,其底層結構體會自動生成一個同名指針的成員,屬于指針傳遞,所以在block內部可以更改捕獲的外界變量的值;
-
總結:
-
block能捕獲外界局部變量,block底層結構體會自動生成同名的成員變量來接收外界局部變量的值,但在block內部不能修改外界局部變量的值,因為是值傳遞,內外內存地址不同; -
block能捕獲外界靜態(tài)局部變量,其底層結構體會自動生成同名的成員變量來接收,且在block內部可以修改外界局部變量的值,因為是地址的傳遞,內外內存地址相同; -
block不能捕獲外界全局變量,block底層結構體不會自動生成同名的成員變量來接收,但在block內部可以修改全局變量的值,因為全局變量是全局的可以直接訪問;
-
由上面的內容我們知道,在block內部是不能修改局部變量的,但是可以修改靜態(tài)局部變量,那么如何修改局部變量呢?接下來就要引出__block修飾符;通過__block修飾符我們可以在block內部修改局部變量;
__block修飾符的底層實現(xiàn)
- 先上代碼如下所示:

- 編譯之后的底層代碼:
struct __Block_byref_m_0 {
void *__isa;
__Block_byref_m_0 *__forwarding;
int __flags;
int __size;
int m;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_m_0 *m; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_m_0 *_m, int flags=0) : m(_m->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_m_0 *m = __cself->m;
(m->__forwarding->m) = 20;
printf("YY m = %d",(m->__forwarding->m));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->m, (void*)src->m, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->m, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_m_0 m = {(void*)0,(__Block_byref_m_0 *)&m, 0, sizeof(__Block_byref_m_0), 10};
void(*TestTwo)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_m_0 *)&m, 570425344));
((void (*)(__block_impl *))((__block_impl *)TestTwo)->FuncPtr)((__block_impl *)TestTwo);
}
return 0;
}
- __block修飾的外界局部變量,在底層結構中會生成
__Block_byref_m_0結構體,此結構體會保存局部變量的值和指針,且其內部還有一個__forwarding指針指向它自己; - 在block內部捕獲的不是__block變量的值,而是捕獲__block變量的地址,通過地址訪問內存修改其值;
- 打印出__block修飾的局部變量m與內部捕獲的變量m的地址,如下所示:

- 可以看出__block修飾的局部變量存儲在棧區(qū),而內部捕獲到的局部變量存儲在堆區(qū);
- block訪問__block修飾局部變量,其底層結構體之間的關系如下:

- __block修飾的局部變量,在底層會被包裝成一個
__Block_byref_age_0結構體,此結構體有一個__forwarding成員是指向自己的,其分析如下:

- 當block還在棧區(qū)時,__forwarding指向的是棧區(qū)的__block變量的底層結構體;
- 當block拷貝到堆區(qū)時,棧區(qū)的__forwarding指向堆區(qū)的__block變量的底層結構體,堆區(qū)的__forwarding也是指向__block變量的底層結構體,保證通過__forwarding指針訪問的一定是堆區(qū)的__block變量;

block底層源碼實現(xiàn)
- 在定義block處加上斷點,然后進入?yún)R編頁面看到如下:

- 在創(chuàng)建block時會調用
objc_retainBlock函數(shù); - 在工程中配置
符號斷點 objc_retainBlock,LLDB調試如下:

- 再在工程中配置
符號斷點 _Block_copy,LLDB調試如下:

- 可以看到底層執(zhí)行的是
libsystem_blocks.dylib中的_Block_copy函數(shù); - 到蘋果開源網站下載 libclosure-78 源碼,全局搜索
_Block_copy,結果如下所示:

- 看到
Block_layout結構體,它才是block的真正類型,Block_layout結構如下:
// Block 結構體
struct Block_layout {
//指向表明block類型的類
void *isa;//8字節(jié)
//用來作標識符的,類似于isa中的位域,按bit位表示一些block的附加信息
volatile int32_t flags; // contains ref count 4字節(jié)
//保留信息,可以理解預留位置,用于存儲block內部變量信息
int32_t reserved;//4字節(jié)
//函數(shù)指針,指向具體的block實現(xiàn)的調用地址
BlockInvokeFunction invoke;
//block的附加信息
struct Block_descriptor_1 *descriptor;
// imported variables
};
- isa:表明block是一種class類;
- flags:標識符,按bit位表示一些block的附加信息,類似于isa中的位域,其中flags的種類有以下幾種,主要重點關注BLOCK_HAS_COPY_DISPOSE 和 BLOCK_HAS_SIGNATURE, BLOCK_HAS_COPY_DISPOSE 決定是否有 Block_descriptor_2,BLOCK_HAS_SIGNATURE 決定是否有 Block_descriptor_3;
- 第1 位 - BLOCK_DEALLOCATING,釋放標記,-般常用 BLOCK_NEEDS_FREE 做 位與 操作,一同傳入 Flags , 告知該 block 可釋放。
- 第16位 - BLOCK_REFCOUNT_MASK,存儲引用計數(shù)的值,是一個可選用參數(shù);
- 第24位 - BLOCK_NEEDS_FREE,低16是否有效的標志,程序根據(jù)它來決定是否增加或是減少引用計數(shù)位的 值;
- 第25位 - BLOCK_HAS_COPY_DISPOSE,是否擁有拷貝輔助函數(shù)(a copy helper function);
- 第26位 - BLOCK_IS_GC,是否擁有 block 析構函數(shù);
- 第27位,標志是否有垃圾回收;//OS X
- 第28位 - BLOCK_IS_GLOBAL,標志是否是全局block;
- 第30位 - BLOCK_HAS_SIGNATURE,與 BLOCK_USE_STRET 相對,判斷當前 block 是否擁有一個簽名。用于 runtime 時動態(tài)調用。
// flags 標識
// Values for Block_layout->flags to describe block objects
enum {
//釋放標記,一般常用于BLOCK_BYREF_NEEDS_FREE做位與運算,一同傳入flags,告知該block可釋放
BLOCK_DEALLOCATING = (0x0001), // runtime
//存儲引用引用計數(shù)的 值,是一個可選用參數(shù)
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
//低16位是否有效的標志,程序根據(jù)它來決定是否增加或者減少引用計數(shù)位的值
BLOCK_NEEDS_FREE = (1 << 24), // runtime
//是否擁有拷貝輔助函數(shù),(a copy helper function)決定block_description_2
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
//是否擁有block C++析構函數(shù)
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
//標志是否有垃圾回收,OSX
BLOCK_IS_GC = (1 << 27), // runtime
//標志是否是全局block
BLOCK_IS_GLOBAL = (1 << 28), // compiler
//與BLOCK_HAS_SIGNATURE相對,判斷是否當前block擁有一個簽名,用于runtime時動態(tài)調用
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
//是否有簽名
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
//使用有拓展,決定block_description_3
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
- reserved:保留信息,可以理解預留位置,猜測是用于存儲block內部變量信息;
- invoke:是一個函數(shù)指針,指向block的執(zhí)行代碼;
- descriptor:block的附加信息,比如保留變量數(shù)、block的大小、進行copy或dispose的輔助函數(shù)指針,有三類如下所示:
- Block_descriptor_1是必選的;
- Block_descriptor_2 和 Block_descriptor_3都是可選的;
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;//保留信息
uintptr_t size;//block大小
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;//拷貝函數(shù)指針
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 布局
};
- 以上關于descriptor的可以從其構造函數(shù)中體現(xiàn),其中Block_descriptor_2和Block_descriptor_3都是通過Block_descriptor_1的地址,經過內存平移得到的;
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;//默認打印
}
#endif
// CJL注釋:Block 的描述 : copy 和 dispose 函數(shù)
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;//descriptor_1的地址
desc += sizeof(struct Block_descriptor_1);//通過內存平移獲取
return (struct Block_descriptor_2 *)desc;
}
//Block 的描述 : 簽名相關
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;
}
- 下面來探索block在創(chuàng)建的時候,其存儲域的變化過程:
- 初始化創(chuàng)建一個局部block變量add,內部未訪問外界局部變量,在創(chuàng)建處打下斷點:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.button];
//定義了一個block變量add,類型為void(^)(void)
__block int m = 10;
void(^add)(void) = ^{
};
add();
}
- 進入?yún)R編界面,執(zhí)行到
objc_retainBlock斷點停住,LLDB調試如下:

- 看到此時的block類型為全局block,即
__NSGlobalBlock__; - 若block內部捕獲外界局部變量時,代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.button];
//定義了一個block變量add,類型為void(^)(void)
__block int m = 10;
void(^add)(void) = ^{
m = 20;
NSLog(@" m = %d",m);
};
add();
}
- 進入?yún)R編界面,執(zhí)行到
objc_retainBlock斷點停住,LLDB調試如下:

- 看到此時的block類型為棧區(qū)block,即
__NSStackBlock__; - 添加一個符號斷點
_Block_copy,當執(zhí)行到此函數(shù)匯編指令的ret指令時,停住斷點,LLDB調試結果如下:

可以看到底層源碼在調用
_Block_copy函數(shù)之后,block類型變成了堆區(qū)block,即__NSMallocBlock__,完成了block從棧區(qū)到堆區(qū)的拷貝;_Block_copy的函數(shù)源碼解析:
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
//這里是核心重點 block的拷貝操作: 棧Block -> 堆Block
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;//強轉為Block_layout類型對象,防止對外界造成影響
if (aBlock->flags & BLOCK_NEEDS_FREE) {//是否需要釋放
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {//如果是全局block,直接返回
return aBlock;
}
else {//為棧block 或者 堆block,由于堆區(qū)需要申請內存,所以只可能是棧區(qū)
// Its a stack block. Make a copy. 它是一個棧區(qū)block,執(zhí)行拷貝
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);//申請空間并接收
if (!result) return NULL;
//通過memmove內存拷貝,將 aBlock 拷貝至result
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;//可以直接調起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;//設置block對象類型為堆區(qū)block
return result;
}
}
首先判斷block是否需要free,需要free就直接釋放;
然后判斷block是否為全局block,如果是則不需要copy,直接返回;
-
最后專門時針對棧區(qū)block的邏輯操作,將棧區(qū)block拷貝到堆區(qū);
- 通過malloc申請內存空間用于接收block;
- 通過memmove將block拷貝至新申請的內存中;
- 設置block對象的類型為堆區(qū)block,即result->isa = _NSConcreteMallocBlock
_Block_object_assign函數(shù)是在底層編譯代碼中,當block從棧區(qū)拷貝到堆區(qū)時,外部變量的拷貝(棧區(qū)-->堆區(qū))調用的方法就是它;外部變量的類型如下:
// Block 捕獲的外界變量的種類
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
//普通對象,即沒有其他的引用類型
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
//block類型作為變量
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
//經過__block修飾的變量
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
//weak 弱引用變量
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
//返回的調用對象 - 處理block_byref內部對象內存會加的一個額外標記,配合flags一起使用
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
其中最常用的是
BLOCK_FIELD_IS_BYREF與BLOCK_FIELD_IS_OBJECT;_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;
}
}
針對普通對象(BLOCK_FIELD_IS_OBJECT),執(zhí)行
_Block_retain_object(),引用計數(shù)+1;針對block變量(BLOCK_FIELD_IS_BLOCK),執(zhí)行
_Block_copy()即拷貝;針對__block修飾的變量(BLOCK_FIELD_IS_BYREF),執(zhí)行
_Block_byref_copy();_Block_byref_copy的源碼實現(xiàn)如下:
static struct Block_byref *_Block_byref_copy(const void *arg) {
//強轉為Block_byref結構體類型,保存一份
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack 申請內存
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內部持有的Block_byref 和 外界的Block_byref 所持有的對象是同一個,這也是為什么__block修飾的變量具有修改能力
//copy 和 scr 的地址指針達到了完美的同一份拷貝,目前只有持有能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//如果有copy能力
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
//Block_byref_2是結構體,__block修飾的可能是對象,對象通過byref_keep保存,在合適的時機進行調用
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;
}
//等價于 __Block_byref_id_object_copy
(*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;
}
首先將傳入的對象,強轉成
Block_byref結構體類型對象;-
判斷傳入的對象是否已經拷貝到堆區(qū):
- 已經拷貝到堆區(qū),處理直接返回;
- 未拷貝到堆區(qū),首先申請內存,然后進行拷貝,再將copy(堆區(qū))與src(棧區(qū))的forwarding指針都指向堆區(qū)的內存地址;
代碼調試:
int main(int argc, const char * argv[])
{
@autoreleasepool{
__block NSString *a = [NSString stringWithFormat:@"li"];
void(^TestBlock)(void) = ^{
NSLog(@" a = %@",a);
};
NSLog(@" TestBlock = %@",TestBlock);
}
return 0;
}
- clang編譯成C++語言如下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_c5_l8bnxw0d2w92f4439t_r8qjc0000gn_T_main_5b35fd_mii_1,(a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[])
{
/* @autoreleasepool */{ __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_c5_l8bnxw0d2w92f4439t_r8qjc0000gn_T_main_5b35fd_mii_0)};
void(*TestBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_c5_l8bnxw0d2w92f4439t_r8qjc0000gn_T_main_5b35fd_mii_2,TestBlock);
}
return 0;
}
看到__block修飾OC對象時,__Block_byref結構體會多生成出兩個函數(shù)分別為
__Block_byref_id_object_copy和__Block_byref_id_object_dispose;調試底層源碼libclosure,首先底層會執(zhí)行
_Block_copy函數(shù),實現(xiàn)將棧區(qū)block拷貝到堆區(qū);

- 當執(zhí)行到
_Block_call_copy_helper函數(shù)時,接下來會調用_Block_object_assign函數(shù),傳入的參數(shù)是__block修飾的局部變量被封裝成Block_byref結構體對象;

- 接下來進入
_Block_byref_copy函數(shù)內部,實現(xiàn)將棧區(qū)的Block_byref結構體對象拷貝到堆區(qū);

- 上面已經提到如果__block修飾的是OC對象,在__Block_byref結構體會多生成出兩個函數(shù)分別為
__Block_byref_id_object_copy和__Block_byref_id_object_dispose;
//block自身拷貝(_Block_copy)
// __block bref結構體拷貝(_Block_object_assign) //_Block_object_assign中對外部變量(存儲在bref)拷貝一份到內存
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);
}
- if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) 這個判斷條件就與是否生成COPY_DISPOSE兩個函數(shù)有關,如果生成了就會執(zhí)行這兩個函數(shù)的相關邏輯;
- 從COPY_DISPOSE兩個函數(shù)實現(xiàn)我們看到,dst是__Block_byref結構體,根據(jù)結構體成員布局(dst + 40)就是外界局部變量a;所以COPY_DISPOSE這兩個函數(shù)執(zhí)行的是外界局部變量a的拷貝與釋放;
總結:
__block修飾OC對象存在三層copy:
- block對象的copy,調用
_Block_copy函數(shù)實現(xiàn)將棧區(qū)block拷貝到堆區(qū); - 外界局部變量生成C++結構體
Block_byref對象的copy,調用_Block_object_assign-->_Block_byref_copy實現(xiàn)將Block_byref結構體對象從棧區(qū)拷貝到堆區(qū); - 只針對OC對象類,即在結構體
Block_byref中生成COPY_DISPOSE函數(shù),將外界局部變量從棧區(qū)拷貝到堆區(qū);
_Block_object_dispose
- 同一般的retain和release一樣,_Block_object_assign其本質主要是retain,所以對應的還有一個release,即_Block_object_dispose方法,其源碼實現(xiàn)如下,也是通過區(qū)分block種類,進行不同釋放操作;
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents 當Blocks或Block_byrefs持有對象時,其銷毀助手例程將調用此入口點以幫助處置內容
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://__block修飾的變量,即bref類型的
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK://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;
}
}
- 進入_Block_byref_release源碼,主要就是對象、變量的釋放銷毀;
static void _Block_byref_release(const void *arg) {
//對象強轉為Block_byref類型結構體
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) {//是否有拷貝輔助函數(shù)
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);//銷毀拷貝對象
}
free(byref);//釋放
}
}
}
Block的內存管理
- 棧區(qū)block,訪問外界局部變量,不會對局部變量產生強引用;
- 堆區(qū)block,會調用block內部的copy函數(shù),copy函數(shù)內部會調用
_Block_object_assign函數(shù),此函數(shù)會根據(jù)局部變量的修飾符(__strong,__weak,__unsafe_unretained)做出相應的操作, - 堆區(qū)block從堆區(qū)移除時,會調用block內部的dispose函數(shù),dispose函數(shù)內部會調用
_Block_object_dispose函數(shù),其會自動釋放引用的局部變量,相當于release局部變量;

- 棧區(qū)block拷貝到堆區(qū)時,__block修飾的變量也會從棧區(qū)復制到堆區(qū),并且堆區(qū)block會對__block變量進行引用;