前言

这是我在我们技术团队所做的一次分享,稍作改动放到博客上来。

我们技术团队会不定期(一般一个月1~2次)做技术分享,对我们团队有兴趣的能够私信我。

下面是正文。

这里的主题是“Inside ARC”,顾名思义。主要是探讨ARC在我们看不见的地方为我们做了什么事情。以及怎么做的。出发点是对底层实现的兴趣,不了解这些也最好还是碍写好代码。了解一点应该故意。

下面一些内容參考自Apple的官方文档《Transitioning to ARC Release Notes》或维基百科。兴许不再一一说明。



首先要明白的是ARC并非GC。仅仅是把之前由程序猿手工管理内存(如retain/release)的事情交给编译器来处理,即编译器为程序猿在合适的时机插入一些内存管理代码。

比較例外的是weak特性,除了编译器外,还须要runtime的支持。这直接体如今OS X 10.7和iOS 5之后,才支持完整的ARC特性,包含弱引用的支持(为什么弱引用须要runtime支持?)。

利用Xcode提供的汇编功能(Product - Perform Action - Assemble “filename”)。我们能够初步了解(或猜測)编译器为我们加入的代码。

只是我们看到的是AT&T汇编代码。一片的数据在寄存器之间流动,不是非常利于分析。

所以我们能够“在一个ARC项目中针对部分文件不採取ARC编译”,比方针对“ARCObject.m”採用ARC方式编译,而针对“nonARCObject.m”不採取ARC方式编译。用来作比較。

栈上的变量默认初始化为nil

在ARC的编译条件下。strong、weak和autoreleasing三种栈上的变量会被默认初始化为nil。



(图-1)  

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamFzb25ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">(图-2)



如上两张代码片段,图-1的nonARCObject是不採用ARC编译的。图-2的ARCObject则採用ARC进行编译。于是图-2的obj会被初始化为nil,图-1的则不会。

(图-3)



我们能够简单地用if-else语句再加上一些日志输出来推断是否真的初始化为nil,也能够通过汇编后的代码来观察,以便于由浅入深地探讨兴许内容。



(图-4)



(图-5)



图-4是相应于图-1的汇编代码片段,从25行的“-[ARCObject testFunc]”能够看出;而图-5则相应图-2的汇编代码片段。

两者显著的区别在于图-4的L50-L51相应于“ARCObject.m:15:0”。这里的一行汇编代码“movl    $0, -16(%ebp)”目測做的是初始化obj为nil的工作。应该是将马上数0放到基址指针偏移-16的地方去。

我们能够再添加一个变量来验证下:



watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamFzb25ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">(图-6)



此时的汇编代码关键片段例如以下:



(图-7)



由此能够验证编译器为我们插入了初始化为nil的代码。

保证变量的释放,不会有内存泄露

ARC的一个主要作用就是会帮我们合理释放内存。避免内存泄露。

为了看下编译器是怎么做的,我们在既有代码上添加内存的分配:



(图-8) 

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamFzb25ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">(图-9)



在非ARC的情况下。图-8的代码片段为obj分配了内存但没有释放,存在内存泄露。

在图-9中。尽管代码一模一样,但在ARC模式下,编译器会为我们释放obj对象。

(图-10)



(图-11)



图-10和图-11都有两次objc_msgSend的出现,相应于内存的分配和初始化:



NSObject *obj = objc_msgSend(NSObject, @selector(alloc));

objc_msgSend(obj, @selector(init));



不同的是相应于源码的L16,即表示作用域结束的花括号,图-10是简单的return出去,而图-11多做了一次objc_storeStrong

參考Clang的ARC文档objc_storeStrong的代码例如以下:



idobjc_storeStrong(id*object,id
value) {

  value=
[value retain];

 id
oldValue=*object;

 *object=
value;

  [oldValue release];

 return
value;

}



即在retain新值的同一时候释放旧值。

从图-11能够猜測出。在出作用域时,应该是运行了“objc_storeStrong(obj, nil)”。

我们能够对代码稍作改动进一步验证:



(图-12)



相应的汇编片段例如以下:



(图-13)



图-13中的关键变化有:

     相应于图-12中的L17,这里的赋值语句包括了一次objc_retain,这是因为默觉得__strong类型。强引用产生的全部权须要对引用计数+1。

     相应于图-12中的L19,因为此时显式指定了__unsafe_unretained类型,不具有全部权,所以仅仅是简单的指针赋值;

     相应于图-12中的L20,此时出作用域,调用了两次objc_storeStrong进行释放。



所以,对象分配、赋值产生的引用计数变化,以及出作用域时对象的释放。都得到了验证。

命名风格决定的对象全部权

在我们还没全然迁移到ARC的时候。我们拟定的编码规范中提到了——这时候我去翻阅了下发现居然没有!尽管我印象中非常深刻地有这么一条。可能是在群里有说

