标签:\n interface and 调整 定义变量 内存 convert 替换 argc
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声明方式,在开发过程中,或者阅读第三方源码时经常再到一些复杂的声明,这举几个例子:
1 - (void)func { 2 void (^paramraw)(void (^)(char)); 3 }
该paramraw接收一个void (^)(char)型的参数,且无返回值。直接声明这种类型的block的技巧性很强。这里,我们从void (^)(char)类型入手,一步步完成该paramraw的声明。
void (^paramraw)(void);void替换为void (^)(char)类型即可,此时便得到了上述声明。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);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\n", 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\n", 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 = 8372block, 该函数指针的参数以及返回值类型与定义的block类型相同。__main_block_impl_0的实例,通过它的构造函数,将__main_block_func_0的地址赋值给了字段impl.FuncPtr,将__main_block_desc_0_DATA的地址赋值给了字段Desc,同时,将我们外部声明的变量a赋值给了字段a,这里是值传递,所以内部无法对外部的变量a进行修改。另外,该结构体的形参flags带有默认参数0,所以在main函数中未看到其参数传递的过程。block->FunPtr并执行。在block内部写外部定义的变量时,需要对变量添加__block修饰符,面试的过程中,可能会问到__block的实现原理,这里只需要将其转换后的cpp源码理解了即可。
示例如下:
1 int main(int argc, char const *argv[]) 2 { 3 __block int a = 132; 4 printf("1a的地址: %p\n", &a); 5 void (^block)(void) = ^(void){ 6 printf("0a的地址: %p\n", &a); 7 a = 10; 8 printf("hello world: %d\n", a); 9 }; 10 block(); 11 printf("2a的地址: %p\n", &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\n", &(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\n", &(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 }
标签:\n interface and 调整 定义变量 内存 convert 替换 argc
原文地址:https://www.cnblogs.com/daydreamsan/p/10415993.html