Block
的使用在OC编程中,我们经常会使用到
block
。它可以作为成员变量、函数参数、函数返回值等等。接下来,我们先来看下block
的一些常用方式。
- (void)blockTest1 { //无参数无返回值 void(^printBlock)(void) = ^ { NSLog(@"print block"); }; printBlock(); //有参数无返回值 void(^combineBlock)(NSString *str, NSNumber *num) = ^(NSString *str, NSNumber *num) { NSString *combineStr = [NSString stringWithFormat:@"%@-%@", str, num]; NSLog(@"combine block: %@", combineStr); }; combineBlock(@"str", @1); //有参数有返回值 int(^caculateBlock)(int value1, int value2) = ^(int value1, int value2) { return value1 + value2; }; int sum = caculateBlock(1000, 24); NSLog(@"caculate block: %d", sum); //定义Block typedef NSString*(^ConvertBlock)(int value); ConvertBlock block = ^(int value) { return [NSString stringWithFormat:@"convert-%d", value]; }; NSString *convertStr = block(1); NSLog(@"convert block: %@", convertStr); } 复制代码
根据上面的例子,可以看出Block
的定义为:返回类型 (^Block名称) (参数类型)
- (void)blockTest2 { [self doSomethingWithCompletion:^(BOOL success) { NSLog(@"do something finished with %@", (success ? @"YES" : @"NO")); }]; } - (void)doSomethingWithCompletion:(void(^)(BOOL success))completion { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //模拟耗时 if (completion) { completion(YES); } }); } 复制代码
通常在进行一些异步操作的时候,我们都会使用block
作为函数参数来回调结果。
- (void)blockTest3 { NSString *(^convertBlock)(void) = [self createBlockWithValue:1]; NSString *convertStr = convertBlock(); NSLog(@"convert block: %@", convertStr); } - (NSString *(^)(void))createBlockWithValue:(int)value { return ^{ return [NSString stringWithFormat:@"str-%d", value]; }; } 复制代码
通常将block
作为函数返回值处理的场景会比较少,不过后面讲到的链式调用就会通过该形式实现。
@interface BlockViewController : UIViewController @property (nonatomic, strong) void(^blockTest)(NSString *result); @end @implementation BlockViewController - (void)viewDidLoad { [super viewDidLoad]; if (self.blockTest) { self.blockTest(@"block result"); } } @end BlockViewController *viewController = [[BlockViewController alloc] init]; viewController.blockTest = ^(NSString * _Nonnull result) { NSLog(@"block result: %@", result); //通知外层 }; [self.navigationController pushViewController:viewController animated:YES]; 复制代码
可以通过设置成员变量为block
来通知外部调用者,从而达成两者数据的传递。
__block
修饰符- (void)blockTest4 { int value1 = 1; void (^onlyreadBlock)(void) = ^ { NSLog(@"only read block: %d", value1); }; onlyreadBlock(); __block int value2 = 1; void(^processBlock)(void) = ^ { value2 = 2; NSLog(@"process block: %d", value2); }; processBlock(); } 复制代码
当需要在block内修改局部变量时,需要通过__block
修饰符定义,否则只能读取,不能修改。
__weak
和__strong
修饰符- (void)blockTest5 { __weak typeof(self) weakSelf = self; [self doSomethingWithCompletion:^(BOOL success) { [weakSelf doSecondThing]; }]; [self doSomethingWithCompletion:^(BOOL success) { __strong typeof(self) strongSelf = weakSelf; if (strongSelf) { [strongSelf doSecondThing]; [strongSelf doThirdThing]; } }]; } - (void)doSecondThing { NSLog(@"do second thing"); } - (void)doThirdThing { NSLog(@"do third thing"); } 复制代码
为了避免循环引用,通常在Block
内会将self
转换为weakSelf
,但为什么有时候还需要使用strongSelf
呢?比如第一个block中只使用weakSelf
定义,而第二个block却额外使用了__strongSelf
。
其实,这里主要是无法确定block内weakSelf
何时会被释放掉,对第一个block,若weakSelf
被释放了,则不会调用doSecondThing
方法,这样通常并不会导致什么错误发生。但对于第二个block,如果还是继续沿用weakSelf
,假设weakSelf
在执行完doSecondThing
后被释放了,那么就会导致doThirdThing
方法不会被调用,意味着只执行了一个方法!这样势必是很容易引发出一些不可预见的情况。
因此,为了保证代码执行的"完整性",block内使用__strong
修饰符,这样在weakSelf
未被释放的情况下进入block后,block
在被执行完前都不会被释放。具体分析可以看这篇文章。
Block
的内存类型在我们一般的开发工作中,往往很少会涉及到
Block
的内存类型相关。因为在ARC下,编译器帮我们自动处理了关于block
内存相关的操作。不过总有些情况,需要我们对Block
的内存概念有所了解,不然会导致一些错误的发生,比如下面的这个例子。
- (void)viewDidLoad { [super viewDidLoad]; NSArray *blocks = [self blockArray]; typedef void(^blk)(void); for (NSInteger index = 0; index < blocks.count; index ++) { blk block = blocks[index]; if (block) { block(); } NSLog(@"blk-%ld: %@", index, block); } } - (NSArray *)blockArray { int val = 10; return [[NSArray alloc] initWithObjects:^(){NSLog(@"blk0: %d", val);}, ^(){NSLog(@"blk1: %d", val);}, ^(){NSLog(@"blk2: %d", val);}, nil]; } 复制代码
上面的例子,其实就是通过blockArray
方法返回一个包含3个block
的数组,然后遍历该数组,分别调用block
对象。
运行程序,会发现程序crash了,并提示:
可以看到当访问第二个block
时,导致程序crash,显示EXC_BAD_ACCESS
。通常该错误是访问了野指针,但这里获取到的blocks
为何会出现野指针,而且访问第一个block
的时候是正常的?
可以先从blocks
的debug信息下手:
debug信息显示第一个block
为_NSMallocBlock_
类型,再探究之前,这里需要了解清楚Block
的内存类型了。我们再看另一个例子:
- (void)blockTest { void(^globalBlock)(void) = ^ { NSLog(@"global block"); }; int value = 1; void(^stackBlock)(void) = ^ { NSLog(@"stack block: %d", value); }; void(^mallocBlock)(void) = [stackBlock copy]; NSArray *blocks = [[NSArray alloc] initWithObjects:globalBlock, stackBlock, mallocBlock, nil]; for (id blk in blocks) { NSLog(@"blk: %@", blk); } } 复制代码
在运行前,需要在Build Prases -> Compile Sources中找到对应的文件,然后添加-fno-objc-arc
,即对该文件禁用ARC功能。因为ARC会自动对Block
进行一些额外内存处理操作。运行后,可以看到结果如下:
blk: <__NSGlobalBlock__: 0x10ac45758> blk: <__NSStackBlock__: 0x7ffee4fe4ea8> blk: <__NSMallocBlock__: 0x600001d3daa0> 复制代码
可见,上面的三种block
分别对应Block
的三种不同内存类型。
__NSGlobalBlock__
: 存储在数据段(一般用来存储全局变量、静态变量等,直到程序结束时才会回收)中,block
内未访问了局部变量
__NSStackBlock__
: 存储在栈(一般用来存储局部变量,自动分配内存,当局部变量作用域执行完后会被立即回收)中,block
内访问了局部变量
__NSMallocBlock__
: 存储在堆(alloc出来的对象,由开发者进行管理)中,当__NSStackBlock__
进行copy
操作时
清楚Block
的内存类型后,再将文件中-fno-objc-arc
去掉,看看ARC下,结果又是如何?
ARC下结果: blk: <__NSGlobalBlock__: 0x1087bc758> blk: <__NSMallocBlock__: 0x600001505a10> blk: <__NSMallocBlock__: 0x600001505a10> 复制代码
根据结果,得知第二个stackBlock
也是__NSMallocBlock__
类型,这是因为在ARC
下,当对栈block
进行赋值操作时,编译器会自动将其拷贝到堆中。
再回到第一个例子,就能解释为什么会出现野指针问题了:因为blockArray
中创建的block
访问了局部变量,为__NSStackBlock__
类型,而这里并没有对block
进行赋值或者copy
,所以ARC下也不会将其拷贝到堆中。因此,当blockArray
作用域结束时,__NSStackBlock__
类型的block也会被自动释放,而导致后面访问的block
为野指针。
- (NSArray *)blockArray { int val = 10; id block0 = ^(){NSLog(@"blk0: %d", val);}; id block1 = ^(){NSLog(@"blk1: %d", val);}; id block2 = ^(){NSLog(@"blk2: %d", val);}; return [[NSArray alloc] initWithObjects:[^(){NSLog(@"blk0: %d", val);} copy], [^(){NSLog(@"blk1: %d", val);} copy], [^(){NSLog(@"blk2: %d", val);} copy], nil]; //方式二 //return [[NSArray alloc] initWithObjects:^(){NSLog(@"blk0: %d", val);}, ^(){NSLog(@"blk1: %d", val);}, ^(){NSLog(@"blk2: %d", val);}, nil]; //方式二 } 复制代码
如上,使用赋值或copy
操作都可以使得block
修改为__NSMallocBlock__
类型,从而能正常访问。
这里还有一个问题就是,为什么第一个例子中第一个Block
是__NSMallocBlock__
?笔者这里猜测应该是initWithObjects:
方法内部会保留第一个对象,即会导致第一个Block
会被赋值,所以会被拷贝到堆中。
Block
的本质我们知道在OC中,大部分情况下都是跟
NSObject
对象打交道的,而Block
似乎是与NSObject
不太一样的一种存在形式。那么Block
的本质到底是怎么样呢,接下来将通过一些例子来进行探究。
a. 先在main.m
文件中定义一个block
,并调用它
int main(int argc, char * argv[]) { NSString * appDelegateClassName; @autoreleasepool { appDelegateClassName = NSStringFromClass([AppDelegate class]); void(^block)(void) = ^ { NSLog(@"hello world"); }; block(); } return UIApplicationMain(argc, argv, nil, appDelegateClassName); } 复制代码
b. 然后使用clang插件对main.m
改写为cpp
文件
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m 复制代码
生成对应的main.cpp
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; 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)}; 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; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_5w_hqdc9zg163j32btftjdkh1kw0000gp_T_main_25fc50_mi_0); } int main(int argc, char * argv[]) { NSString * appDelegateClassName; /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))); //1. block创建 void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); //2. 调用block ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return UIApplicationMain(argc, argv, __null, appDelegateClassName); } 复制代码
这里可以分为Block
的创建和调用两步来看,代码这样看起来可能会比较难理解,这里对这两个步骤整理为如下:
// 1-1:创建__main_block_impl_0对象 __main_block_impl_0 block_impl = __main_block_impl_0((void *) __main_block_func_0, &__main_block_desc_0_DATA); // 1-2: 将__main_block_impl_0强制转换为函数指针 void(*block)(void) = (void(*)()) &block_impl; // 2-1:定义另一个函数指针,并将__main_block_impl_0中的FuncPtr转换为定义的函数指针 void(*block_call)(__block_impl *) = (void(*)(__block_impl *)) ((__block_impl *)block)->FuncPtr; // 2-2: 调用函数指针 block_call((__block_impl *)block); 复制代码
如上所示,block
的定义实际上就是创建了一个__main_block_impl_0
的对象,而block
的调用,即调用对象的函数指针FuncPtr
。
接下来,我们来看下__main_block_impl_0
相关信息,这里对比着Block_layout来看:
//main.cpp struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; 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)}; 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_private.h struct Block_layout { void *isa; volatile int32_t flags; // contains ref count int32_t reserved; BlockInvokeFunction invoke; struct Block_descriptor_1 *descriptor; // imported variables }; struct Block_descriptor_1 { uintptr_t reserved; uintptr_t size; }; typedef void(*BlockInvokeFunction)(void *, ...); 复制代码
可以看到生成的__main_block_desc_0
和Block_layout
的结构是一致的。
NSObject
的内部结构的话,应该清楚,它一般是指向其父类Block
的一些信息,后面会讲解到#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 }; 复制代码
仔细查看Block_private.h
文件时,会发现有三种Block_descriptor_x
结构体,而Block_layout
只包含了Block_descriptor_1
,是否其他两种就没用到呢?我们通过例子来验证下:
int main(int argc, char * argv[]) { NSString * appDelegateClassName; @autoreleasepool { appDelegateClassName = NSStringFromClass([AppDelegate class]); __block int value = 1; void (^block)(void) = ^{ value = 2; //修改__block变量 NSLog(@"update value: %d", value); }; block(); } return UIApplicationMain(argc, argv, nil, appDelegateClassName); } 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}; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_value_0 *value; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : value(_value->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; 复制代码
如上所示,block
修改了__block
修饰符的变量,重写为c++
代码后,对比之前的__main_block_desc_0
,新增了copy
和dispose
两个函数指针。而这两个函数指针刚好与Block_descriptor_2
的变量对应。由此,可以看出Block_layout
会根据具体情况决定是否添加Block_descriptor_2
或Block_descriptor_3
。
总结:通过上面的探究,得知:
Block
的本质为一个Block_layout
的结构体,也包含了isa
指针,因此,也可以称为OC对象。
__Block
修饰符原理我们知道通常要在
Block
内修改局部变量,必须得使用__block
修饰局部变量。而对于成员变量和全局变量则直接修改即可。接下来我们将通过例子来说明__block
是如何实现支持局部变量的修改,以及为何成员变量和全局变量不需要使用__block
修饰。
- (void)_blockTest { NSInteger readonlyValue = 1; void(^readonlyBlock)(void) = ^{ NSLog(@"readonly variable : %ld", readonlyValue); }; readonlyBlock(); void(^memberVariableBlock)(void) = ^{ self.value = 1; NSLog(@"member variable :%ld", self.value); }; memberVariableBlock(); static NSInteger globalValue = 0; void(^globalVariableBlock)(void) = ^{ globalValue = 1; NSLog(@"global variable :%ld", globalValue); }; globalVariableBlock(); __block NSInteger localValue = 0; void(^localVariableBlock)(void) = ^{ localValue = 1; NSLog(@"local variable :%ld", localValue); }; localVariableBlock(); } 复制代码
同样,我们先通过clang插件将其重写为cpp
文件:
static void _I_BlockTypeViewController__blockTest(BlockTypeViewController * self, SEL _cmd) { //1.仅访问局部变量 NSInteger readonlyValue = 1; void(*readonlyBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_0((void *)__BlockTypeViewController___blockTest_block_func_0, &__BlockTypeViewController___blockTest_block_desc_0_DATA, readonlyValue)); ((void (*)(__block_impl *))((__block_impl *)readonlyBlock)->FuncPtr)((__block_impl *)readonlyBlock); //2.修改成员变量 void(*memberVariableBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_1((void *)__BlockTypeViewController___blockTest_block_func_1, &__BlockTypeViewController___blockTest_block_desc_1_DATA, self, 570425344)); ((void (*)(__block_impl *))((__block_impl *)memberVariableBlock)->FuncPtr)((__block_impl *)memberVariableBlock); //3.修改全局变量 static NSInteger globalValue = 0; void(*globalVariableBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_2((void *)__BlockTypeViewController___blockTest_block_func_2, &__BlockTypeViewController___blockTest_block_desc_2_DATA, &globalValue)); ((void (*)(__block_impl *))((__block_impl *)globalVariableBlock)->FuncPtr)((__block_impl *)globalVariableBlock); //4.修改局部变量 __attribute__((__blocks__(byref))) __Block_byref_localValue_0 localValue = {(void*)0,(__Block_byref_localValue_0 *)&localValue, 0, sizeof(__Block_byref_localValue_0), 0}; void(*localVariableBlock)(void) = ((void (*)())&__BlockTypeViewController___blockTest_block_impl_3((void *)__BlockTypeViewController___blockTest_block_func_3, &__BlockTypeViewController___blockTest_block_desc_3_DATA, (__Block_byref_localValue_0 *)&localValue, 570425344)); ((void (*)(__block_impl *))((__block_impl *)localVariableBlock)->FuncPtr)((__block_impl *)localVariableBlock); } 复制代码
__block_impl_
结构体,可以看到这里把访问的局部变量的值拷贝到结构体中了,在函数调用中,是直接访问结构体中对应的值。因此,这种方式无法修改外部局部变量的值!struct __BlockTypeViewController___blockTest_block_impl_0 { struct __block_impl impl; struct __BlockTypeViewController___blockTest_block_desc_0* Desc; NSInteger readonlyValue; //拷贝的值 __BlockTypeViewController___blockTest_block_impl_0(void *fp, struct __BlockTypeViewController___blockTest_block_desc_0 *desc, NSInteger _readonlyValue, int flags=0) : readonlyValue(_readonlyValue) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __BlockTypeViewController___blockTest_block_func_0(struct __BlockTypeViewController___blockTest_block_impl_0 *__cself) { NSInteger readonlyValue = __cself->readonlyValue; // bound by copy .... } 复制代码
__block_impl_
结构体,可以看到这里将BlockTypeViewController
保存到结构体中,函数调用修改成员变量时,直接通过结构体中保存的self
引用来修改value
变量。struct __BlockTypeViewController___blockTest_block_impl_1 { struct __block_impl impl; struct __BlockTypeViewController___blockTest_block_desc_1* Desc; BlockTypeViewController *self; __BlockTypeViewController___blockTest_block_impl_1(void *fp, struct __BlockTypeViewController___blockTest_block_desc_1 *desc, BlockTypeViewController *_self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __BlockTypeViewController___blockTest_block_func_1(struct __BlockTypeViewController___blockTest_block_impl_1 *__cself) { BlockTypeViewController *self = __cself->self; // bound by copy ((void (*)(id, SEL, NSInteger))(void *)objc_msgSend)((id)self, sel_registerName("setValue:"), (NSInteger)1); .... } 复制代码
__block_impl_
结构体,可以看到结构体保存了globalValue
的引用,函数修改时,直接修改其引用的值。struct __BlockTypeViewController___blockTest_block_impl_2 { struct __block_impl impl; struct __BlockTypeViewController___blockTest_block_desc_2* Desc; NSInteger *globalValue; __BlockTypeViewController___blockTest_block_impl_2(void *fp, struct __BlockTypeViewController___blockTest_block_desc_2 *desc, NSInteger *_globalValue, int flags=0) : globalValue(_globalValue) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __BlockTypeViewController___blockTest_block_func_2(struct __BlockTypeViewController___blockTest_block_impl_2 *__cself) { NSInteger *globalValue = __cself->globalValue; // bound by copy (*globalValue) = 1; ... } 复制代码
__block
修饰符后,会对变量进行引用,并创建一个__Block_byref
结构体。__attribute__((__blocks__(byref))) __Block_byref_localValue_0 localValue = {(void*)0,(__Block_byref_localValue_0 *)&localValue, 0, sizeof(__Block_byref_localValue_0), 0}; struct __Block_byref_localValue_0 { void *__isa; __Block_byref_localValue_0 *__forwarding; int __flags; int __size; NSInteger localValue; }; 复制代码
然后将__Block_byref
对象作为引用传入到__block_impl
结构体中,并在函数修改值时,通过__block_impl
获取到__Block_byref
的引用,再通过__Block_byref
获取到局部变量的引用,从而达到修改局部变量值的目的。
struct __BlockTypeViewController___blockTest_block_impl_3 { struct __block_impl impl; struct __BlockTypeViewController___blockTest_block_desc_3* Desc; __Block_byref_localValue_0 *localValue; // by ref __BlockTypeViewController___blockTest_block_impl_3(void *fp, struct __BlockTypeViewController___blockTest_block_desc_3 *desc, __Block_byref_localValue_0 *_localValue, int flags=0) : localValue(_localValue->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __BlockTypeViewController___blockTest_block_func_3(struct __BlockTypeViewController___blockTest_block_impl_3 *__cself) { __Block_byref_localValue_0 *localValue = __cself->localValue; // bound by ref (localValue->__forwarding->localValue) = 1; ... } 复制代码
总结:全局变量会保存其引用,成员变量会保存
self
指针,从而达到直接修改变量的目的;而局部变量,若未使用__block
修饰,则直接将其值拷贝过去,因此block
内的修改将无法影响到外部的变量。使用__block
修饰后,将会保存变量的引用。
Block
的Copy
和Release
上面谈及到ACR下,对block赋值时,会对block进行
copy
操作,将其从栈中拷贝到堆中。接下来,我们将通过源码分析block的拷贝和释放操作。
_Block_copy
:该操作主要是将栈block拷贝到堆中,称为堆block。enum { //引用计数 BLOCK_DEALLOCATING = (0x0001), // runtime BLOCK_REFCOUNT_MASK = (0xfffe), // runtime //堆block标识 BLOCK_NEEDS_FREE = (1 << 24), // runtime //Block_layout中是否包含Block_descriptor_2的copy BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code BLOCK_IS_GC = (1 << 27), // runtime //全局block标识 BLOCK_IS_GLOBAL = (1 << 28), // compiler BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE //Block_descriptor_3中signature和layout对应标识 BLOCK_HAS_SIGNATURE = (1 << 30), // compiler BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler }; 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; //1. if (aBlock->flags & BLOCK_NEEDS_FREE) { //2. // latches on high latching_incr_int(&aBlock->flags); return aBlock; } else if (aBlock->flags & BLOCK_IS_GLOBAL) { //3. return aBlock; } else { // Its a stack block. Make a copy. //4. 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 // 5. // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1 // 6. _Block_call_copy_helper(result, aBlock); // Set isa last so memory analysis tools see a fully-initialized object. //7. result->isa = _NSConcreteMallocBlock; return result; } } 复制代码
Block_layout
类型,我们知道Block
的本质其实就是Block_layout
结构体。BLOCK_NEEDS_FREE
标识判断block是否在堆中,若是,则直接增加其引用计数即可,不需要再进行拷贝操作。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)) { //引用计数增加2,block的每次增加是以2位单位 return old_value+2; } } } 复制代码
根据BLOCK_IS_GLOBAL
标识判断block是否为全局block,若是,则直接返回。
首先使用malloc
给新的block分配内存空间,然后再通过memmove
方法将block信息拷贝到新的block中。
这里先将block的引用计数设置为0,然后设置标识BLOCK_NEEDS_FREE
,并将引用计数设置为2。
首先通过_Block_descriptor_2
方法获取到Block_layout
中的Block_descriptor_2
对象,然后调用其copy
方法。这里的作用主要是用于使用__block
修饰的变量,会对局部变量进行copy
操作,所以当对block进行copy
时,同时也要对__block
修饰的局部变量进行copy
。
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock) { struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock); if (!desc) return; (*desc->copy)(result, aBlock); // do fixup } 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; } 复制代码
_NSConcreteMallocBlock
。_Block_release
:主要针对__NSMallocBlock__
类型的blockvoid _Block_release(const void *arg) { //1. struct Block_layout *aBlock = (struct Block_layout *)arg; if (!aBlock) return; //2. if (aBlock->flags & BLOCK_IS_GLOBAL) return; if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return; //3. if (latching_decr_int_should_deallocate(&aBlock->flags)) { //4. _Block_call_dispose_helper(aBlock); //5. _Block_destructInstance(aBlock); free(aBlock); } } 复制代码
Block_layout
类型static bool latching_decr_int_should_deallocate(volatile int32_t *where) { while (1) { int32_t old_value = *where; if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) { //引用计数为最大值,则直接返回 return false; // latched high } if ((old_value & BLOCK_REFCOUNT_MASK) == 0) { //引用计数为0,也直接返回 return false; // underflow, latch low } int32_t new_value = old_value - 2; bool result = false; if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) { //引用计数为2的话 new_value = old_value - 1; //设置为1,表明正在释放 result = true; //返回true,需要对block进行释放操作 } if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) { //更新block的flag标识 return result; } } } 复制代码
_block
修饰的局部变量进行释放static void _Block_call_dispose_helper(struct Block_layout *aBlock) { struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock); if (!desc) return; (*desc->dispose)(aBlock); } 复制代码
_Block_destructInstance
默认是空操作的,最后调用free
方法释放内存_Block_object_assign
:该方法主要用于block内对外部变量的处理,针对不同情况下,会对外部变量进行不同的处理。我们可以先看一个例子:- (void)blockAssignTest { NSString *str = @"str"; void(^block1)(void) = ^{ NSLog(@"%@", str); }; block1(); __block NSNumber *value = @1; void(^block2)(void) = ^{ NSLog(@"%@", value); }; block2(); } 复制代码
使用clang插件改写为c++文件后:
static void _I_BlockTypeViewController_blockAssignTest(BlockTypeViewController * self, SEL _cmd) { NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_5w_hqdc9zg163j32btftjdkh1kw0000gp_T_BlockTypeViewController_3fbb00_mi_0; void(*block1)(void) = ((void (*)())&__BlockTypeViewController__blockAssignTest_block_impl_0((void *)__BlockTypeViewController__blockAssignTest_block_func_0, &__BlockTypeViewController__blockAssignTest_block_desc_0_DATA, str, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1); __attribute__((__blocks__(byref))) __Block_byref_value_0 value = {(void*)0,(__Block_byref_value_0 *)&value, 33554432, sizeof(__Block_byref_value_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1)}; void(*block2)(void) = ((void (*)())&__BlockTypeViewController__blockAssignTest_block_impl_1((void *)__BlockTypeViewController__blockAssignTest_block_func_1, &__BlockTypeViewController__blockAssignTest_block_desc_1_DATA, (__Block_byref_value_0 *)&value, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2); } 复制代码
可以看到使用了__block
修饰符的变量会创建一个__Block_byref_value_0
来存储变量,这个在讲解__block
的时候也提及到了。我们先分别对比看下两者的__BlockTypeViewController__blockAssignTest_block_desc_0_DATA
和__BlockTypeViewController__blockAssignTest_block_desc_1_DATA
static struct __BlockTypeViewController__blockAssignTest_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __BlockTypeViewController__blockAssignTest_block_impl_0*, struct __BlockTypeViewController__blockAssignTest_block_impl_0*); void (*dispose)(struct __BlockTypeViewController__blockAssignTest_block_impl_0*); } __BlockTypeViewController__blockAssignTest_block_desc_0_DATA = { 0, sizeof(struct __BlockTypeViewController__blockAssignTest_block_impl_0), __BlockTypeViewController__blockAssignTest_block_copy_0, __BlockTypeViewController__blockAssignTest_block_dispose_0}; static struct __BlockTypeViewController__blockAssignTest_block_desc_1 { size_t reserved; size_t Block_size; void (*copy)(struct __BlockTypeViewController__blockAssignTest_block_impl_1*, struct __BlockTypeViewController__blockAssignTest_block_impl_1*); void (*dispose)(struct __BlockTypeViewController__blockAssignTest_block_impl_1*); } __BlockTypeViewController__blockAssignTest_block_desc_1_DATA = { 0, sizeof(struct __BlockTypeViewController__blockAssignTest_block_impl_1), __BlockTypeViewController__blockAssignTest_block_copy_1, __BlockTypeViewController__blockAssignTest_block_dispose_1}; 复制代码
两者都包含了copy
和dispose
函数指针,这里先看看copy
函数指针对应的函数实现:
static void __BlockTypeViewController__blockAssignTest_block_copy_0(struct __BlockTypeViewController__blockAssignTest_block_impl_0*dst, struct __BlockTypeViewController__blockAssignTest_block_impl_0*src) { _Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __BlockTypeViewController__blockAssignTest_block_copy_1(struct __BlockTypeViewController__blockAssignTest_block_impl_1*dst, struct __BlockTypeViewController__blockAssignTest_block_impl_1*src) { _Block_object_assign((void*)&dst->value, (void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/); } 复制代码
可以看到两者都调用了_Block_object_assign
方法,但两者传入了一个大小不同的flag
。那么接下来我们继续看_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_object_assign
会根据传入的flag
来做不同的处理
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_FIELD_IS_BLOCK = 7, // a block variable BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines. }; 复制代码
这里主要深挖下BLOCK_FIELD_IS_BYREF
的情况,即使用了__block
修饰符的情况,会调用_Block_byref_copy
方法对变量进行拷贝。
static struct Block_byref *_Block_byref_copy(const void *arg) { //1. struct Block_byref *src = (struct Block_byref *)arg; //2. if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) { // src points to stack //3. 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; copy->forwarding = copy; // patch heap copy to point to itself src->forwarding = copy; // patch stack to point to heap copy copy->size = src->size; //4. 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; //5. 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 { //6. // Bitwise copy. // This copy includes Block_byref_3, if any. memmove(copy+1, src+1, src->size - sizeof(*src)); } } // 7. // 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
修饰的变量会被封装成一个__Block_byref_value_0
结构体,而这个结构体和Block_byref
是一致的。所以这里将参数强制转换为Block_byref
结构体。struct __Block_byref_value_0 { void *__isa; __Block_byref_value_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSNumber *value; }; 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_byref_2
和Block_byref_3
会根据不同情况,编译器将其添加到Block_byref
中,这一点跟Block_layout
是一致的。
malloc
方法将变量拷贝到堆中,并将设置相关信息,这里要注意flags
标识的设置copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
这里先设置其为BLOCK_BYREF_NEEDS_FREE
,表明存在堆中,然后将其引用计数设置为4,其中调用者和栈各自持有。- (void(^)(void))blockTest1 { __block NSNumber *value = @1; void(^block)(void) = ^{ NSLog(@"%@", value); }; return block; } - (void)blockTest2 { void(^blk)(void) = [self blockTest1]; blk(); } 复制代码
如上,对于blockTest1
方法中__block
修饰的value
变量,引用计数分别由blockTest1
所处栈和blockTest2
调用方持有。
Block_byref
中包含Block_byref_2
,则需要取出Block_byref_2
,然后分别赋值给copy
对象Block_byref
中包含Block_byref_3
,则需要取出Block_byref_3
,然后赋值layout
指针Block_byref
中不包含Block_byref_2
,则直接使用memmove
方法拷贝。flags
中含有BLOCK_BYREF_NEEDS_FREE
,表明已经存在堆中了,则只需要增加其引用计数即可。_Block_object_dispose
:对block持有的外部变量的释放操作,与_Block_object_assign
相反。可以回到上个例子的dispose
函数static void __BlockTypeViewController__blockAssignTest_block_dispose_0(struct __BlockTypeViewController__blockAssignTest_block_impl_0*src) { _Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __BlockTypeViewController__blockAssignTest_block_dispose_1(struct __BlockTypeViewController__blockAssignTest_block_impl_1*src) { _Block_object_dispose((void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/); } 复制代码
如上所示,都调用了_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; } } 复制代码
这里主要对BLOCK_FIELD_IS_BYREF
的情况进行分析_Block_byref_release
方法:
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); } } } 复制代码
这里首先根据flags
标识判断对象是否在堆中,若在堆中,然后再根据对象的引用计数判断是否进行销毁,若需要销毁,则判断Block_byref
对象是否包含dispose
指针,若包含,则调用该函数指针销毁,最后调用free
方法销毁Block_byref
。
Objective-C对比起Swift语法会显得更加繁琐些,对于一些连贯性的方法操作,使用普通的方法调用方式,可能会显得很臃肿。这时候就可以考虑使用
Block
来实现OC的链式调用。
以一个简易计算器的实现为例,一开始我们可能会这样实现:
@interface Calculator : NSObject @property (nonatomic, assign, readonly) NSInteger result; - (instancetype)initWithValue:(NSInteger)value; - (void)add:(NSInteger)value; - (void)sub:(NSInteger)value; - (void)multiply:(NSInteger)value; - (void)divide:(NSInteger)value; @end @interface Calculator () @property (nonatomic, assign, readwrite) NSInteger result; @end @implementation Calculator - (instancetype)initWithValue:(NSInteger)value { if (self = [super init]) { self.result = value; } return self; } - (void)add:(NSInteger)value { self.result += value; } - (void)sub:(NSInteger)value { self.result -= value; } - (void)multiply:(NSInteger)value { self.result *= value; } - (void)divide:(NSInteger)value { if (value == 0) { return; } self.result /= value; } @end 复制代码
调用方式:
// (2*3+4-8)/2 Calculator *cal = [[Calculator alloc] initWithValue:2]; [cal multiply:3]; [cal add:4]; [cal sub:8]; [cal divide:2]; 复制代码
明显这样的方式看起来逻辑非常不连贯,如果改成使用链式调用呢?
- (Calculator *(^)(NSInteger))add; - (Calculator *(^)(NSInteger))sub; - (Calculator *(^)(NSInteger))multiply; - (Calculator *(^)(NSInteger))divide; - (Calculator * _Nonnull (^)(NSInteger))add { return ^(NSInteger value) { self.result += value; return self; }; } - (Calculator * _Nonnull (^)(NSInteger))sub { return ^(NSInteger value) { self.result -= value; return self; }; } - (Calculator * _Nonnull (^)(NSInteger))multiply { return ^(NSInteger value) { self.result *= value; return self; }; } - (Calculator * _Nonnull (^)(NSInteger))divide { return ^(NSInteger value){ self.result /= value; return self; }; } //调用方式: Calculator *cal = [[Calculator alloc] initWithValue:2]; cal.multiply(3).add(4).sub(8).divide(2); 复制代码
如上所示,使用链式调用的方式,明显更能使代码更简洁,更连贯。而实现这一方式的本质是返回一个返回值为自身的Block对象。
当然,其实在平时使用的一些第三方库里,也经常能见到链式调用的影子,比如Masonry
:
__weak typeof(self) weakSelf = self; [self.blockView mas_remakeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(50).height.mas_equalTo(100); make.left.mas_equalTo(weakSelf.view.mas_left).offset(20).top.mas_equalTo(weakSelf.view.mas_top).offset(100); }]; 复制代码
Block
我们知道
Block
的格式为返回类型(^名称)(参数类型),通常我们定义的时候,一般都是会定义固定个数的参数,比如void(^blk1)(NSString*)
或void(^blk2)(NSString*, NSNumber*)
,但有些场景下,为了实现Block
的通用性,会考虑使用不定参数Block
。其格式为void(^blk)()
,即参数列表设置为空。
- (void)variableBlockTest { typedef void(^CommonBlock)(); void(^nonParameterBlock)(void) = ^{ NSLog(@"no parameter"); }; void(^oneParameterBlock)(NSString *) = ^(NSString *param){ NSLog(@"parameter: %@", param); }; void(^twoParameterBlock)(NSString *, NSNumber *) = ^(NSString *param1, NSNumber *param2) { NSLog(@"parameter: %@, %@", param1, param2); }; CommonBlock blk1 = nonParameterBlock; CommonBlock blk2 = oneParameterBlock; CommonBlock blk3 = twoParameterBlock; blk1(); blk2(@"str"); blk3(@"str", @2); } 复制代码
如上所示,我们可以对不定参数CommonBlock
传入任意参数调用,但前提是需要保证CommonBlock
的内部结构参数个数与传入参数个数是一致的,否则会出错,比如blk1(@"str")
这样调用是不允许的,因为blk1
本质为nonParameterBlock
,参数个数为0。这里我们只能通过名称来确定CommonBlock
的具体参数个数,但有些情况下,就无法这么确定了。
- (void)doSomethingWithCommonBlock:(CommonBlock)blk { //无法确定blk参数个数 } 复制代码
当然你也可以增加一个参数,再调用的时候将blk
的参数个数传递过去:
[self doSomethingWithCommonBlock:nonParameterBlock withArgumentsNum:0]; [self doSomethingWithCommonBlock:oneParameterBlock withArgumentsNum:1]; [self doSomethingWithCommonBlock:twoParameterBlock withArgumentsNum:2]; - (void)doSomethingWithCommonBlock:(CommonBlock)blk withArgumentsNum:(NSInteger)num{ } 复制代码
这样的处理方式显然会比较”难看“,有没什么更优雅的处理方式呢?其实,根据上面的探究,我们知道Block
的本质就是一个Block_layout
结构体。在编译过程中,clang插件也会将Block
转换为对应的结构体,具体转换规则,可以看这里
struct Block_literal_1 { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor_1 { unsigned long int reserved; // NULL unsigned long int size; // sizeof(struct Block_literal_1) // optional helper functions void (*copy_helper)(void *dst, void *src); // IFF (1<<25) void (*dispose_helper)(void *src); // IFF (1<<25) // required ABI.2010.3.16 const char *signature; // IFF (1<<30) } *descriptor; // imported variables }; 复制代码
我们可以看到Block_literal_1
中有个signature
变量,即函数签名,如果能获取到该变量,则可以通过NSMethodSignature
获取到对应的numberOfArguments
,即参数个数。这里将通过对PromiseKit获取Block
函数签名的方式来分析。
struct PMKBlockLiteral { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct block_descriptor { unsigned long int reserved; // NULL unsigned long int size; // sizeof(struct Block_literal_1) // optional helper functions void (*copy_helper)(void *dst, void *src); // IFF (1<<25) void (*dispose_helper)(void *src); // IFF (1<<25) // required ABI.2010.3.16 const char *signature; // IFF (1<<30) } *descriptor; // imported variables }; typedef NS_OPTIONS(NSUInteger, PMKBlockDescriptionFlags) { PMKBlockDescriptionFlagsHasCopyDispose = (1 << 25), PMKBlockDescriptionFlagsHasCtor = (1 << 26), // helpers have C++ code PMKBlockDescriptionFlagsIsGlobal = (1 << 28), PMKBlockDescriptionFlagsHasStret = (1 << 29), // IFF BLOCK_HAS_SIGNATURE PMKBlockDescriptionFlagsHasSignature = (1 << 30) }; static NSMethodSignature *NSMethodSignatureForBlock(id block) { if (!block) return nil; //1. struct PMKBlockLiteral *blockRef = (__bridge struct PMKBlockLiteral *)block; //2. PMKBlockDescriptionFlags flags = (PMKBlockDescriptionFlags)blockRef->flags; //3 if (flags & PMKBlockDescriptionFlagsHasSignature) { void *signatureLocation = blockRef->descriptor; signatureLocation += sizeof(unsigned long int); signatureLocation += sizeof(unsigned long int); //4. if (flags & PMKBlockDescriptionFlagsHasCopyDispose) { signatureLocation += sizeof(void(*)(void *dst, void *src)); signatureLocation += sizeof(void (*)(void *src)); } //5. const char *signature = (*(const char **)signatureLocation); return [NSMethodSignature signatureWithObjCTypes:signature]; } return 0; } 复制代码
PMKBlockLiteral
结构体,以便获取到对应的信息;flags
标识,用于判断是否包含函数签名;block_descriptor
的signature
;copy_helper
和dispose_helper
指针,所以也要通过flags
标识来判断,从而确定是否要移动指针位置;signature
转换为NSMethodSignature
。- (void)doSomethingWithCommonBlock:(CommonBlock)blk { NSMethodSignature *signature = NSMethodSignatureForBlock(blk); if (!signature) { return; } NSInteger arguments = signature.numberOfArguments-1; if (arguments == 0) { blk(); } else if (arguments == 1) { blk(@"str"); } else if (arguments == 2) { blk(@"str", @2); } } 复制代码
这样我们就不用额外传入block对应的参数个数了,这里要注意一点的是block的参数个数是signature.numberOfArguments-1
。因为NSMethodSignature
的numberOfArguments
是包含self
和_cmd
这两个参数的,所以对应方法真正的参数个数应该减2处理,而block不含_cmd
,所以减1即可。
不定参数Block在实际应用中还有很多场景,比较典型的就是上面介绍的PromiseKit,利用不定参数block来实现then
操作的通用性,当然不定参数Block也有一个比较明显的缺点:无法提供代码提示,需要自己手动去写参数。对于这个缺点,其实也可以通过自定义代码块处理。
笔者学习完PromiseKit和promises后,针对两者优缺点,也造了一个关于promise
概念的轮子JPromise。