在非ARC的时候我们约定,以alloc/new等词开头的方法应该返回对象的全部权,即无需增加自己主动释放池。

在ARC的时候。编译器就是这么做的。



watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamFzb25ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">(图-14)



在非ARC的情况下,这两个函数的汇编代码是一样的。但在ARC的情况下则不然:



watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamFzb25ibG9n/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">(图-15)



(图-16)



如图-15,“testObj”方法在为obj分配内存并初始化后,调用了一次objc_retain对引用计数+1,在出作用域时调用objc_storeStrong释放了一次。最后调用objc_autoreleaseReturnValue将对象增加自己主动释放池。所以对于返回出去的testObj。外部默认是没有全部权的。



而对于图-16。即“newTestObj”方法,最后并没有调用objc_autoreleaseReturnValue,由于编译器依据方法名前缀“new”推断出此时返回的对象,外部应该拥有全部权。

那么。是否返回对象全部权有什么差别呢?



(图-17)



如图-17。分别调用“testObj”和“newTestObj”。相应的汇编片段例如以下:



(图-18)



对于图-17中的L15,编译器添加了objc_retainAutoreleasedReturnValue,然后马上调用objc_release进行释放。

而对于图-17中的L17。编译器直接调用objc_release进行释放。

这能够依据方法名来进行决策。

自己主动释放池

上面讨论了__strong(默认)和__unsafe_unretained,接着先讨论__autoreleasing。再讨论__weak。

(图-19)



(图-20)



能够看出,对于__autoreleasing变量,编译器会自己主动调用objc_autorelease,将对象加入到最内层的自己主动释放池中。

而假设加入了@autorelease_pool代码块,编译器还会添加一对调用。各自是objc_autoreleasePoolPushobjc_autoreleasePoolPop,前者创建一个新的自己主动释放池作为当前自己主动释放池。而后者将之前注冊的对象一一释放。并将上一层自己主动释放池设为当前释放池。

弱引用

因为ARC并非GC,所以没办法检測出循环引用并消除,所以提供了__weak弱引用机制来解决问题。

__weak通经常使用于两个互相引用的对象的当中一方,比方delegate。

另外。使用__weak訪问对象时会将对象增加到自己主动释放池中,避免由于没有强引用被马上释放,导致兴许代码逻辑出错。



(图-21)



(图-22)



这里出现了objc_initWeakobjc_destroyWeak两个调用,代码实现分别例如以下:



idobjc_initWeak(id*object,id
value) {

 *object=nil;

 return
objc_storeWeak(object, value);

}



voidobjc_destroyWeak(id*object)
{

  objc_storeWeak(object,nil);

}



objc_storeWeak的函数原型为“id objc_storeWeak(id *object, id value);”,它的作用是:

     当value为nil时,或者object指向的对象開始析构时。object会被置为nil。而且从weak table中移除。

     当value不为nil时,object会被注冊到weak table中。



这里能够插一下上次我和子通讨论的break retain-cycle的问题,跟__weak、retain-cycle和dealloc都有关。

析构

以图-21中的L16为例。考虑到可能有多个__weak指针指向obj对象,在obj对象析构时须要将这些__weak指针所有置为nil。那么weak table将维护着以obj对象地址为key的表项,该项的值应该是一个数组,维护着多个指向obj对象的__weak指针。

当obj对象被销毁时。以obj对象地址为key去weak table寻找相应的表项进行操作并移除。 



之前和慕华讨论过在ARC模式下,不须要在dealloc中释放成员变量。当时有个点是runtime在何时为我们释放成员变量,依据Clang的文档(ARC下的dealloc)能够得知成员变量的销毁是在dealloc之后。

当然,这并不意味着不须要写“dealloc”方法。



假设我们在dealloc方法中设置断点进行调试,那么我们会发现有个方法名叫“.cxx_destruct”,这里有篇文章对其进行探究。



參考Apple的开源文件,当一个对象的引用计数变为0时。会调用dealloc。接着是_objc_rootDealloc
- object_dispose - objc_destructInstance - objc_clear_deallocating,在最后一个调用中清理weak table。

关于block

我之前有分享过一个主题叫《iOS中block实现的探究》,当中谈到依据内存位置区分,block能够分为三种,各自是_NSConcreteGlobalStack、_NSConcreteStackBlock和_NSConcreteMallocBlock,相应着全局、栈和堆三种不同的内存位置。



在非ARC的情况下,我们在传递block对象时须要使用Block_copy进行拷贝,而不能仅仅是使用strong属性做强引用。由于当栈展开后,指向的block对象就不在了,我们须要提前将其复制到堆上。



而在ARC下,编译器自己主动为我们做了处理:



(图-23)



(图-24)



能够看到在进行赋值时,因为默认是__strong类型,所以编译器调用了objc_retainBlock

这种方法的作用是。假设block对象在栈上则将其复制到堆上。假设block对象已经在堆上。则做retain操作。



