作为一个iOS开发程序员,没用过block是不可能的。这次我探讨的是block原理,但是有些更深层次的东西,我也不是很清楚,以后随着更加了解block将会慢慢完善。

  第一个问题,什么是block?

  我们都会用block,但是block是什么呢,这是首先要弄清楚的概念。虽然,是什么并不影响我们用它,但是搞清楚原理我们才能更好的去使用它,我觉得作为一个程序员,需要时刻保持对事物原理追究的心态?

  block的是本质是对象。但是你也可以说它是代码块、闭包、内联函数、函数指针...还有很多叫法,也可能这里的叫法都是错误的,或是不准确的,但是我个人觉得从功能上讲,可以这么理解。不过为了对oc的尊敬,还是叫它block吧,block就是block。在其他语言中也有类似block的语法,像javascript的闭包,函数里面的函数,java中的代码块,c中的函数指针等。就好像,事先放一段代码在这里,然后需要的时候回过头来调用。我们知道,代码执行是按顺序调用的,也就是我们常说的面向过程。但是block可以反向调用。只不过block可以写在函数里面,也可以说它是函数中的函数,它是比较特殊的函数。我们看下面的一个例子。

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. int i = ;
  4. void (^block)() = ^(){
  5. NSLog(@"%i", i);
  6. };
  7. block();
  8. }
  9. return ;
  10. }

  block先放在i=0;的后面,但是它没有立即执行,在使用block();后它才执行,这就是block。至于block本质,到后面会讲到。

  第二个问题,为什么要用block?

  虽然知道了什么是block,但是我们为什么要用呢?什么情况下用呢?这个问题其实也很重要,要掌握他的应用场景,我们才能用好它。

  最常用的情况,网络请求。当调用网络的的API请求服务器的数据的时候,我们不知道什么时候才会完成,这个时候程序也不可能停下来等待请求完成再继续运行,这样肯定是不行的。在请求成功的时候,去执行一些操作,这个时候我们需要做到一件事,就是执行事先已经准备好的一段代码。这种情况不正好与block吻合了吗,block不就是来干这件事的吗?有人会说,用代理也可以做到啊,没错,代理也可以实现这种场景需要的操作。但是block它的好处在于简洁,代理以后再谈。

  再举一个例子,你写了一个页面,页面上有按钮可以点击,但是点击事件是用户点了之后才触法的,而你的响应事件想放在控制器中处理(遵循一下MVC设计模式)。这个时候,也可以用block。

  总的来说,当你的程序中需要用到回调的时候,用block会让你思路清晰,代码简洁。

  第三个问题,如何用block?

  1.block的定义:void(^a)()=^(){};

  1. =前面:
  2. void:返回值类型;
  3. ()():语法结构,第一个括号里面是block名字,第二个括号里面是参数列表,和cjava中参数列表写法一样;例如:int i,char c...
  4. ^:脱字符,block标识
  5. a:block名称,就像函数名,对象实例化名称,说白了就是一个名字;
  6. =号后面:
  7. ^是block标识;
  8. ()参数列表,当没有参数时,可以省略,但是“=”前面的()参数列表不能省略;
  9. {}要执行的代码放在这里面,最后加“;”

  最后调用,a();跟方法的调用何其相似。但是它和方法有一个非常明显的区别,方法可以在当前类中(或是在main函数中)都可以调用,但是block不同,它出了它所在的作用域就不能被调用了。就像变量一样,说到这里,你是不是想到了奖block设为全局。没错,下面我们使用一下全局block。

  2.全局block

  1. void(^globeBlock)(int, int);
  2. void max(int, int);
  3. int main(int argc, const char * argv[]) {
  4. @autoreleasepool {
  5. globeBlock = ^(int i, int j){
  6. printf("%d\n", i>j?i:j);
  7. };
  8. max(, );
  9. }
  10. return ;
  11. }
  12. void max(int i, int j){
  13. globeBlock(i,j);
  14. }

  上面的代码中,我们可以看到,声明了一个全局的block,在main函数中实现,在max方法中调用。这样就可以让block在全局中都能调用,但是一定要实现block,否则会报错。全局block原理和变量一样,这里不多说了。有全局block,那么有没有静态block呢(别打我,我也是推测),试了一下,好像没有,也可能我写法不对。

  3.将block用作为对象的属性。

  创建一个类Block,.h文件中:

  1. typedef void (^block)();
  2. @interface Block : NSObject
  3. @property (nonatomic, assign) block myBlock;
  4. - (void)start;

  .m文件中

  1. - (void)start{
  2. if (self.myBlock) {
  3. self.myBlock();
  4. }
  5. }

  在main函数中:

  1. __block int i = ;
  2. Block *block = [Block new];
  3. block.myBlock = ^{
  4. i++;
  5. };
  6. [block start];

  这种写法是比较常用的,但是也会造成一些问题,例如:retain cycle,这个放在后面说。

  4.将block作为变量传递。

  还是用上面的类举例。.h文件中:

  1. - (void)show:(void (^)(NSInteger index))paramBlock;

  .m文件中

  1. - (void)show:(void (^)(NSInteger index))paramBlock{
  2. paramBlock();
  3. }

  main函数中调用:

  1. [block show:^(NSInteger index) {
  2.   NSLog(@"%li", index);
  3. }];

  这样就将block当作变量传递了,一些网络请求库里面会经常用这种方式。

  上面讲到的有关block的概念都是一些比较基础的东西,下面会讲block更深层的理论。

  1.retain cycle,循环引用一直是一个老生常谈的问题,我相信99.9的oc程序员都遇到过,并都能很好的解决,所以我这里也不赘述,简单说一下。

  retain cycle在block中是怎么产生的。自从iOS引进了ARC之后,内存管理变得方便,但同时有些情况让人头疼。当对象A持有对象B的时候,A释放,B会随着A的释放而释放。那么问题来了,如果B也持有对象A,那么A会随着B的释放而释放。很好,现在A、B都在等对方释放,互相伤害啊,结果大家都不释放,这样便造成了循环引用。用weak关键字修饰,或是用其他的方法,都可以解决,这里不多说,没多大意思了。

  2.不会形成retain cycle的block。像UIKit中UIView的block动画它不会形成retain cycle,还有网络库AF中的回调也不会形成retain cycle。没有形成retain cycle说明没有互相强引用,UIView调用的是类方法,当前控制器不可能强引用一个类,所以并没有造成retain cycle;至于AF里面怎么做到的,说实话我也不是很清楚,哈哈,还要研究一下。既然如此,那我们来写写不会造成循环引用的block。

  2.1block里面没有引用当前控制器

  视图控制器是TestViewController,有一个属性testView,类TestView代码如下:

  1. .h
  2. @interface TestView : UIView
  3. {
  4. NSString *str;
  5. }
  6. @property (nonatomic, copy) void(^block)();
  7. @end
  8.  
  9. .m
  10. #import "TestView.h"
  11.  
  12. @implementation TestView
  13. - (void)dealloc{
  14. NSLog(@"TestView 被释放");
  15. }
  16. - (instancetype)initWithFrame:(CGRect)frame{
  17. if (self = [super initWithFrame:frame]) {
  18. self.backgroundColor = [UIColor blackColor];
  19. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(click)];
  20. [self addGestureRecognizer:tap];
  21. }
  22. return self;
  23. }
  24.  
  25. - (void)click{
  26. if (self.block) {
  27. self.block();
  28. }
  29. }
  30.  
  31. @end

  TestViewController代码如下:

  1. - (void)dealloc{
  2. NSLog(@"TestViewController 被释放");
  3. }
  4.  
  5. - (void)viewDidLoad {
  6. [super viewDidLoad];
  7. // Do any additional setup after loading the view.
  8. self.view.backgroundColor = [UIColor whiteColor];
  9. self.testView = [[TestView alloc] initWithFrame:CGRectMake(, , , )];
  10. self.testView.block = ^{
  11. NSLog(@"没有引用self");
  12. };
  13. [self.view addSubview:self.testView];
  14.  
  15. }

  在block中,没有引用self,所以当前控制器didDisAppear之后就销毁了。

  1. -- ::33.556 noRetainBlock[:] TestViewController 被释放
  2. -- ::33.557 noRetainBlock[:] TestView 被释放

  会造成循环引用的情况:

  然后TestViewController和TestView都没有被释放,成功造成retain cycle。用weak修饰一下self就可以了,这里不说了。

  2.2类方法调用,block作为变量,TestView代码如下:

  1. .h
  2. @interface TestView : UIView
  3. @property (nonatomic, copy) void(^block)();
  4. + (void)show:(void(^)())myBlock;
  5. @end
  6.  
  7. .m
  8. #import "TestView.h"
  9.  
  10. @implementation TestView
  11. - (void)dealloc{
  12. NSLog(@"TestView 被释放");
  13. }
  14. - (instancetype)initWithFrame:(CGRect)frame{
  15. if (self = [super initWithFrame:frame]) {
  16. self.backgroundColor = [UIColor blackColor];
  17. UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(click)];
  18. [self addGestureRecognizer:tap];
  19. }
  20. return self;
  21. }
  22. - (void)click{
  23. if (self.block) {
  24. self.block();
  25. }
  26. }
  27. + (void)show:(void (^)())myBlock{
  28. myBlock();
  29. }
  30. @end

  控制器中调用:

  1. - (void)dealloc{
  2. NSLog(@"TestViewController 被释放");
  3. }
  4.  
  5. - (void)viewDidLoad {
  6. [super viewDidLoad];
  7. // Do any additional setup after loading the view.
  8. self.view.backgroundColor = [UIColor whiteColor];
  9. [TestView show:^{
  10. [self show];
  11. }];
  12. }
  13. - (void)show{
  14. NSLog(@"show");
  15. }

  最后,控制器消失之后被释放

  1. -- ::15.482 noRetainBlock[:] show
  2. -- ::17.784 noRetainBlock[:] TestViewController 被释放

  不要问我,为什么testView没有被释放,因为根本就没有创建这个对象,哈哈,被show了吧。还有一种写法我试过了,也会造成retain cycle,将testView不设为控制器的属性,这样不会有警告,但是最终还是会造成循环引用,因为析构函数没有被调用。

  以上两种均可以避开循环引用,不过貌似第一种没啥用。因为不在里面操作self的一些方法貌似这个block没啥意义,说到底也只写了一种。至于还有没有其他的写法,我还没有研究。以后要是发现了会补上的。接下来,我们将看看block到底是什么东西。

  在看block源码之前,我们先回到下面这段代码,仔细看看会发现,i被__block修饰了。

  1. __block int i = ;
  2. Block *block = [Block new];
  3. block.myBlock = ^{
  4. i++;
  5. };
  6. [block start];

  那么不用__block修饰会是什么样子呢?答案是:如果在block中修改局部变量i的值,那么编译器会报错,根本不能通过编译,不过你要打印或是给其他变量赋值还是可以的。既然这样,我们来看看上述写法和下面写法的两种源码,在终端cd到main函数所在文件夹,使用命令 clang -rewrite-objc main.m会得到main.cpp的文件。可以查看源码。

  第一种,不用__block修饰,打印变量i:

  1. int i = ;
  2. void(^block)() = ^{
  3. NSLog(@"%i", i);
  4. };
  5. block();
  6.  
  7. cpp文件中,末尾,因为大概有95000行代码,只取最后部分代码
  8. struct __main_block_impl_0 {
  9. struct __block_impl impl;
  10. struct __main_block_desc_0* Desc;
  11. int i;
  12. __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=) : i(_i) {
  13. impl.isa = &_NSConcreteStackBlock;
  14. impl.Flags = flags;
  15. impl.FuncPtr = fp;
  16. Desc = desc;
  17. }
  18. };
  19. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  20.   int i = __cself->i; // bound by copy
  21.     NSLog((NSString *)&__NSConstantStringImpl__var_folders_s1_pqp17thd18bdxjswsty3xkmh0000gn_T_main_e26a0c_mi_0, i);
  22. }
  23. static struct __main_block_desc_0 {
  24. size_t reserved;
  25. size_t Block_size;
  26. } __main_block_desc_0_DATA = { , sizeof(struct __main_block_impl_0)};
  27. int main(int argc, const char * argv[]) {
  28. /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  29. int i = ;
  30. void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
  31. ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  32. }
  33. return ;
  34. }

  源码中__main_block_impl_0和__main_block_desc_0中的信息告诉我们block的本质了,它有一个isa指针,可以将block看成一个对象,但是不能说它就是一个对象。在上述源码中可以看到,block中将变量i拷贝了一份,也就是说,block外面的i和里面的i是两个变量。就好像实参和行参的关系。行参将实参拷贝了一份,放在内存中。oc做了优化,不能直接改变局部变量i的值。但是用__block修饰变量i之后就可以改变了。这又是为啥呢,看下面的源码(只看main中的):

  1. __block int i = ;
  2. void(^block)() = ^{
  3. i++;
  4. NSLog(@"%i", i);
  5. };
  6. block();
  7.  
  8. int main(int argc, const char * argv[]) {
  9. /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  10. __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*),(__Block_byref_i_0 *)&i, , sizeof(__Block_byref_i_0), };
  11. void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, ));
  12. ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  13. }
  14. return ;
  15. }

  我们可以看到__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};被__block修饰过的变量i变成这样了,然后在block中的i是(__Block_byref_i_0 *)&i,一个地址。原来,用__block修饰过的i传入block里面时,是地址,所以可以改变i的值。

  其实还有一个变量的值是可以在block中修改的,全局变量,静态变量。但是要注意,在改变变量的值的时候要注意循环引用的问题。

  总结:block是一个很有趣的东西,掌握它,弄清楚原理,才能更好的使用它。

