深度围观block:第三集

发布于:2013-07-12 10:09阅读数:7804

本文是深度围观block的第三篇文章,也是最后一篇。希望读者阅读了之后,对block有更加深入的理解,同时也希望之前对汇编语言恐惧或者陌生的读者转变看法,其实只要你用心去看,去学,很

“”

 

本文由破船译自galloway!

小引

本文是深度围观block的第三篇文章,也是最后一篇。希望读者阅读了之后,对block有更加深入的理解,同时也希望之前对汇编语言恐惧或者陌生的读者转变看法,其实只要你用心去看,去学,很容易就搞懂的。

  
另外由于block具有闭包性,我们也可以将其当做匿名函数,所以大家如果想要了解更多关于OC中的闭包性和匿名函数就来看看这篇文章吧: Closure and anonymous functions in Objective-C。 
  
目录
介绍
已知内容
Block_copy()
Block_release()
何去何从
  
正文
介绍
本文话费了很长时间才出炉。实际上,几个月之前就已经打好草稿了,只不过一直忙于写我的这本书:Effective Objective-C 2.0,所以没有时间完成本文。 
  
接着之前的两篇文章: 深度围观block:第一集和深度围观block:第二集,本文将更进一步了解当block被拷贝时发生了什么。可能你已经听过这样的说辞“block开始于栈”,以及“如果你希望将block保存下来,以便后续使用,那么必须对block进行拷贝”。那么,这是为什么呢?而在拷贝过程中实际又会发生什么情况?我一直在思考拷贝block时是利用了什么机制。就如之前介绍的block在进行值拷贝时发生了什么。本文我将揭晓这些疑问。 
 
已知内容
通过第一集和第二集两篇文章,我们可以知道block的内存布局如下图所示: 
  
在 第二集中,我们也知道了当block初始化的时候,会在栈中创建像上图这样的一个结构。由于这个结构是在栈上,而在栈空间是会被重复使用的。那么如果我们想要在以后继续使用该block,就必须要对block进行拷贝操作。拷贝操作需要调用 Block_copy()函数,或者可以理解为给block发送一个copy消息(因为block可以看成一个Objective-C对象),这也会调用 Block_copy()函数。 
  
下面我们就来看看Block_copy()函数都做了什么。 
  
Block_copy()
我们首先来看看Block.h文件,在这里面可以看到如下定义: 
  1. #define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
  2. void *_Block_copy(const void *arg);
可以看出,Block_copy()实际上就是一个宏定义(#define),该宏定义将传入的参数(const void *)做强制类型转换,然后再传给_Block_copy()。我们也可以在实现文件runtime.c中找到_Block_copy()的原型: 
  1. void *_Block_copy(const void *arg) {
  2. return _Block_copy_internal(arg, WANTS_ONE);
  3. }
  
上面的方法调用了_Block_copy_internal()函数,并传入block本身(arg)以及WANTS_ONE。要弄白具体意思,需要查看_Block_copy_internal方法的实现,该方法也是在runtime.c文件中。如下代码所示(已经去除掉了一些无关的内容:主要是垃圾回收相关): 
  1. static void *_Block_copy_internal(const void *arg, const int flags) {
  2. struct Block_layout *aBlock;
  3. const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
  4. // 1
  5. if (!arg) return NULL;
  6. // 2
  7. aBlock = (struct Block_layout *)arg;
  8. // 3
  9. if (aBlock->flags & BLOCK_NEEDS_FREE) {
  10. // latches on high
  11. latching_incr_int(&aBlock->flags);
  12. return aBlock;
  13. }
  14. // 4
  15. else if (aBlock->flags & BLOCK_IS_GLOBAL) {
  16. return aBlock;
  17. }
  18. // 5
  19. struct Block_layout *result = malloc(aBlock>descriptor->size);
  20. if (!result) return (void *)0;
  21. // 6
  22. memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
  23. // 7
  24. result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
  25. result->flags |= BLOCK_NEEDS_FREE | 1;
  26. // 8
  27. result->isa = _NSConcreteMallocBlock;
  28. // 9
  29. if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
  30. (*aBlock->descriptor->copy)(result, aBlock); // do fixup
  31. }
  32. return result;
  33. }
  
下面来看看该方法都做了些什么事情: 
1、如果传入的参数是 NULL则直接返回 NULL。这样可以保证传入一个 NULL block时函数的安全性。 
  
2、将参数强制转换为一个指针,该指针指向一个 Block_layout结构对象。实际上在第一集中就介绍了Block_layout结构:这是一个内部使用的数据结构,该结构组成一个block,其中包含一个block的实现函数,以及另外几个元数据。 
  
