block探究一

Posted daydreamsan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了block探究一相关的知识,希望对你有一定的参考价值。

简介

block是Apple对c语言实现的一种扩展,本文主要针对ARC模式下的block实现进行探究.

声明方式

可以按照定义函数指针的方式进行block的定义,然后将解引用操作符*替换为^即可。按照有无返回值以及是否存在参数,将block的声明分为以下三种类型。同时,根据block所处的位置不同(property、方法参数、方法返回值以及临时变量),block的声明形式也不太一样。

无参数且无返回值

1 - (void)func {
2     void (^raw)(void);
3 }

这里声明了一个临时变量raw,类型为:void (^)(void)。可以这样进行语言描述:声明了一个block变量raw,该变量无形参且无返回值。
这种形式最简单,系统专门做了两个typedef来简化这种写法:

1 typedef void (^dispatch_block_t)(void);//常用;
2 typedef void (^os_block_t)(void);//不常用;

接下来的几种block形式都可以在此声明形式的基础上扩展出来,如下所示:

无参数但有返回值

1 - (void)func {
2     int (^raw)(void);
3 }

有参数但无返回值

1 - (void)func {
2     void (^raw)(int);
3 }

有参数且有返回值

1 - (void)func {
2     int (^raw)(int);
3 }

以上声明方式可以称为简单block声明方式,在开发过程中,或者阅读第三方源码时经常再到一些复杂的声明,这举几个例子:

block的参数为block
1 - (void)func {
2     void (^paramraw)(void (^)(char));
3 }

该paramraw接收一个void (^)(char)型的参数,且无返回值。直接声明这种类型的block的技巧性很强。这里,我们从void (^)(char)类型入手,一步步完成该paramraw的声明。

  1. 首先声明void (^paramraw)(void)
  2. 将参数的void替换为void (^)(char)类型即可,此时便得到了上述声明。
block的返回值为block
1 - (void)func {
2     int (^(^returnraw)(long))(char);
3 }

这里的returnraw接受一个long型的参数,且返回一个block(记作rblock),rblock接受一个char型的形参并且返回一个int类型的值。这里依然基于void (^returnraw)(long)来做调整。

  • 首先将返回类型void替换为^,得到^(^returnraw)(long),此时即表明了returnraw返回的是一个block;
  • 紧接着,基于^(^returnraw)(long),我们为其添加参数,得到(^(^returnraw)(long))(char);
  • 最后,我将为^(^returnraw)(long)添加返回值类型,得到int (^(^returnraw)(long))(char);
一个更复杂的block声明
1 - (void)func {
2     unichar (^(^(^complexraw)(int (^(^paramblock)(char))(size_t)))(NSEdgeInsets))(CGRect) = ^(int (^(^pb)(char))(size_t st)) {
3         unichar (^(^tmp)(NSEdgeInsets))(CGRect);
4         return tmp;
5     };
6 }

complexraw的定义看起来较为复杂,其实实现也很简单,按照上面的例子依次对void (^complexraw)(void)的参数项及返回值项作替换即可。

优雅的写法:

block的声明方式有时候的确让人不知所措,就连Apple自家的工程师也在工程代码中写注释称:

