Inside ARC — to see the code inserted by the compiler

前言

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

我们技术团队会不定期(一般一个月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)  (图-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的地方去。

我们可以再增加一个变量来验证下:

(图-6)

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

(图-7)

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

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

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

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

(图-8) (图-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的时候,编译器就是这么做的。

(图-14)

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

(图-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,布布扣,bubuko.com

时间: 2024-11-03 03:46:17

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

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 identified in an object file. The offset from the base address corresponds to an offset location in a memory configured for storing an address of a variable

设置ARC有效或者无效

在编译单位上,可以设置ARC有效或者无效.比如对每个文件可以选择使用或者不使用ARC,一个应用程序中可以混合ARC有效或者无效的二进制形式. 设置ARC有效的编译方法如下所示:(Xcode4.2开始默认设定对所有文件ARC有效) *使用clang(LLVM编译器)3.0或以上版本 *指定编译器属性为"-fobjc-arc" 设置ARC有效或者无效,一般两种方式:对所有文件统一操作.对部分文件操作. 1.对所有文件统一操作 需求:有的编程人员为了能更好的理解内存管理机制,刻意将编译器指定

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

转:http://blog.csdn.net/duxinfeng2010/article/details/8709697 实践总结:-fno-objc-arc 设置 解决了 旧代码中存在 release autorelease retain 等手动管理内存代码的错误. Xcode4.2(iOS 5)以后启用了ARC技术,虽然4.2以后版本仍然可以不开启ARC,但是我们在建工程的时候有时为了不想管理内存然后就启用了ARC,但是再开发过程中需要用到第三开发类库,而这些第三方类库或是没做更新而不支持A

ARC和非ARC文件混编

在编程过程中,我们会用到很多各种各样的他人封装的第三方代码,但是有很多第三方都是在非ARC情况下运行的,当你使用第三方编译时出现和下图类似的错误,就说明该第三方是非ARC的,需要进行一些配置. 解决方法: 苹果文档Transitioning to ARC Release Notes有一种解决办法为: Use Compiler Flags to Enable and Disable ARC You enable ARC using a new -fobjc-arc compiler flag. Y

OC 内存管理:MRC与ARC

内存中的五大区域: 栈区,堆区,BBS段,数据段和代码段,其中除了堆区以外,其他区域的内存管理由系统自行回收 OC对象是存储在堆区的,所以OC的内存管理主要是对”堆区中的OC对象”进行管理 内存管理中的几个概念: ->引用计算器:既retainCount,每个OC对象内部都有1个8字节空间用来存储retainCount,表示有多少”人”正在使用; 对象刚被创建时,默认计数值就为1,当计数值为0时,系统会自动调用dealloc方法将对象销毁 引用计数器的用法:给对象发送相应的技术操作来改变计数器的

Visual Studio Code开发TypeScript

[Tool] 使用Visual Studio Code开发TypeScript [Tool] 使用Visual Studio Code开发TypeScript 注意 依照本篇操作步骤实作,就可以在「Windows」.「OS X」操作系统上,使用Visual Studio Code开发TypeScript. 前言 为了解决JavaScript:缺少面向对象语法.缺少编译期间错误检查...等等问题.微软提供了一个开源的TypeScript语言,让开发人员能够使用面向对象撰写TypeScript程序代

[Tool] 使用Visual Studio Code开发TypeScript

[Tool] 使用Visual Studio Code开发TypeScript 注意 依照本篇操作步骤实作,就可以在「Windows」.「OS X」操作系统上,使用Visual Studio Code开发TypeScript. 前言 为了解决JavaScript:缺少面向对象语法.缺少编译期间错误检查...等等问题.微软提供了一个开源的TypeScript语言,让开发人员能够使用面向对象撰写TypeScript程序代码,接着再透过TypeScript编译程序将程序代码编译成为JavaScript

How can I disable ARC for a single file in a project?

It is possible to disable ARC for individual files by adding the -fno-objc-arc compiler flag for those files.You add compiler flags in Targets -> Build Phases -> Compile Sources. You have to double click on the right column of the row under Compiler

Xcode中 Object-C中编译ARC配置

Xcode4.2(iOS 5)以后启用了ARC(Automatic Reference Countion)技术(即内存自动管理机制) 1.需要iOS5才支持: 2.很多引用的类库暂时还不支持. 麻烦的是对大多数第三方库需要加禁用arc的编译flag,因为大多都因为兼容性还在使用手动内存管理. 虽然4.2以后版本仍然可以不开启ARC,  但是我们在建工程的时候有时为了不想管理内存然后就启用了ARC, 但是再开发过程中需要用到第三开发类库, 而这些第三方类库或是没做更新而不支持ARC,然后编译时就出