3、 如果block的flags包含BLOCK_NEEDS_FREE,说明这是一个堆block(a heap block)。这种情况下,需要做的事情就是增加引用计数(reference count),然后将同一个的block返回。 
  
4、如果block是一个全局block(参考第一集),那么不用做任何事情,直接返回同一个block即可——因为全局block是一个单例(singleton)。 
  
5、如果到这一步了,可以肯定该block肯定被分配在栈上。这种情况,需要将block拷贝到堆上。这也是最有趣的一部分。首先是利用malloc()函数在堆上创建block对应size大小的内存空间。如果失败了,就返回 NULL,否则继续往下执行。 
  
6、 利用 memmove()函数将分配在栈中的block按位拷贝至刚刚在堆上分配的空间中。按位拷贝可以确保block中的所有元数据都能准确的进行拷贝,例如block的descriptor。 
  
7、接着需要更新一下block的flags。第一行代码是确保引用计数被设置为0。后面紧跟的注释表示这不是必须的——估计此时引用计数已经是0了。我猜测这行代码的作用是为了防止潜在的bug,会引起引用计数不为0的情况。第二行代码是设置 BLOCK_NEEDS_FREE标志,这标示该block是一个堆block,当引用计数变为0时,需要 free掉。后面紧跟的 | 1是将block的引用计数设置为1。 
  
8、将block的 isa指针设置为 _NSConcreteMallocBlock,这就意味着该block是一个堆block。 
  
9、最后,如果block有一个拷贝辅助函数(a copy helper function),那么就调用它。如果有必要的话,表一起会生成一个拷贝辅助函数。例如block需要拷贝对象的时候,拷贝辅助函数会retain住已经拷贝的对象。 
  
思路很清晰吧!现在你应该知道当block被拷贝时会发什么了!下面还需要了解一下当release时又回发生什么? 
  
Block_release
与Block_copy对应的是Block_release()。同样,Block_release()也是一个宏定义,如下所示: 
  1. #define Block_release(...) _Block_release((const void *)(__VA_ARGS__))
实际上,跟 Block_copy()类似, Block_release()会为我们把参数进行强制类型转换。这样开发者就不用亲自来处理转换的事情了。 
  
下面我们来看看 _Block_release()函数(为了看起来清晰点,我对代码重排了一下,并移除了垃圾回收相关的代码): 
  1. void _Block_release(void *arg) {
  2. // 1
  3. struct Block_layout *aBlock = (struct Block_layout *)arg;
  4. if (!aBlock) return;
  5. // 2
  6. int32_t newCount;
  7. newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
  8. // 3
  9. if (newCount > 0) return;
  10. // 4
  11. if (aBlock->flags & BLOCK_NEEDS_FREE) {
  12. if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
  13. _Block_deallocator(aBlock);
  14. }
  15. // 5
  16. else if (aBlock->flags & BLOCK_IS_GLOBAL) {
  17. ;
  18. }
  19. // 6
  20. else {
  21. printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
  22. }
  23. }
  
来看看他们都做了些什么: 
1、 首先将参数强制转换为 Block_layout结构。如果传入的是 NULL,那么为了函数的安全起见,将直接返回。 
  
2、将block的引用计数标志位减1(还记得 Block_copy()中将这个引用计数标志位设置为1吗?)。 
  
3、如果newCount大于0,说明还有别的对象引用了这个block,所以并不需要立即释放block,只需简单的返回即可。 
  
4、否则,如果flags中包含 BLOCK_NEEDS_FREE,那么说明这个block是分配到堆上的,并且如果引用计数为0,那么需要释放这个block。首先是调用了block的dispose辅助函数,该函数跟copy辅助函数相反,负责做相反的操作,例如释放掉所有在block中拷贝的变量等。最后使用 _Block_deallocator函数释放掉block,如果你去 runtime.c文件中看看,会发现该函数的尾部是一个指向 free的函数指针,也就是释放掉 malloc分配的内存。 
  
5、如果block是全局的,那么什么事情也不用做。 
  
6、如果代码执行到这里了,会发生一些奇怪的事情:因为正在尝试将栈上的block释放掉,所以这行代码是为了提醒开发者的。在程序实际运行过程中,永远不会看到这里的提示。 
  
就是这些了,没有更多,也没有再复杂的东西了! 
  
何去何从
本文也是我深度围观block的最后一篇。其中有一些内容也可也在我的这本书中找到: Effective Objective-C 2.0。这一系列文章介绍了如何有效的使用block,并且如果你对block感兴趣的话,这系列的内容也可以帮助你更加深入的了解block。 
  
来源: 破船的博客

CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广