// I use the following typedef to keep myself sane in the face of the wacky Objective-C block syntax.
typedef void (^ChallengeCompletionHandler (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * credential);

平时开发过程中,对于复杂些的类型声明,建议全部使用typedef进行类型命名。避免出现上一个示例中的情况

变量捕捉

使用block时,若在block内部使用到了外部声明的变量,那么此时便会发现变量捕获。根据block是否修改了变量值,我们将其分为只读变量读写变量两种类型

只读变量

block内部对外部变量只有读操作,没有写操作,此时,在block的底层实现上,只会将一个参数值传递block结构体的构造函数。

1 int main(int argc, char const *argv[]) {
2   int a = 8372;
3   void (^block)(void) = ^(void){
4     printf("hello world: %d
", a);
5   };
6   block();
7   return 0;
8 }

该示例中,block内部对变量a只有读操作。通过-rewrite-objc获取的.cpp源文件中,我们可以看到如下代码:

1 int main(int argc, char const *argv[])
2 {
3   int a = 8372;
4   void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
5   ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
6   return 0;
7 }

这里比较重要的有__main_block_impl_0__main_block_func_0__block_impl,另外,我们可以看到block在这里被重写成了函数指针。

__main_block_impl_0说明
 1 struct __main_block_impl_0 {
 2   struct __block_impl impl;
 3   struct __main_block_desc_0* Desc;
 4   int a;
 5   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
 6     impl.isa = &_NSConcreteStackBlock;
 7     impl.Flags = flags;
 8     impl.FuncPtr = fp;
 9     Desc = desc;
10   }
11 };

从这里我们知道__main_block_impl_0是一个结构体类型。这里可以发现int a字段,同时在结构体的构造函数中,也通过初始化参数列表的形式将_a赋值给字段a。同时为impl.FuncPtr赋值了函数指针,在这block执行的时候会进行调用。

__main_block_func_0说明
1 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
2   int a = __cself->a; // bound by copy
3   printf("hello world: %d
", a);
4 }

该静态方法可以看作是block的执行体的实现。这里首先获取取了__main_block_impl_0a字段,并将a进行了输出

__block_impl说明
1 struct __block_impl {
2   void *isa;
3   int Flags;
4   int Reserved;
5   void *FuncPtr;
6 };

这里将该结构体看作是block的具体实现,其中isa指针,这也表明了block中对象类型。另外,FuncPter指向的是block的执行体,该示例中,其指向__main_block_func_0.

总结

重写的cpp文件的整体流程大致如下:

  1. 定义变量 int a = 8372
  2. 声明一个函数指针 block, 该函数指针的参数以及返回值类型与定义的block类型相同。
  3. 创建__main_block_impl_0的实例,通过它的构造函数,将__main_block_func_0的地址赋值给了字段impl.FuncPtr,将__main_block_desc_0_DATA的地址赋值给了字段Desc,同时,将我们外部声明的变量a赋值给了字段a,这里是值传递,所以内部无法对外部的变量a进行修改。另外,该结构体的形参flags带有默认参数0,所以在main函数中未看到其参数传递的过程。
  4. 通过前3步,已经准备好了block执行所需要的环境。最后一步,经过一系列的类型转换取出block->FunPtr并执行。
  5. 第3、4步中涉及到大量的类型的转换,这里也需要具备结构体的内存布局相关的知识。

读写变量

在block内部写外部定义的变量时,需要对变量添加__block修饰符,面试的过程中,可能会问到__block的实现原理,这里只需要将其转换后的cpp源码理解了即可。
示例如下:

 1 int main(int argc, char const *argv[])
 2 {
 3   __block int a = 132;
 4   printf("1a的地址: %p
", &a);
 5   void (^block)(void) = ^(void){
 6     printf("0a的地址: %p
", &a);
 7     a = 10;
 8     printf("hello world: %d
", a);
 9   };
10   block();
11   printf("2a的地址: %p
", &a);
12   return 0;
13 }

 

这里,打印出了不同阶段变量a的地址。同样的,通过重写,得到的cpp中的关键代码如下:

1 int main(int argc, char const *argv[])
2 {
3   __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 132};
4   printf("1a的地址: %p
", &(a.__forwarding->a));
5   void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
6   ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
7   printf("2a的地址: %p
", &(a.__forwarding->a));
8   return 0;
9 }

相比于只读操作,这里多了一个__Block_byref_a_0,并且__main_block_impl_0的构造函数有及内部字段均发生了改变。关键的部分在于主函数将变量a包装成了一个__Block_byref_a_0结构体,并且将a的地址以及保存起来,这样,在修改变量a的时候,实际会通过__Block_byref_a_0指针找到该内存区域,之后再取出其中的a将其修改。

内存管理

这里的内存管理主要是指循环引用导致的内存泄漏问题。当block有引用对象自身时(包括对象自身的属性字段),不论是只读还是读写,都会捕获对象,这样就形成了循环引用。一般情况下,我们会通过__weak typeof(self) _weakSelf = self;来创建一个基于self的弱引用,然后在block内部通过_weakSelf做一些额外的操作。另外,为保证在block执行期间内_weakSelf不被销毁,我们也可以在block内部通过__strong typeof(_weakSelf) _strongSelf = _weakSelf;来强引用self
事实上,某些block内部完全可以大胆的使用self,如下面的情况:

 1 @class QTCalculator;
 2 typedef QTCalculator *(^CalculatorMaker)(int);
 3 @interface QTCalculator : NSObject
 4 
 5 @property (nonatomic, assign, readonly) int result;
 6 
 7 - (CalculatorMaker)add;
 8 
 9 @end
10 
11 @implementation QTCalculator
12 
13 - (CalculatorMaker)add {
14     return ^(int x) {
15         self.result += x;
16         return self;
17     };
18 }
19 
20 @end

该类的实现部分在block中直接引用了self,但并不会造成循环引用。这是因为add并没有被类QTCalculator的实例所引用,这样就不会造成实例中引用block,同时block中引用实例的现象。类似的还有+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion;等系统API,这里的block在执行完成之后会被销毁,所以不会造成循环引用。

如果无法分辨什么情况下使用__weak typeof(self) ws = self;,那么就粗暴的全部以这种方式实施就好。

链式编程

通过block可以方便的实现链式编程。这里通过一个计算器的示例来说明。

  1 @class QTCalculator;
  2 typedef QTCalculator *(^CalculatorMaker)(float);
  3 @interface QTCalculator : NSObject
  4 
  5 @property (nonatomic, copy  ) CalculatorMaker add;
  6 @property (nonatomic, copy  ) CalculatorMaker subtract;
  7 @property (nonatomic, copy  ) CalculatorMaker multiply;
  8 @property (nonatomic, copy  ) CalculatorMaker division;
  9 @property (nonatomic, assign, readonly) float result;
 10 
 11 @end
 12 
 13 @interface QTCalculator ()
 14 
 15 @property (nonatomic, assign) float result;
 16 
 17 @end
 18 
 19 @implementation QTCalculator
 20 
 21 - (void)dealloc {
 22     NSLog(@"dealloc...");
 23 }
 24 
 25 - (instancetype)initWithResult:(int)r {
 26     self = [super init];
 27     self.result = r;
 28     return self;
 29 }
 30 
 31 - (CalculatorMaker)add {
 32     if (_add) {
 33         return _add;
 34     }
 35     __weak typeof(self) ws = self;
 36     _add = ^(float x) {
 37         __strong typeof(ws) ss = ws;
 38         ss.result += x;
 39         return ss;
 40     };
 41     return _add;
 42 }
 43 
 44 - (CalculatorMaker)subtract {
 45     if (_subtract) {
 46         return _subtract;
 47     }
 48     __weak typeof(self) ws = self;
 49     _subtract = ^(float x) {
 50         __strong typeof(ws) ss = ws;
 51         ss.result -= x;
 52         return ss;
 53     };
 54     return _subtract;
 55 }
 56 
 57 - (CalculatorMaker)multiply {
 58     if (_multiply) {
 59         return _multiply;
 60     }
 61     __weak typeof(self) ws = self;
 62     _multiply = ^(float x) {
 63         __strong typeof(ws) ss = ws;
 64         ss.result *= x;
 65         return ss;
 66     };
 67     return _multiply;
 68 }
 69 
 70 - (CalculatorMaker)division {
 71     if (_division) {
 72         return _division;
 73     }
 74     __weak typeof(self) ws = self;
 75     _division = ^(float x) {
 76         __strong typeof(ws) ss = ws;
 77         ss.result /= x;
 78         return ss;
 79     };
 80     return _division;
 81 }
 82 
 83 @end
 84 
 85 //main.c中
 86 int main(int argc, const char * argv[]) {
 87     int status = 0;
 88     @autoreleasepool {
 89         QTCalculator *calculator = QTCalculator.new;
 90         float result = calculator
 91         .add(4)
 92         .add(5)
 93         .multiply(2)
 94         .division(3)
 95         .subtract(2)
 96         .result;
 97         NSLog(@"result: %f", result);
 98     }
 99     return status;
100 }

 





以上是关于block探究一的主要内容,如果未能解决你的问题,请参考以下文章

iOS底层原理 - Block本质探究

block探究一

深入探究Block

TensorFlow_Fold深度探究 Blocks for Composition

对display主要属性的探究,以及vertical-aligin

浮生半日:探究Python字节码