在我刚刚接触iOS开发的时候,是通过MJ老师讲的OC基础入门的,iOS圈的人应该基本都知道MJ大神吧,即便如此大神,讲解完block之后我依然感觉晕晕乎乎的,直到后来真正进公司做项目,依然感觉这是自己的一个弱项,后来通过不断接触,对它可能有了更多的了解,但是不一定够全面够深入,现在准备通过自己看过的几篇觉得还不错的文章,系统的来总结一下block的使用。不多废话,下面开始:
1、我在平时读他人文章的时候对block常见的描述是匿名函数,再多一些描述就是可以在方法内部使用,也可以在方法外部使用,还能做参数使用。下面看一下它的简单定义方式和使用:
1 int x = 8; 2 - (void)blockTest10 { 3 4 int (^myBlock)(int) = ^(int b){ 5 x = 5; 6 return x + b; 7 }; 8 int result = myBlock(3); 9 NSLog(@"%d", result); //8 10 } 11 12 - (void)blockTest9 { 13 static int a = 8; 14 int (^myBlock)(int) = ^(int b){ 15 a = 5; 16 return a + b; 17 }; 18 int result = myBlock(3); 19 NSLog(@"%d", result); //4 20 } 21 22 23 - (void)blockTest8 { 24 static int a = 8; 25 int (^myBlock)(int) = ^(int b){ 26 return a + b; 27 }; 28 a = 5; 29 int result = myBlock(3); 30 NSLog(@"%d", result); //8 31 } 32 - (void)blockTest7 { 33 __block int a = 5; //加上__block前缀,就会传址进去 34 int (^myBlock)(int) = ^(int b){ 35 // a = 2; //编译不再报错 36 return a + b; 37 }; 38 a = 7; 39 int result = myBlock(3); 40 //上面 a = 2 注释的情况下打印出的是10,解注释的情况下打印出的是5 41 NSLog(@"%d", result); 42 43 } 44 45 - (void)blockTest6 { 46 NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil]; 47 int result = ^(int a){ 48 [mutableArray removeLastObject]; //这里是传址而不是传值,因此这行代码会移除成功 49 return a * a; 50 }(5); 51 52 NSLog(@"array :%@", mutableArray); 53 NSLog(@"%d",result); //25 54 } 55 56 - (void)blockTest5 { 57 //这段代码你会发现打印值依然未变,这是因为block对a的使用只是a的值的使用,而不是地址的引用,它在内部把a的值5作为常量来使用,因此在外部再改变a的值不会对block内部的a值造成任何影响,并且这时在block内部a是不能改变的,它在这里相当于一个常量。 58 int a = 5; 59 int (^myBlock)(int) = ^(int b){ 60 // a = 2; //编译报错 61 return a + b; 62 }; 63 a = 7; 64 int result = myBlock(3); 65 //依然打印出8 66 NSLog(@"%d", result); 67 68 } 69 70 - (void)blockTest4 { 71 int a = 5; 72 int (^myBlock)(int) = ^(int b){ 73 return a + b; 74 }; 75 76 int result = myBlock(3); 77 //打印出8 78 NSLog(@"%d", result); 79 80 } 81 82 //square参数的类型是int(^)(int) 83 - (void)blockTest3:(int(^)(int))square{ 84 NSLog(@"%d",square(3)); 85 } 86 87 88 - (void)blockTest2 { 89 //声明一个名为square的block,并且返回值和参数都是int型 90 int (^square)(int); 91 //为square赋实体,类似函数内部的执行模块 92 square = ^(int a){ 93 return a * a ; 94 }; 95 //调用block,这里跟C函数调用一模一样 96 int result = square(5); 97 //这里打印值为25 98 NSLog(@"%d", result); 99 } 100 - (void)blockTest1 { 101 //直接使用一个block实体来进行计算 102 //前面说过block某些方面跟函数非常相似,所以在这里 103 //a相当于参数,return的值相当于返回值,5相当于传入的参数 104 int result = ^(int a){ 105 return a * a; 106 }(5); 107 NSLog(@"%d",result); //打印值为25 108 //当然,我们几乎不会这样去使用它 109 }
以上是十种简单的使用情景,具体的说明在注释里,我想应该足够详细了,反正在这里了解一下基本格式和简单使用就好,平时不会直接这样使用,所以不能说不重要,但不属于精髓部分,这些应该是小白都懂的东西。下面是调用:
1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 2 3 // [self blockTest1]; 4 // [self blockTest2]; 5 //上面是block的一些简单定义和使用的方式,下面看一下block作为参数的简单使用 6 // [self blockTest3:^int(int a) { //执行这行代码,会打印出9 7 // return a * a; 8 // }]; 9 //如果上面的示例不太明白的话,可以继续往下看,后面会介绍block做参数在实际开发工作中的具体使用场景。 10 11 //下面看一下block对外部变量的使用 12 // [self blockTest4]; 13 //可以把4和5对比来看 14 // [self blockTest5]; 15 16 //对象引用,传址的情况 17 // [self blockTest6]; 18 19 //如果外部变量不像上面一样是一个对象指针,那该怎么处理呢? 20 // [self blockTest7]; 21 22 //静态变量全局只有一份,所以不管在哪里访问改变的都是a本身 23 // [self blockTest8]; 24 25 // [self blockTest9]; 26 27 //全局变量也一样 28 [self blockTest10]; 29 }
好了,简单的东西简单说,如果你感兴趣还可以去我的github把demo弄下来自己运行看看。这是地址:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/1-block%E7%9A%84%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8
2、好,继续往下进行,再深入一点,下面是较常用的方式了,比较重要,用block在类之间传递信息。
前面已经说过,block某些方面很像函数(但是block是OC对象),所以定义一个block,相当于定义了一个函数,它既可以定义在方法内部也可以定义在方法外部(定义在外部的时候你可以把它看做一个全局变量),而且调用规则和函数一样,调用的时候才会执行block内部的代码;
个人感觉从这里开始才真正进入主题,因为像之前那种做法基本没人会那么用,在同一个控制器内定义和使用block,如果是这种需求那就没有必要用block了,你用C函数或OC方法岂不是更方便?block最大的用处本人认为是在不同的类之间传递消息,类似代理,但是使用起来比代理更加灵活简便,但是对掌握不熟练地人来说也很容易出错,这也会让很多人望而生畏,之前的我也是如此,哈哈。
好了,不BB那么多了,看到这就直接去看另外两个demo吧,代理传值和block传值,这两个已经在我之前的博客控制器之间传值的方式总结中讲过了,但是我打算在把代理和block两个demo拷过来,这样可以方便对比一下,并针对block再多加几句话。
第一步:在需要对外传值的控制器声明一个block。
1 //一般都会用这种方式声明一个block类型(返回值类型为空,参数类型为字符串) 2 typedef void (^TestBlock) (NSString *str);
第二步:声明block类型属性。
1 //一般使用copy策略,因为在ARC环境下已经不再有存储在栈中的block了,而是在堆中。声明一个TestBlock类型的变量 2 @property (nonatomic, copy) TestBlock testBlock;
第三步:在你认为需要传值出去的时机调用block,把你想要传递出去的信息传递出去。
1 //block传值 2 //自己确定需要传递信息的时机,这里是返回上一页的时候传值 3 //用if判断一下是为了安全性,只有block确实存在的时候才会调用,否则会出问题 4 if(self.testBlock) { 5 //调用block成员变量 6 self.testBlock(@"绿色"); 7 }
第四步:调用block的时候会执行block实体,这时候就把消息传过来了。
1 //这里是block回传的值 2 //在这里实现block的实体,并接收调用者传递过来的参数,这就实现了控制器之间传递信息 3 nextVc.testBlock = ^(NSString *str) { 4 NSLog(@"%@",str); 5 };
代理这里就不说咯,自己可以把demo搞下来对比一下,github地址:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/2-block%EF%BC%88%E5%86%8D%E6%B7%B1%E5%85%A5%E4%B8%80%E7%82%B9%EF%BC%89
3、下面进行到第三阶段了,这里着重分析一下block的实现原理,虽然之前我在面试题blog中也简单分析过,但是这里打算放一些更详细一点的,我就直接把唐巧大大的博客链接放到这里了,因为过于底层的东西平时开发是很少会接触到的,但是对于爱刨根问底的我们-程序猿来说,还是很有诱惑力的,所以有兴趣的童鞋可以去看看,没兴趣的就继续往下吧。我在这里只摘抄一些我认为大家都应该知道的一些东西。
1)、闭包
闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。block 实际上就是 Objective-C 语言对于闭包的实现。
2)、在 Objective-C 语言中,一共有 3 种类型的 block:
- _NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
- _NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
- _NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁
3)、NSConcreteMallocBlock 类型的 block 通常不会在源码中直接出现,因为默认它是当一个 block 被 copy 的时候,才会将这个 block 复制到堆中,目标的 block 类型被修改为 _NSConcreteMallocBlock。
4)、在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。
原本的 NSConcreteStackBlock 的 block 会被 NSConcreteMallocBlock 类型的 block 替代。证明方式是以下代码在 XCode 中,会输出<__NSMallocBlock__: 0x100109960>
。在苹果的 官方文档 中也提到,当把栈中的 block 返回时,不需要调用 copy 方法了。
引用文章地址:http://blog.devtang.com/2013/07/28/a-look-inside-blocks/
上面这四条都来自于唐巧的博客,但是后面我还打算补充一点更通俗一点的东西。
5)Block在MRC下的内存管理
block在mrc的情况下,默认是存储在栈中的,因此不需要程序员自己对它做内存管理,即使引用了外部的对象,也不会对该对象的引用计数产生任何影响。
但是,一旦对block进行了copy操作,它便会被移到堆中,这时候便需要对它做内存管理,包括释放以及对它引用对象的释放,因为如果它存在堆中的时候,那么被它引用的对象引用计数会+1,所以这个对象需要释放两次。
1 void(^myBlock)() = ^{ 2 NSLog(@"------"); 3 }; 4 myBlock(); 5 6 Block_copy(myBlock); 7 8 // do something ... 9 10 Block_release(myBlock);
那么如何避免当block存在于堆,又对其他对象做了强引用,并且这个对象又对block产生了强引用的情况(比如block内部使用了self)。
1 void(^myBlock)() = ^{ 2 NSLog(@"------%@",self.view); 3 }; 4 myBlock(); 5 6 Block_copy(myBlock); 7 8 // do something ... 9 10 Block_release(myBlock);
由于对block进行了copy操作,这时候self对block是强引用的,那么block内部又对self做了强引用,就会造成强引用循环,造成内存泄漏。解决办法便是:
1 __block typeof(self) weakSelf = self; 2 void(^myBlock)() = ^{ 3 NSLog(@"------%@",weakSelf.view); 4 }; 5 myBlock(); 6 7 Block_copy(myBlock); 8 9 // do something ... 10 11 Block_release(myBlock);
加入了第一行之后,block便不会再对self做强引用了。
6)Block在ARC下的内存管理
在ARC默认情况下,Block的内存存储在堆中,ARC会自动进行内存管理,程序员只需要避免循环引用即可.
1 __weak typeof(self) weakSelf = self; 2 void(^myBlock)() = ^{ 3 NSLog(@"------%@",weakSelf.view); 4 }; 5 myBlock(); 6 7 Block_copy(myBlock); 8 9 // do something ... 10 11 Block_release(myBlock);
ARC环境下,只需要把__block换成__weak就可以了,还有个什么长长的前缀也可以,但是我忘记了,__weak够了。
为什么在ARC环境下__block不行了呢?因为__block在ARC中并不能禁止block对所引用的对象进行强引用,解决办法可以是在Block中将这个强引用对象置空,但是不推荐这么做。
但是在需要修改外部变量的时候,还是需要使用__block对变量进行修饰才能对变量进行修改的。
同时,在block内部定义的变量,会在作用域结束时自动释放,block对其并没有强引用关系,且在ARC中只需要避免循环引用即可,如果只是block单方面地对外部变量进行强引用,并不会造成内存泄漏。
第三阶段就写这么多,即使写到这里我还是觉得远远没到自己感兴趣的那个程度,只掌握到这个程度的话在实际项目应用方面还是比较菜的,所以这里不多废话了,还有两个小demo,没事的话可以clone下来看一下,或许会有点帮助:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/3-%E7%A8%8D%E7%A8%8D%E6%80%BB%E7%BB%93%E4%B8%80%E4%B8%8B
4、实用篇,写一点实际应用场景。
1、之前提到过,也是最常见的,block做参数。
比如AFN中
1 [[AFHTTPSessionManager manager] POST:@"" parameters:params progress:^(NSProgress * _Nonnull uploadProgress) { 2 } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 3 //do something 4 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 5 6 }]; 7
Masonry中:(请忽略我见不得人的命名)
1 [self.nameLaebl makeConstraints:^(MASConstraintMaker *make) { 2 make.top.equalTo(self.productBtn.top); 3 make.left.equalTo(self.productBtn.right).offset(nameLabelLeftMargin); 4 make.right.equalTo(self.right).offset(nameLabelRightMargin); 5 }];
拿AFN来说吧,AFN对上面方法的实现是这样的:
1 - (AFHTTPRequestOperation *)POST:(NSString *)URLString 2 parameters:(id)parameters 3 success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success 4 failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure 5 { 6 AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure]; 7 8 [self.operationQueue addOperation:operation]; 9 10 return operation; 11 }
继续向下调用:
1 - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success 2 failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure 3 { 4 5 dispatch_async(http_request_operation_processing_queue(), ^{ 6 if (self.error) { 7 if (failure) { 8 dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ 9 failure(self, self.error); 10 }); 11 } 12 } else { 13 id responseObject = self.responseObject; 14 if (self.error) { 15 if (failure) { 16 dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ 17 failure(self, self.error); 18 }); 19 } 20 } else { 21 if (success) { 22 dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{ 23 success(self, responseObject); 24 }); 25 } 26 27 }; 28 }
这里负责把方法中block参数中的参数传递出去,而在我们使用这个方法的时候,便在block中接收它传递出来的结果,最常用的是responseObject,AFN返回的网络数据就存在这个值中。
而在我们使用POST方法的时候,就把success block内实现的代码块和failure block内的代码块层层传递到了AFN内部函数,然后等待网络请求的回应失败或者成功就调用相应的block,最后把获取的结果通过AFN方法中的block参数中的参数传给我们。
总结一下:先在block内部实现一个代码块,然后在合适的时候调用该block并传入参数,就可以实现对该代码块的调用,达到回调的目的。(不过AFN属于提供给别人用的,所以我理解的是这个顺序:你调用它的接口方法的时候,便实现了它block接口的实体,这样它在内部调用这个block的时候便有了实体,便成功调用参数block把block中的参数传递出来,所以block做参数的时候,一般block自己的参数便是传递信息的核心媒介)block就是一个对象,和OC中其他的对象一样,所以可以被当做参数来传递,区别是block是一个匿名函数,所以你可以调用它实现某些功能。
2、block做返回值
这里我写了个demo,直接看demo的代码吧:
先新建一个Person类:
1 #import <Foundation/Foundation.h> 2 3 @interface Person : NSObject 4 5 - (NSString * (^)(NSUInteger))speak; 6 7 - (void (^)(NSUInteger))eat; 8 @end 9 10 11 @implementation Person 12 - (NSString *(^)(NSUInteger))speak { 13 return ^ NSString * (NSUInteger a) { 14 return [NSString stringWithFormat:@"my age is %ld", a]; 15 }; 16 } 17 18 - (void (^)(NSUInteger))eat { 19 return ^(NSUInteger a) { 20 NSLog(@"eat 了 %ld 个鸭梨", a); 21 }; 22 } 23 @end
定义两个公共方法供外部调用,返回值都是block类型。
接下来看一下怎么在控制器中使用:
1 Person *p = [[Person alloc] init]; 2 3 // NSLog(@"%@",[p speak](5)); 4 // 5 // [p eat](6); 6 7 //其实上面这种做法是没有必要的,因为如果你真的想实现这种功能的话不需要多走一层block,但是这里是为了使用点语法实现链式调用,所以应该是下面这么用的 8 NSLog(@"%@",p.speak(5)); 9 10 p.eat(6); 11 12 //是不是感觉有点像Masonry中的链式语法,点语法调用函数,就是调用该函数的的getter方法
OK,下面会讲到链式语法。上面代码的demo:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/4-%E5%AE%9E%E7%94%A8%E7%AF%87/block%E5%81%9A%E8%BF%94%E5%9B%9E%E5%80%BC
3、block链式语法
先看一个场景:
1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { 2 NSUInteger a = 1,b = 2,c = 3,d = 4,e = 5; 3 NSUInteger result = 0; 4 result = [self add:a b:b]; 5 result = [self add:result b:c]; 6 result = [self add:result b:d]; 7 result = [self add:result b:e]; 8 NSLog(@"%ld",result); 9 } 10 11 - (NSUInteger)add:(NSUInteger)a b:(NSUInteger)b { 12 return a + b; 13 }
假设定义一个简单的计算两个整数相加之和的方法,当我们需要计算多个数之和的时候,便需要不断的调用,但是链式语法便要简单的多:(这里的举例可能不太合适,因为谁都知道计算两数之和没必要单独写个方法,这里只是为了说明链式语法的优点)
1 NSUInteger result = [NSObject makeCalculate:^(CalculateManager *mgr) { 2 mgr.add(a).add(b).add(c).add(d).add(e); 3 }];
好了,下面就来看一下具体的实现过程是怎么样的:
先新建一个计算管理类:
1 @interface ATCalcManager : NSObject 2 @property (nonatomic, assign) NSUInteger result; 3 4 - (ATCalcManager *(^)(NSUInteger s))add; 5 6 7 @implementation ATCalcManager 8 - (ATCalcManager *(^)(NSUInteger s))add { 9 return ^ATCalcManager *(NSUInteger x) { 10 self.result += x; 11 return self; 12 }; 13 } 14 @end
再新建一个NSObject的分类:
1 @class ATCalcManager; 2 @interface NSObject (ATCalc) 3 + (NSUInteger)makeCalc:(void(^)(ATCalcManager *mgr))block; 4 @end 5 6 7 8 @implementation NSObject (ATCalc) 9 + (NSUInteger)makeCalc:(void(^)(ATCalcManager *mgr))block { 10 ATCalcManager *mgr = [[ATCalcManager alloc] init]; 11 block(mgr); //计算 12 return mgr.result; 13 }
在控制器中使用:
1 NSUInteger a = 1,b = 2,c = 3,d = 4,e = 5; 2 NSUInteger result = 0; 3 4 result = [NSObject makeCalc:^(ATCalcManager *mgr) { 5 mgr.add(a).add(b).add(c).add(d).add(e); 6 }]; 7 NSLog(@"%ld",result); //15
总结:1、先看控制器中的这行调用
1 result = [NSObject makeCalc:^(ATCalcManager *mgr) { 2 mgr.add(a).add(b).add(c).add(d).add(e); 3 }];
这个方法的参数是一个block,我们在这里定义这个block的参数实体,也就是我们想实现的链式语法。
2、Command + 鼠标左键进去看这个函数的具体实现,该方法内部初始化一个ATCalcManager实例对象mgr,然后作为block的参数传入block,然后调用该block,回调了我们上一步实现的block实体。
3、接下来执行的链式调用代码mgr.add(a).add(b).add(c).add(d).add(e);,可以看到add方法返回的是一个block,该block的实现是累加传递进来的值然后赋值给属性result保存下来,然后返回值是self,也就是ATCalcManager实例对象。这样又可以实现点语法继续调用add方法,最后return mgr.result;返回计算结果。
最后,实现链式调用的一个关键点:就是每次调用add方法必须返回自身,然后才可以继续调用,如此一致循环下去,实现这一切都是block的功劳。
4、block实现函数式编程
先看个示例:
1 [[[[mgr calculate:^NSUInteger(NSUInteger result) { 2 result += 1; 3 return result; 4 }] printResult:^(NSUInteger result) { 5 NSLog(@"第一次计算结果为:%ld",result); 6 }] calculate:^NSUInteger(NSUInteger result) { 7 result -= 2; 8 return result; 9 }] printResult:^(NSUInteger result) { 10 NSLog(@"第二次计算结果为:%ld",result); 11 }];
计算和打印循环套用,逻辑过程清晰的连在一起,而且不需要中间变量。
下面看如何实现:
新建一个ATCalcManager类
1 @interface ATCalcManager : NSObject 2 @property (nonatomic, assign) NSUInteger result; 3 - (instancetype)calculate:(NSUInteger(^)(NSUInteger result))calculateBlock; 4 -(instancetype)printResult:(void(^)(NSUInteger result))printBlock; 5 @end 6 7 8 @implementation ATCalcManager 9 - (instancetype)calculate:(NSUInteger (^)(NSUInteger result))calculateBlock 10 { 11 _result = calculateBlock(_result); 12 return self; 13 } 14 15 -(instancetype)printResult:(void(^)(NSUInteger result))printBlock{ 16 printBlock(_result); 17 return self; 18 } 19 @end
和链式编程一样,上面两个函数的关键点仍然在于每次都必须返回self,这样才可以继续嵌套调用其他函数。函数的内部实现是做一些内部处理,然后传入参数来调用block。
5、block保存代码块
首先说一下回调的概念,直接从大神那里拿过来了,简单易懂的例子:你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。
然后看一下具体场景中的应用,一个很常见的需求:
在tableview的每行cell上都有一个按钮,你需要在这个按钮被点击的时候处理这个动作,但是这个动作显然不适合在view中解决,这时就需要借助block回调来传递事件。
1、首先在cell视图中定义一个block属性
1 @property(copy, nonatomic) void (^callBack)(ATModel *);
2、在cell的实现文件中监听按钮点击
1 [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside]; 2 3 //... 4 5 - (void)btnClick:(UIButton *)btn { 6 if (self.callBack) { 7 self.callBack(self.model); 8 } 9 }
3、在cellForRowAtIndexPath方法中为block赋实体
1 cell.callBack = ^(ATModel *model) { 2 NSLog(@"%ld---%@",indexPath.row,model.name); 3 };
这样你想要的点击rowIndex和对应的model都很容易的获取到了。
如果还不明白,直接运行一下demo,看一下输出就明白了。
总结:
做个总结吧,block有时候理解起来确实不那么顺,老感觉有点别扭有点绕,但是它也不过是个普通的OC对象而已,用习惯了就好了。最后推荐一个计算并缓存cellHeigth的小分类工具代码,很惭愧非原创,是我从大牛哪里学来的,做了一点微不足道的改动自己就用上了,哈哈,不过感觉里面的block使用的很值得看看,毕竟是实实在在在实际项目中应用的,而不是简单的demo简单介绍一下的就能比拟效果。代码在这里,有兴趣可以看看:https://github.com/alan12138/Tools/tree/master/rowHeightTest