iOS之block,一点小心得的更多相关文章

  1. BUI Webapp用于项目中的一点小心得

    接触BUI也有一段时间,也用在了移动端的项目开发中,总的来说,该框架用起来也挺灵活的,控件可以自由定制,前提是自己能认真地学习该框架的api,因为api里面说的东西比较详细,如果没有仔细看的,可能有些 ...

  2. ASP.NET MVC Autofac依赖注入的一点小心得(包含特性注入)

    前言 IOC的重要性 大家都清楚..便利也都知道..新的ASP.NET Core也大量使用了这种手法.. 一直憋着没写ASP.NET Core的文章..还是怕误导大家.. 今天这篇也不是讲Core的 ...

  3. Qt使用com组件的一点小心得(使用Qt自带的工具dumpcpp生成.h和.cpp文件)

    这几天工作中要用到Qt调用com组件,主要用到的类型有dll和ocx,使用他们的方法很简单:1.将com组件注册到系统中.2.使用Qt自带的工具dumpcpp将com组件生成cpp和头文件.3.然后就 ...

  4. python+tesseract验证码识别的一点小心得

    由于公司需要,最近开始学习验证码的识别 我选用的是tesseract-ocr进行识别,据说以前是惠普公司开发的排名前三的,现在开源了.到目前为止已经出到3.0.2了 当然了,前期我们还是需要对验证码进 ...

  5. 学习KMP算法的一点小心得

    KMP算法应用于 在一篇有n个字母的文档中 查找某个想要查找的长度为m的单词:暴力枚举:从文档的前m个字母和单词对比,然后是第2到m+1个,然后是第3到m+2个:这样算法复杂度最坏就达到了O(m*n) ...

  6. 近日使用Taro框架的一点小心得

    1.yarn npm安装的包,跟权限问题有关,与网络也有关 2.Vue框架首先,是解决了view-model的问题,解放开发的双手,使得显示和数据和控制分开 3.当你觉得最近没有技术文章看时,就看收藏 ...

  7. jquery框架一点小心得

    下面的小事例 主要实现了 一和按ID查找,并获取元素的 value 或 标签内容和一个去字符串空格的小功能能 假设元素id=“myid”: 获取标签内容$("myid").html ...

  8. 学习R语言的一点小心得

    1.目前R 语言处于入门阶段吧,能够执行一些简单的模型了,还是有收获的. 但是在跑模型的时候经常遇到各种各样的错误,最常见的错误就是数据带入模型之后,数据的类型不对,因此模型跑不下去,因此说,利用he ...

  9. 使用Vue.js时,对Chrome控制台的一点小心得

    之前对Chrome控制台的console.log()输出没太放心上,其实仔细研究后,对工作效率有显著的提示.看下面的五段代码: console.log(''); console.log(typeof ...

随机推荐

  1. 一.软件介绍(apache lighttpd nginx)

    一.软件介绍(apache  lighttpd  nginx) 1. lighttpd Lighttpd是一个具有非常低的内存开销,cpu占用率低,效能好,以及丰富的模块等特点.lighttpd是众多 ...

  2. Android在非UI线程中更新UI的方法

    1.使用Thread+Handler实现非UI线程更新UI界面 在UI Thread中创建Handler.用sendMessage(message)或者obtainMessage(result, ob ...

  3. NGUI 取ScrollView中遮罩区域4个点

    用panel.localCorners而不是panel.finalClipRegion,Region还要再换算 首先通过ScrollView取panel,然后取Corners,它返回值代表4个点,映射 ...

  4. C#反射取数组单个元素的类型

    去bing上查了一下,果然有和我一样蛋疼的朋友,他们在论坛研究了半天,最后还是暴力解决: public Type GetArrayElementType(Type t) { string tName ...

  5. [elk]logstash&filebeat常用语句

    filebeat安装dashboard 参考: https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-getting-star ...

  6. HTTP 用户认证

    HTTP 常见的用户认证可以分为下面三种: 基于IP,子网的访问控制(ACL) 基本用户验证(Basic Authentication) 消息摘要式身份验证(Digest Authentication ...

  7. QT界面 理解QStyle和QStyleOption以及QStyleFactory

    QStyleOption类和QStyle类简介 QStyleOption类存储QStyle函数使用的参数.QStyleOption及其子类包含了QStyle函数绘制图形元素所需的所有信息. 由于性能原 ...

  8. Hadoop中的RPC机制

    1.  RPC——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据.在OSI ...

  9. 多线程中wait和notify的理解与使用

    1.对于wait()和notify()的理解 对于wait()和notify()的理解,还是要从jdk官方文档中开始,在Object类方法中有: void notify()  Wakes up a s ...

  10. Android Listview 去除边框

    最近在做一个时间轴的功能,因为原生效果的Listview有item分隔边框,所以就需要去除边框,调用listview的setDivider方法就可以了: listView.setDivider(nul ...