Objective-C学习之ARC的实现方法

前言

在ARC出现以前,程序员们只能靠retain/relese/autorelease来确保对象们恰好“坚持”到被需要的那一刻。如果忘了retain,或者多次release某个对象,程序就会发生内存泄漏的问题,甚至直接崩溃。

在Xcode 4.2中,除了语法检查外,Apple的新LLVM编译器还将内存管理的苦差事接了过来,它会检查代码,决定何时释放对象。Apple的文档里是这么定义ARC的:

“自动引用计数(ARC)是一个编译器级的功能,它能简化Cocoa应用中对象生命周期管理(内存管理)的流程。”

ARC使内存管理在大部分时候变得如同小事一桩,但我们仍要在决定自己的类如何管理其它对象的引用时承担一些责任。

苹果的官方说明中称,ARC是“由编译器进行内存管理”的,但是实际上只有编译其是无法完全胜任的,再次基础上还需要Objective-C运行时库的协助。

也就是说,ARC由以下工具、库来实现。

  • clang(LLVM编辑器)
  • objc4 Objective-C 运行时库

__strong 修饰符

{
 id __strong obj = [[NSObject alloc] init];
}

~~本人是c/cpp小白,没有编译成功,没看到汇编输出TAT~~

以上代码编译器的模拟代码:

/** 编译器的模拟代码 */
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);

如上所示,调用了2次objc_msgSend方法,变量的作用域结束时通过objc_release释放对象。虽然ARC有效时不能使用release方法,但由此可知编译器自动插入了release。

使用alloc/new/copy/mutableCopy以外的方法时:

{
 id __strong obj = [NSMutableArray array];
}

编译器的模拟代码如下:

/** 编译器的模拟代码 */
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

其中的objc_retainAutoreleasedReturnValue函数主要用于优化程序运行,它用于自己持有(retain)对象的函数,但它持有的对象应为返回注册在autoreleasepool中对象的方法,或是函数的返回值。在调用alloc/new/copy/mutableCopy以外的方法,由编译器插入该函数。

与 objc_retainAutoreleaseReturnValue函数相对的函数为objc_autoreleaseReturnValue函数。它用于alloc/new/copy/mutableCopy方法以外的NSMutableArray类的array类方法等返回对象的实现上。

+ (id) array
{
 return [[NSArray alloc] init];
}

转换后

+ (id) array
{
 id obj = objc_msgSend(NSArray,@selector(alloc));
 objc_msgSend(obj,@selector(init));
 return objc_autoreleaseReturnValue(obj);
}

返回注册到autoreleasepool中对象的方法使用了objc_autoreleaseReturValue函数返回注册到autoreleasepool中的对象。但是objc_autoreleaseReturValue函数同objc_autorelease函数不同,一般不仅限于注册对象到autoreleasepool中。

objc_autoreleaseReturValue函数会检查使用该函数的方法或函数调用方的执行命令列表。如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue()函数,那么就不将返回的对象注册到autoreleasepool中,而直接传递到方法或函数的调用方。objc_retainAutoreleasedReturnValue函数与objc_retain函数不同,它即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象。

通过objc_autoreleaseReturnValue函数和objc_retainAutoreleasedReturnValue函数的协作,可以不讲对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。

__weak 修饰符

  • 若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量。
  • 使用附有__weak修饰符的变量,即是使用注册到了autoreleasepool中的对象。
{
 id __weak obj1 = obj;
}
/** 编译器的模拟代码 */
id obj1;
objc_initWeak(&obj1,obj);
objc_destroyWeak(&obj1);

通过objc_initWeak函数初始化附有__weak修饰符的变量,在变量作用域结束时通过objc_destroyWeak函数释放该变量。
如以下源码所示,objc_initWeak函数将附有__weak修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak函数。

obj1 = 0;
objc_storeWeak(&obj1,obj);

objc_destroyWeak函数将0作为参数调用objc_storeWeak函数。

objc_storeWeak(&obj1,0);

即前面的源代码和以下代码相同:

id obj1;
obj1 = 0;
objc_storeWeak(&obj1,obj);
objc_storeWeak(&obj1,0);

objc_weakStore函数把第二参数的复制对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中,如果第二参数为0,则把变量的地址从weak表中删除。