深度围观block:第三集的更多相关文章

  1. UFLDL深度学习笔记 (三)无监督特征学习

    UFLDL深度学习笔记 (三)无监督特征学习 1. 主题思路 "UFLDL 无监督特征学习"本节全称为自我学习与无监督特征学习,和前一节softmax回归很类似,所以本篇笔记会比较 ...

  2. 百度APP移动端网络深度优化实践分享(三):移动端弱网优化篇

    本文由百度技术团队“蔡锐”原创发表于“百度App技术”公众号,原题为<百度App网络深度优化系列<三>弱网优化>,感谢原作者的无私分享. 一.前言 网络优化解决的核心问题有三个 ...

  3. 最全的机器学习&深度学习入门视频课程集

    资源介绍 链接:http://pan.baidu.com/s/1kV6nWJP 密码:ryfd     链接:http://pan.baidu.com/s/1dEZWlP3 密码:y82m 更多资源 ...

  4. 我厌倦了 Redux,那就造个轮子 Rectx:第三集

    仓库:215566435/rectx 前言 麻烦快去我的仓库里面喷: 老子学不动了,求不要更新. 呵呵,你没想到吧,这玩意儿竟然有第三集!我靠,我自己都没想到,让我们悄悄的回顾一下前两集完全没想到,竟 ...

  5. [深度学习] Pytorch(三)—— 多/单GPU、CPU,训练保存、加载模型参数问题

    [深度学习] Pytorch(三)-- 多/单GPU.CPU,训练保存.加载预测模型问题 上一篇实践学习中,遇到了在多/单个GPU.GPU与CPU的不同环境下训练保存.加载使用使用模型的问题,如果保存 ...

  6. SpringBoot第三集:热部署与单元测试(2020最新最易懂)

    SpringBoot第三集:热部署与单元测试(2020最新最易懂) 有兴趣的可以先参考附录简单了解SpringBoot自动装配流程. 一.SpringBoot开发热部署 项目开发中,你是否也遇到更新配 ...

  7. SIGAI深度学习第三集 人工神经网络2

    讲授神经网络的理论解释.实现细节包括输入与输出值的设定.网络规模.激活函数.损失函数.初始化.正则化.学习率的设定.实际应用等 大纲: 实验环节: 理论层面的解释:两个方面,1.数学角度,映射函数h( ...

  8. 深度学习笔记(三 )Constitutional Neural Networks

    一. 预备知识 包括 Linear Regression, Logistic Regression和 Multi-Layer Neural Network.参考 http://ufldl.stanfo ...

  9. 深度学习基础(三)NIN_Network In Network

    该论文提出了一种新颖的深度网络结构,称为"Network In Network"(NIN),以增强模型对感受野内local patches的辨别能力.与传统的CNNs相比,NIN主 ...

随机推荐

  1. 【动态规划】XMU 1032 装配线问题

    题目链接: http://acm.xmu.edu.cn/JudgeOnline/problem.php?id=1032 题目大意: 一个物品在2条生产线上加工,每条线上n(n<=1000)个节点 ...

  2. SQL:将查询结果插入到另一个表的三种情况

    一:如果要插入目标表不存在: select * into 目标表 from 表 where ... 二:如果要插入目标表已经存在: insert into 目的表 select * from 表 wh ...

  3. MapReduce入门

    说明 MapReduce是一种分布式计算模型,解决海量数据的计算问题,主要有Map和Reduce组成 用户使用时需要实现map()和reduce()两个函数,两个函数的形参都是key/value键值对 ...

  4. ACM中Java的应用

    先说一下Java对于ACM的一些优点吧: (1) 对于熟悉C/C++的程序员来说Java 并不难学,两周时间基本可以搞定一般的编程,再用些时间了解一下Java库就行了.Java的语法和C++非常类似, ...

  5. 自定义VIew——漂亮的圆形进度条

    package com.example.firstapp; import java.text.DecimalFormat; import android.annotation.SuppressLint ...

  6. [LeetCode] Word Break II 解题思路

    Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each ...

  7. C++11 可变参数模板

    在C++11之前, 有两个典型的受制于模板功能不强而导致代码重复难看的问题, 那就 function object 和 tuple. 拿 function objects 来说, 需要一个返回类型参数 ...

  8. Hbase 设计与开发实战

    Hbase 概述 大数据及 NoSQL 的前世今生 传统的关系型数据库处理方式是基于全面的 ACID 保证,遵循 SQL92 的标准表设计模式(范式)和数据类型,基于 SQL 语言的 DML 数据交互 ...

  9. Spark on YARN的两种运行模式

    Spark on YARN有两种运行模式,如下 1.yarn-cluster:适合于生产环境.        Spark的Driver运行在ApplicationMaster中,它负责向YARN Re ...

  10. node.js环境配置(angularjs高级程序设计中出现的错误)

    一:npm install connect会出现错误:解决方法 1:$ npm install connect@2.X.X 2:$ npm install serve-static: 建立server ...