Block
的使用在OC编程中,咱们常常会使用到
block
。它能够做为成员变量、函数参数、函数返回值等等。接下来,咱们先来看下block
的一些经常使用方式。html
- (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名称) (参数类型)
c++
- (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
做为函数参数来回调结果。git
- (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
做为函数返回值处理的场景会比较少,不事后面讲到的链式调用就会经过该形式实现。github
@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
来通知外部调用者,从而达成二者数据的传递。objective-c
__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
方法不会被调用,意味着只执行了一个方法!这样势必是很容易引起出一些不可预见的状况。promise
所以,为了保证代码执行的"完整性",block内使用__strong
修饰符,这样在weakSelf
未被释放的状况下进入block后,block
在被执行完前都不会被释放。具体分析能够看这篇文章。bash
Block
的内存类型在咱们通常的开发工做中,每每不多会涉及到
Block
的内存类型相关。由于在ARC下,编译器帮咱们自动处理了关于block
内存相关的操做。不过总有些状况,须要咱们对Block
的内存概念有所了解,否则会致使一些错误的发生,好比下面的这个例子。app
- (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。