weak表与引用计数表相同,作为散列表被实现。如果使用weak表,将废弃对象的地址作为键值进行检索,能高速地获取对应的附有__weak修饰符的变量的地址。另外,由于一个对象可以同时赋值给多个附有__weak修饰符的变量中,所以对于一个键值,可注册多个变量的地址。

释放对象时,废弃谁都不持有的对象,通过objc_release函数释放。

  • objc_release
  • 因为计数为0,所以执行dealloc
  • _objc_rootDealloc
  • object_dispose
  • objc_destructInstanse
  • objc_clear_deallocating

对象被废弃时最后调用的objc_clear_deallocating函数动作如下:

  • 从weak表中获取废弃对象的地址为键值的记录。
  • 将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil
  • 从weak表中删除该记录。
  • 从引用计数表中删除废弃对象的地址作为键值的记录。

根据以上步骤,前面说的如果附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量这一功能即被实现。由此可知,如果大量使用附有__weak修饰符的变量,则会消耗相应的CPU资源,对此只在需要避免循环引用的时候使用__weak修饰符。

使用__weak修饰符时,以下代码会引起编译器警告

{
 id __weak obj = [[NSObject alloc] init];
 NSLog(@"obj = %@",obj);
}

编译结果如下:

Assigning retained object to weak variable; object will be released after assignment

编译器模拟代码如下:

id obj;
id temp = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(temp,@selector(init));
objc_initWeak(&obj,temp);
objc_release(temp);
objc_destroyWeak(&obj);

运行结果如下:

2017-12-07 19:37:24.075939+0800 ImageOrientation[10963:3581164] obj = (null)

使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象。

{
 id __weak obj1 = obj;
 NSLog(@"%@",obj1);
}

该代码可以转换为如下形式:

/** 编译器模拟代码*/
id obj1;
objc_initWeak(&obj1,obj);
id temp = objc_loadWeakRetained(&obj1);
objc_autorelease(temp);
NSLog(@"%@",obj1);
objc_destroyWeak(&obj1);

与赋值时相比,在使用附有__weak修饰符变量的情形下,增加了对objc_loadWeakRetained函数和objc_autorelease函数的调用。这些函数的动作如下:

  • objc_loadWeakRetained函数取出附有__weak修饰符变量所引用的对象并retain
  • objc_autorelease函数将对象注册到autoreleasepool中。

__autoreleasing 修饰符

将对象赋值给附有__autoreleasing修饰符的变量等同于MRC时调用对象的autorelease方法。

@autoreleasepool{
 id __autoreleasing obj = [[NSObject alloc] init];
}

模拟代码如下:

/** 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_autoreleas(obj);
objc_autoreleasePoolPop(pool);

alloc/new/copy/mutableCopy之外的方法实现:

@autoreleasepool{
 id __autoreleasing obj = [NSMutableArray array];
}
/** 编译器的模拟代码 */
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutorelesedReturnedValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

引用计数

获取引用计数的函数为CFGetRetainCount

例如:

{
 id __strong obj = [[NSObject alloc] init];
 NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}

结果为1

{
 id __strong obj = [[NSObject alloc] init];
 id __weak obj1 = obj;
 NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}

结果为2

{
 id __strong obj = [[NSObject alloc] init];
 id __autoreleaing obj1 = obj;
 NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}

结果为2

{
 id __strong obj = [[NSObject alloc] init];
 @autoreleasepool{
  id __autoreleaing obj1 = obj;
  NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}
 }
 NSLog(@"retainCount = %ld",CFGetRetainCount((__bridge CFTypeRef)obj));
}