考虑到如今默认是__strong类型的变量赋值,所以block中假设引用外部变量都是强引用,可是否须要所有採用弱引用呢?这里能够供參考。

Inside ARC — to see the code inserted by the compiler的更多相关文章

  1. Type Systems

    This section deals with more theoretical aspects of types. A type system is a set of rules used by a ...

  2. AutoReleasePool 和 ARC 以及Garbage Collection

    AutoReleasePool autoreleasepool并不是总是被auto 创建,然后自动维护应用创建的对象. 自动创建的情况如下: 1. 使用NSThread的detachNewThread ...

  3. Code Complete阅读笔记(三)

    2015-05-26   628   Code-Tuning Techniques    ——Even though a particular technique generally represen ...

  4. 在Xcode4.5中禁用ARC(Automatic Referencing Counting) 转

    最近升级了xcode4.5,用上了ios6的SDK.但用着用着发现一个比较烦的问题,以前很多代码提示错误,发现原来因为xcode启用了ARC,当ARC启用后会自动在代码中加入retain.releas ...

  5. Linking code for an enhanced application binary interface (ABI) with decode time instruction optimization

    A code sequence made up multiple instructions and specifying an offset from a base address is identi ...

  6. Manage, Administrate and Monitor GlassFish v3 from Java code usingAMX & JMX

    http://kalali.me/manage-administrate-and-monitor-glassfish-v3-from-java-code-using-amx-jmx/ Manage, ...

  7. 【原】iOS学习之ARC和非ARC文件混编

    在编程过程中,我们会用到很多各种各样的他人封装的第三方代码,但是有很多第三方都是在非ARC情况下运行的,当你使用第三方编译时出现和下图类似的错误,就说明该第三方是非ARC的,需要进行一些配置.

  8. 转:在支持ARC工程中编译不支持ARC的文件

    转:http://blog.csdn.net/duxinfeng2010/article/details/8709697 实践总结:-fno-objc-arc 设置 解决了 旧代码中存在 releas ...

  9. [Tool] 使用Visual Studio Code开发TypeScript

    [Tool] 使用Visual Studio Code开发TypeScript 注意 依照本篇操作步骤实作,就可以在「Windows」.「OS X」操作系统上,使用Visual Studio Code ...

随机推荐

  1. 搭建自己的websocket server_1

    用Node.js实现一个WebSocket的Server. https://github.com/sitegui/nodejs-websocket#event-errorerr   nodejs-we ...

  2. 认识React框架

    在大厂面试的时候被问会不会React框架几乎是必须的,可见React框架在现在前端市场的份额.所以说学习React框架的必要性. react框架起源于Facebook的内部项目,因为对市场上的Java ...

  3. 08.十分钟学会JSP传统标签编程

    一.认识标签 1,说明:传统标签编程在开发中基本用不到,学习标签编程主要还是为了完善知识体系. 2,标签的主要作用:移除或减少jsp中的java代码 3,标签的主要组成部分及运行原理 4,简单标签示例 ...

  4. Python 之 基础知识(四)

    一.公共方法(列表.元组.字典以及字符串) 1.内置函数 cmp函数取消可以用比较运算符来代替,但是字典是无序的,故而不可以用比较运算符比较. 2.切片(列表.元组.字符串适用) 3.运算符 列表中直 ...

  5. date 格式化

    以这个为例:    yyyy-MM-dd HH:mm:ss 首先得写好你需要的模板 options.sign = options.sign || 'yyyy-MM-dd HH:mm:ss'; 其次就可 ...

  6. 【转载】Java实现word转pdf

    最近遇到一个项目需要把word转成pdf,GOOGLE了一下网上的方案有很多,比如虚拟打印.给word装扩展插件等,这些方案都依赖于ms word程序,在java代码中也得使用诸如jacob或jcom ...

  7. ML一:python的KNN算法

    (1):list的排序算法: 参考链接:http://blog.csdn.net/horin153/article/details/7076321 示例: DisListSorted = sorted ...

  8. 编写可维护的javascript阅读笔记

    格式 变量 变量命名, 采取小驼峰大小写 变量使用名词, 函数前缀为动词 局部变量应统一定义在函数的最上面, 而不是散落在函数的任意角落. 赋初始值的定义在未赋初始值的变量的上面. 我个人建议不使用单 ...

  9. Linux VFS分析(二)

    inode的管理:Inode-cache hash表inode_hashtable索引节点缓存 dentry的管理: 我们知道,若干dentry描绘了一个树型的目录结构,这就是用户所看到的目录结构,每 ...

  10. TF从文件中读取数据

    从文件中读取数据 在TensorFlow中进行模型训练时,在官网给出的三种读取方式,中最好的文件读取方式就是将利用队列进行文件读取,而且步骤有两步: 把样本数据写入TFRecords二进制文件 从队列 ...