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的声明。
- 首先声明
void (^paramraw)(void)
; - 将参数的
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_0
的a
字段,并将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文件的整体流程大致如下:
- 定义变量
int a = 8372
- 声明一个函数指针
block
, 该函数指针的参数以及返回值类型与定义的block
类型相同。 - 创建
__main_block_impl_0
的实例,通过它的构造函数,将__main_block_func_0
的地址赋值给了字段impl.FuncPtr
,将__main_block_desc_0_DATA
的地址赋值给了字段Desc
,同时,将我们外部声明的变量a
赋值给了字段a
,这里是值传递,所以内部无法对外部的变量a
进行修改。另外,该结构体的形参flags
带有默认参数0
,所以在main
函数中未看到其参数传递的过程。 - 通过前3步,已经准备好了block执行所需要的环境。最后一步,经过一系列的类型转换取出
block->FunPtr
并执行。 - 第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探究一的主要内容,如果未能解决你的问题,请参考以下文章
TensorFlow_Fold深度探究 Blocks for Composition