使用 Block 的时候谨记以下几点:

1.Block类型:全局块(Global Block)和堆块(Heap Block),以及栈块(Stack Block)。
2.变量捕获: 默认无法修改变量,需要添加 __block 修饰符
3.避免循环引用。

推荐文章:
1.官方文档:
快速上手:Working with Blocks,进阶:Blocks Programming Topics
2.优秀博客:
Deep into Block: A look inside blocks: Episode 1, Episode 2, Episode 3
底层实现:谈 Objective-C Block 的实现 - By 唐巧
类型探讨:Objective-C Blocks Quiz
啥都有:对 Objective-C 中 Block 的追探

类型

之前看文档或是其他人写的文章都讲述了三种 Block 类型,但直到看到上面的测试,我才意识 Block 类型是如何决定的。经过一些实践,简单来说,在 Objective-C 中:
1.Block 中没有使用外围变量的话,在开启 ARC 的条件下,因为不需要依赖其他状态,其使用的内存区域在编译期就可以确定,将会被编译为全局块;而没有开启 ARC 时,总是被编译为栈块。
在唐巧的博客中使用 Clang 来研究 Block ,这里也学习了一下,但是基本上这些块都是被编译为栈块,按照唐巧的说法,在 ARC 下,是被编译为全局块的。

《Effective Objective-C 2.0》给出如下的例子:声明了一个 Block,但需要根据条件来选择合适的实现。

void (^block)();
if(条件 A) {block = ^{NSlog(@"Block A");}
}else{ block = ^{ NSLog(@"Block B"); } } block()

上面这段代码的问题在于,这两个块只在相应的 if 或 else 语句范围内有效,离开了相应的范围后,编译器有可能覆写块所在的内存。这样的代码可以编译,但在运行时可能出错。书中提出,这里可以使用 copy 操作将块拷贝到堆上,这样一来,块可以在定义它的范围外使用。正确写法如下:

void (^block)();
if(条件 A) {block = [^{NSlog(@"Block A");} copy]; }else{ block = [^{ NSLog(@"Block B"); } copy]; } block()

书中没有提及此处是否开启了 ARC;我将上述代码编写在 C 文件中,使用 Clang 将之转化为 cpp 实现后,发现这里是个栈块;而按照上一条的说法,开启 ARC 后,这里将会被编译为全局块。那么在开启 ARC 的条件下,这段代码是否有问题呢?答案是没有,因为原来的代码的问题在于块的内存在栈中,而开启 ARC 下,块编译为全局块,不存在这个问题。
在 Objective-C Blocks Quiz 的 Example C中,提到:

That’s correct. Since the block doesn’t capture any variables in its closure, it doesn’t need any state set up at runtime. it gets compiled as an NSGlobalBlock. It’s neither on the stack nor the heap, but part of the code segment, like any C function. This works both with and without ARC.

2.使用了外围变量的话,若开启了 ARC,则只会被编译为堆块,内存是分配在堆上的;若没有开启 ARC,函数中定义的 Block 将会编译为栈块(如今除了旧项目很少有不开启 ARC 的吧)。

总结下:开启 ARC 的条件下,将不会有栈块,这样可以省去不少麻烦,但是 Objective-C Blocks Quiz 的最后也提到LLVM的一位维护者说:

We consider this to be a compiler bug, and it has been fixed for months in the open-source clang repository. What that means for any hypothetical future Xcode release, I cannot say. :)

保险点,开启 ARC。

生命周期

栈块,顾名思义,离开了定义它的函数范围就被收回了;堆块,就像普通的对象一样采用引用计数机制;全局块,在应用的整个生命周期都存在。

变量捕获 + 循环引用

在声明块的范围内,所有变量都可以被块捕获(就是可以使用)。默认情况下,不可以在块里修改外围的变量,因为块拷贝了一份变量到它的内存中,对于对象则是拷贝了对象的地址;若想修改,需要在外围变量前面添加修饰符 __block。是否添加 __block 修饰符,源代码会有很大不同,可以在唐巧的博客里看到。另外,Block 最著名的问题就是循环引用,就是由于互相保持着对方的引用,所以 ARC 拿这俩没办法,将会一直存在;复杂一点的,多个对象对其他对象的引用形成了一个圈,ARC 也是没办法。一般的解决办法是,在形成的引用圈的一处使用弱引用,这样就有机会打破强引用圈。

常见的循环引用陷阱是在类中定义了块变量,然后在块中使用了类实例的属性。而在访问属性的同时,块实质上隐式地捕获了当前实例,这样一来就造成了循环引用。
在 Objective-C 中,解决方法通常如下:

