对Block的一些理解

2020/5/21 23:26:45

本文主要是介绍对Block的一些理解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、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

可以看到当访问第二个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_0Block_layout的结构是一致的。

  • isa指针:了解过NSObject的内部结构的话,应该清楚,它一般是指向其父类
  • flags:用于判断Block的一些信息,后面会讲解到
  • reserved:保留信息
  • invoke:block调用对应的函数指针
  • descriptor: 用于存储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,新增了copydispose两个函数指针。而这两个函数指针刚好与Block_descriptor_2的变量对应。由此,可以看出Block_layout会根据具体情况决定是否添加Block_descriptor_2Block_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);

}
复制代码
  1. 访问局部变量:查看__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
    ....
}
复制代码
  1. 修改成员变量:查看__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);
   ....
}
复制代码
  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;
  ...
}
复制代码
  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修饰后,将会保存变量的引用。

五、BlockCopyRelease

上面谈及到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;
    }
}
复制代码
  1. 将参数转换为Block_layout类型,我们知道Block的本质其实就是Block_layout结构体。
  2. 根据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;
        }
    }
}
复制代码
  1. 根据BLOCK_IS_GLOBAL标识判断block是否为全局block,若是,则直接返回。

  2. 首先使用malloc给新的block分配内存空间,然后再通过memmove方法将block信息拷贝到新的block中。

  3. 这里先将block的引用计数设置为0,然后设置标识BLOCK_NEEDS_FREE,并将引用计数设置为2。

  4. 首先通过_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;
}
复制代码
  1. 最后将block设置为_NSConcreteMallocBlock
  • _Block_release:主要针对__NSMallocBlock__类型的block
void _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);
    }
}
复制代码
  1. 将参数转换为Block_layout类型
  2. 若block为全局block或栈block,则直接返回
  3. 根据引用计数判断是否需要对block进行释放
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;
        }
    }
}
复制代码
  1. 这里其实就是对应上面的第6步操作,对_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);
}
复制代码
  1. _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};
复制代码

两者都包含了copydispose函数指针,这里先看看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;
}
复制代码
  1. 在我们上面改写的cpp文件中可以看到,对于__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_2Block_byref_3会根据不同情况,编译器将其添加到Block_byref中,这一点跟Block_layout是一致的。

  1. 判断当前引用计数是否为0,若为0,则需要进行拷贝操作;
  2. 调用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调用方持有。

  1. Block_byref中包含Block_byref_2,则需要取出Block_byref_2,然后分别赋值给copy对象
  2. Block_byref中包含Block_byref_3,则需要取出Block_byref_3,然后赋值layout指针
  3. Block_byref中不包含Block_byref_2,则直接使用memmove方法拷贝。
  4. 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

六、OC中的链式调用

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;
}
复制代码
  1. 首先将block强制转换为PMKBlockLiteral结构体,以便获取到对应的信息;
  2. 获取到block的flags标识,用于判断是否包含函数签名;
  3. 若包含函数签名,则需要通过指针移动到block_descriptorsignature;
  4. 我们知道Block并不是一定包含copy_helperdispose_helper指针,所以也要通过flags标识来判断,从而确定是否要移动指针位置;
  5. 最后将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。因为NSMethodSignaturenumberOfArguments是包含self_cmd这两个参数的,所以对应方法真正的参数个数应该减2处理,而block不含_cmd,所以减1即可。

不定参数Block在实际应用中还有很多场景,比较典型的就是上面介绍的PromiseKit,利用不定参数block来实现then操作的通用性,当然不定参数Block也有一个比较明显的缺点:无法提供代码提示,需要自己手动去写参数。对于这个缺点,其实也可以通过自定义代码块处理。

笔者学习完PromiseKit和promises后,针对两者优缺点,也造了一个关于promise概念的轮子JPromise。

参考资料

  • A look inside blocks: Episode 3 (Block_copy)
  • I finally figured out weakSelf and strongSelf


这篇关于对Block的一些理解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程