结果为2和1

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Objective-C的入门学习笔记

    对于一门语言得学习,个人觉得最主要得就是其语法格式和那些关键字. 因为对于基本现在大多数得语言,每种语言都是一种符合人思维得方式来与计算机交流. 因此,其实每种语言中定义或是封装好得基础类和类库,其实都是很相似得. 比如 int,double几种基本类型,String,数组,集合以及字典数据类型. 因此你在编程时比如你想以字符串形式和计算机交流,那你很容易就能想到在C中可以使用String这个类,在java中也是Stirng (当然,前提是你至少有一种同抽象级别得语言基础),那OC呢,也有这样一

  • Objective-C 宏定义详细介绍

    喜欢读一些开源项目源码的人,总是会发现,大神的代码中总是有那么一些简短而高效的宏定义,点击进去一看,发现晦涩难懂,别说学习了,有时候理解都是一种困难,但是宏定义本身并没有那么难,但是写出一个好的宏当然还是需要丰富的经验和技术,接下来就说一说宏定义,看懂大神的宏是第一步,偶尔写一个也是装逼的好办法- 定义: 宏定义分为两种:一种是对象宏(object-like macro)另一种就是函数宏(function-like macro) 根据名字也可以理解到,对象宏就是用来定义一个量,通过这个宏可以拿到

  • Objective-C实现无限循环轮播器

    先看看效果图: 具体实现代码: 1. 控制器  // // AppDelegate.m // 无限轮播器 // // Created by zhangmi on 16/5/16. // Copyright © 2016年 Paramount Pictures. All rights reserved. // #import "ViewController.h" #import "SNInfiniteScrollView.h" @interface ViewContr

  • Swift调用Objective-C代码

    最近iOS开发之新编程语言Swift在iOS开发圈内反响比较大,国内外都有很多教程或小示例. 虽然Swift这门语言仍然在不断的进化之中,而且变动还是比较大,苹果公司也不承诺目前所写代码会在将来兼容,但仍挡不住iOS开发者的热情. 为什么要使用Swift调用Objective-C代码 目前Swift语言所编写的应用才刚刚可以使用Xcode 6 GM版本提交,而Objective-C作为苹果的主开发语言存在了很多年了.目前尚无成熟的Swift库可用,所以当前编写应用可以说基本离不开调用Object

  • Swift和Objective-C 混编注意事项

    Swift和Objective-C 混编注意事项整理: 前言 Swift已推出数年,与Objective-C相比Swift的语言机制及使用简易程度上更接地气,大大降低了iOS入门门槛.当然这对新入行的童鞋没来讲,的确算是福音,但对于整个iOS编程从业者来讲,真真是,曾几何时"高大上",转瞬之间"矮矬穷".再加上培训班横行,批量批发之下,iOS再也看不到当年的辉煌.iOS10推出后,紧跟着Xcode8也推送了更新,细心者会发现,Xcode8下iOS版本最低适配已变为i

  • Swift能代替Objective-C吗?

    我文章的中心是,以 Apple 目前给出的各种资料来看,这语言不会替代掉ObjC,它不是下一代的ObjC.它有很多缺点,使得它不足以做大型项目.这些缺点使得,Apple 自己都没有使用它做 Mac/iOS 的 app.我不排除明年后年它有很大改进,但至少现在还没有这端倪. 因此,如果你会ObjC,你不需要去看它. 但你如果问我这语言对普通开发者重要不重要,我说重要,可以明确告诉你这一点--它是 Apple 在 WWDC 向全世界推出的重磅语言我怎麽能说它不重要? 它降低了入门的门槛.使得大量的

  • Objective-C学习之ARC的实现方法

    前言 在ARC出现以前,程序员们只能靠retain/relese/autorelease来确保对象们恰好"坚持"到被需要的那一刻.如果忘了retain,或者多次release某个对象,程序就会发生内存泄漏的问题,甚至直接崩溃. 在Xcode 4.2中,除了语法检查外,Apple的新LLVM编译器还将内存管理的苦差事接了过来,它会检查代码,决定何时释放对象.Apple的文档里是这么定义ARC的: "自动引用计数(ARC)是一个编译器级的功能,它能简化Cocoa应用中对象生命周期

  • python学习之第三方包安装方法(两种方法)

    这篇文章主要介绍了python学习之第三方包安装方法,最近在学习QQ空间.微博(爬虫)模拟登录,都涉及到了RSA算法.这样需要下一个RSA包(第三方包),在网上搜了好多资料,具体有以下两种方法: 第一种方法(不使用pip或者easy_install): Step1:在网上找到的需要的包,下载下来.eg. rsa-3.1.4.tar.gz Step2:解压缩该文件. Step3:命令行工具cd切换到所要安装的包的目录,找到setup.py文件,然后输入python setup.py install

  • JavaScript学习笔记之ES6数组方法

    ES6(ECMAScript 6)是即将到来的新版本JavaScript语言的标准,代号harmony(和谐之意,显然没有跟上我国的步伐,我们已经进入中国梦版本了).上一次标准的制订还是2009年出台的ES5.目前ES6的标准化工作正在进行中,预计会在14年12月份放出正式敲定的版本.但大部分标准已经就绪,且各浏览器对ES6的支持也正在实现中. ES6给数组添加了一些新特性,而这些新特性到目前为止完全可以运用到自己的业务层.在这一节中将总结有关于ES6给数组提供一些新特性的使用方法. ES6提供

  • Android游戏开发学习②焰火绽放效果实现方法

    本文实例讲述了Android游戏开发学习②焰火绽放效果实现方法.分享给大家供大家参考.具体如下: 本节介绍在游戏开发中常用到的数学物理应用--粒子系统.粒子系统与上一节的小球有类似的地方,都是通过数学方法和物理公式模拟客观世界中的物体的运动轨迹.不同的是小球更强调个体运动,而焰火粒子等粒子系统更注重整体感觉. 一.焰火粒子效果 1.粒子对象类Particle类和粒子集合类ParticleSet类 每个粒子都为一个Particle类的对象,程序中产生的所有Particle对象都由一个Particl

  • JavaScript学习笔记之数组求和方法

    推荐阅读:JavaScript学习笔记之数组的增.删.改.查 通过最近学习,越来越感觉JavaScript的强大与魅力之处,仅数组一个知识点就让我这样的初学者折腾了很久.折腾是折腾,但还是很有效果的,这几天的学习到的一些基本知识,就在自己的业务就用到了.顿感自信心爆棚,我也有一天能学会JavaScript. 别的不多说了,咱们今天一起来玩数组的求和,看看有哪些方法可以实现数组的求和. 数组方法 JavaScript中数组有很多种方法,下图能更好的向你阐述JavaScript有多少种方法,作用是啥

  • vue 双向数据绑定的实现学习之监听器的实现方法

    提到了vue实现的基本实现原理:Object.defineProperty() -数据劫持 和 发布订阅者模式(观察者),下面讲的就是数据劫持在代码中的具体实现. 1.先看如何调用 new一个对象,传入我们的参数,这个Myvue ,做了啥? 上面看到了在实例化一个Myvue 对象的时候,会执行init方法, init 方法做了两个事,调用了observer 方法,和 实例化调用了 compile 方法. 到这里我们就明白了,实例化一个Myvue后,我们要做的就是监听数据变化和编译模板 . 上面O

  • Vue学习之axios的使用方法实例分析

    本文实例讲述了Vue学习之axios的使用方法.分享给大家供大家参考,具体如下: Axios 是一个用于实现网络数据请求的JavaScript库,可以用在网页和 node.js 中,用于创建 XMLHttpRequests,vue官方支持使用axios代替vue--resourse来实现网络数据请求. 使用前需要在你的项目中安装axios,例如通过npm安装库: npm install --save axios 接着在项目中引入axios: import axios from 'axios' 1

  • jsp学习之scriptlet的使用方法详解

    scriptlet的使用 jsp页面中分三种scriptlet: 第一种:<% %> 可以在里面写java的代码.定义java变量以及书写java语句. 第二种:<%! %> 可以在里面定义全局变量以及方法,类. 第三种:<%=%> 用于打印变量或者输出值. <% %>的使用 <html> <head> <title>this is java page</title> </head> <bod

  • python算法学习双曲嵌入论文方法与代码解析说明

    目录 1. 方法说明 损失函数 梯度下降 梯度求解 2. 代码训练过程 3. 结果表现 其他参考资料 本篇接上一篇:python算法学习双曲嵌入论文代码实现数据集介绍 1. 方法说明 首先学习相关的论文中的一些知识,并结合进行代码的编写.文中主要使用Poincaré embedding. 对应的python代码为: def dist1(vec1, vec2): # eqn1 diff_vec = vec1 - vec2 return 1 + 2 * norm(diff_vec) / ((1 -

  • Go语言学习之结构体和方法使用详解

    目录 1. 结构体别名定义 2. 工厂模式 3. Tag 原信息 4. 匿名字段 5. 方法 1. 结构体别名定义 变量别名定义 package main import "fmt" type integer int func main() { //类型别名定义 var i integer = 1000 fmt.Printf("值: %d, 类型: %T\n", i, i) var j int = 100 j = int(i) //j和i不属于同一类型,需要转换 fm

随机推荐