__weak myClass *weakSelf = self;
self.block = ^{__strong myClass *strongSelf = weakSelf;//防止当前 self 为空 if(strongSelf){ [strongSelf doSomeThing]; } }

又或者使用 typeof,不过这种高级技巧,可读性就不敢保证了。今天这条微博引起了一些讨论:

是否会捕获 self

这个代码的关键在于 typeof(self),因为它在块的内部出现,那么块是否捕获了 self呢?答案是不会,因为 typeof是个编译符号,在编译期间起作用,而不是运行时,因此这么写不会造成循环引用的问题。

在 Swift 中,针对这个问题有了更加优雅的解决方案:捕获列表(Capture List)。在闭包参数前添加列表,从属关系以及捕获对象成对为一组值,多组值用「,」隔开。
无参数:

var someClosure: () -> Void = {[unowned self, weak delegate = self.delegate!] in //closure body }

有参数:

var someClosure: (Int, String) -> String = {[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) in //closure body }

 

转载于:https://www.cnblogs.com/rosee-1224/p/5167879.html

Block(Closure) Tips相关推荐

  1. 追求卓越追求完美规范学习_追求新的黄金比例

    追求卓越追求完美规范学习 The golden ratio is originally a mathematical term. But art, architecture, and design a ...

  2. Python 中 with 用法详解

    浅谈 Python 的 with 语句:https://developer.ibm.com/zh/articles/os-cn-pythonwith/ python3,浅谈with的神奇魔法:http ...

  3. samba文件共享,windows与linux共享

    Samba 是在 Linux 和 UNIX 系统上实现 SMB 协议的一个免费软件,由服务器及客户端程序构成. NFS 与 samba 一样,也是在网络中实现文件共享的一种实现,但不幸的是,其不支持 ...

  4. LINUX学习笔记之mount命令

    depmod modprobe loop lsmod | grep loop loop 19017 0 有时需要在linux下需要访问windows的共享文件夹,可以使用mount挂载或者使用samb ...

  5. 小程序外卖系统(多商家)

    文章目录 获取源码 系统简介 1.实际效果图 2.部分代码 获取源码 https://docs.qq.com/doc/DTHh4THBCbVl0eFBj 系统简介 这是个小程序的多商家外卖系统,适用于 ...

  6. S32K1xx系列MCU的Flash擦除与编程

    List item S32K1xx系列MCU的Flash擦除与编程 (使用S32K1xx SDK Flash驱动API) S32K1xx系列MCU的Flash资源包括P-Flash和Flex NVM, ...

  7. Linux就该这么学36期学员

    今天太忙了,就直接复制书上的内容: 作为一名合格的运维人员,要想更快.更好地了解Linux服务器,必须具备快速查看系统运行状态的能力,因此接下来会逐个讲解与网卡网络.系统内核.系统负载.内存使用情况. ...

  8. 初学者应该掌握的Linux命令

    目录 一.前言 二.强大好用的SHELL 三. 执行命令的必备知识 四.常用系统工作命令 五.系统状态检测命令 六.查找定位文件命令 七.文本文件编辑命令 八.文件目录管理命令 一.前言 首先介绍系统 ...

  9. Deferred Deeplink(延展的深度链接)

    最近在研究Deferred Deeplink,网上查了查资料,在这里整理记录一下,供大家学习参考. 前言 Deferred Deeplink(延展的深度链接) Deeplink有个局限,就是只能在已安 ...

最新文章

  1. linux运行dock打包的镜像,Linux部署之Docker方式部署项目
  2. CoronaSDK 对象锚点探秘(Anchors)
  3. STM32F030控制LED
  4. 重构智能合约(上):非确定性的幽灵
  5. 互联网1分钟 | 0124 抖音社交产品“多闪”登顶苹果商店总排行榜;王欣或将推出新社交产品丸子视频...
  6. springboot Field userInfoInter in com.**.** required a bean of type ‘***.**‘
  7. OpenGL——使用Bresenham算法绘制圆
  8. 网络与并行计算机,并行计算机系统结构网络版 白中英,杨旭东编著.pdf
  9. AIR切换SDK版本时遇到的问题
  10. 如何在 Mac 上设置自定义锁屏信息?
  11. android图像与动画处理,在Android和iPhone上对照片进行动画处理的7种最佳应用 | MOS86...
  12. 如何把pdf文件转换为excel表格
  13. SharePoint CAML In Action——Part II
  14. 【OMNeT++】ALOHA协议仿真中的channelUtilization
  15. 有道翻译 翻译功能的功能实现
  16. 降噪无线耳机推荐,热销火爆的降噪蓝牙耳机分享
  17. Linux学习:第一天_笔记
  18. 直播系统定制开发中安卓直播间websocket协议破解还原
  19. 如何将VSCode添加到鼠标右键菜单
  20. 通过继承实现圆柱体面积体积的计算

热门文章

  1. 驱动级的自动按键_空调遥控器特殊按键使用方法及注意事项
  2. 汇编 cmp_汇编复习
  3. 【OpenGL从入门到精通(七)】OpenGL中的数学
  4. 【音视频安卓开发 (二)】
  5. jsp mysql 插入数据_jsp连接MySQL实现插入insert操作功能示例
  6. python爬虫xpath教程_使用 Xpath 进行爬虫开发
  7. mysql索引下沉_MySQL 5.6 索引条件下推优化
  8. qgis 图片_QGIS入门教程公告!!!
  9. 几何画板200个经典课件_项目制学科联动 | 金芬娥首席工作室:灵动“画板”,研修创新,协同进步...
  10. VGA光端机技术原理及应用领域介绍