第一章-自动引用计数

1.1什么是自动引用计数


内存管理中对引用采用自动计数的计数
在LLVM编译器中设置ARC为有效状态, 就无需再次键入retain或者release代码

1.2内存管理/引用计数


1.2.1概要

引用计数例子: 开关灯

  • 最早进入办公室的人开灯
  • 之后进入办公室的人需要照明
  • 下班早离开办公室的人不需要照明
  • 最后离开办公室的人关灯

通过需要照明人数来判断是否有人在办公室

状态 操作 分析
第一个人进入办公室 需要照明人数+1 计数值从0变成1, 需要开灯
每当有人进入办公室 需要照明人数+1 如计数值从1变成2
每当有人离开办公室 需要照明人数-1 如计数值从2变成1
最后的人离开办公室 需要照明人数-1 计数值从1变成0, 需要关灯
  • 照明设备 - 对象
  • 人- 对象的使用环境
对照明设备所做的动作 对Objective-C对象所做的动作
开灯 生成对象
需要照明 引用对象
不需要照明 释放对象
关灯 回收对象

1.2.2内存管理的思考方式

引用计数比较客观的思考方式:

  • 自己生成的对象, 自己所持有
  • 非自己生成的对象, 自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放

对象操作与Objective-C方法对应

对象操作 Objective-C方法
生成并且持有对象 alloc/new/copy/mutableCopy等方法
持有对象 retain方法
释放对象 release方法
废弃对象 dealloc方法

关于Objective-C内存管理方法包含在Cocoa框架中, Cocoa框架中Foundation框架类库的NSObject类负责内存管理的职责

自己生成的对象, 自己所持有

alloc/new/copy/mutableCopy等方法生成的对象, 只有自己持有

// 以下方法, 所有objx对象引用计数器都会+1
id obj1 = [[NSObject alloc] init];
id obj2 = [[NSObject new];
id obj3 = [objc copy];
id obj4 = [objc mutableCopy];

非自己生成的对象, 自己也能持有

除alloc/new/copy/mutableCopy等方法之外的方法取得的对象, 因为非自己生成并持有, 所以自己不是该对象的持有者, 需要通过retain方法来持有

// 取得非自己生成并持有的对象
id obj = [NSMutableArray array]; // 取得的对象存在, 但自己不持有对象
[obj retain]; // 通过-retian方法使自己持有对象

不再需要自己持有的对象时释放

自己持有的对象一旦不再需要, 持有者有义务释放该对象, 释放使用release方法

id obj1 = [[NSObject alloc] init]; // 自己生成并持有对象
[obj1 release]; // 释放对象

id obj2 = [NSMutableArray array]; // 取得的对象存在, 但自己不持有对象
[obj2 retain]; // 通过-retian方法使自己持有对象
[obj2 release]; // 释放对象

用alloc/new/copy/mutableCopy等方法生成并持有的对象, 或者使用retain方法持有的对象, 一旦不再需要, 务必要用release方法进行释放

非自己持有的对象无法释放

用alloc/new/copy/mutableCopy等方法生成并持有的对象, 或者使用retain方法持有的对象, 由于持有者是自己, 所以在不需要该对象时需要将其释放. 除此之外得到的对象绝对不能释放, 否则会造成崩溃

1.2.3 alloc/retain/release/dealloc实现

通过GNUstep来说明的
GNUstep是Cocoa框架的互换框架, 与Cocoa实现方式比较一致, 通过其来了解Cocoa框架的实现

1.2.4苹果的实现

通过对alloc类方法设置断点追踪程序的运行, 得出执行所调用的方法和函数

+ alloc
+ allocWithZone:
class_createInstance
calloc

alloc类方法首先调用allocWithZone:类方法, 然后调用class_createInstance函数, 最后通过calloc来分配内存块

retainCount/retain/release实例方法所调用的方法和函数

- retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey

- retain
__CFDoExternRefOperation
CFBasicHashAddValue

- release
__CFDoExternRefOperation
CFBasicHashRemoveValue // 该方法返回0时, -realse调用dealloc

1.2.5autorelease

autorealse自动释放, 更类似于C中的局部变量, 即超出作用域时对象实例的realease实例方法被调用
autorelease的具体使用方法:

  1. 生成并持有NSAutoreleasePool对象
  2. 调用已分配对象的autorelease实例方法
  3. 废弃NSAutoreleasePool对象
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id objc = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; // 相当于[obj release];

在Cocoa框架中, 相当于程序主循环的NSRunLoop或者在其他程序可运行的地方, 对NSAutoreleasePool对象进程生成, 持有和废弃处理.
在大量产生autorelease对象时, 只要不废弃NSAutoreleasePool对象, 那么生成的对象就不能被释放, 有时会产生内存不足的现象. 例如: 读入大量图像的同事改变其尺寸, 图像文件读入到NSData对象, 并从中生成UIImage对象, 改变对象尺寸后生成新的UIImage对象, 这种情况下会产生大量autorelease对象.

for (int i = 0; i < 图像数; ++i) {
  // 读入图像
  // 大量产生autorelease对象
  // 由于没有废弃NSAutoreleasePool对象
  // 最终导致内存不足
  
  [pool drain]; // 通过该方法, autorelease的对象被一起release
}

1.2.6autorelease实现

通过GNUstep, 我们能够看到NSObject类的autorealse实例方法, 本质就是调用NSAutoreleasePool对象的addObject类方法

- (id)autorelease {
  [NSAutoreleasePool addobject: self];
}

1.2.7苹果的实现

1.3ARC规则


1.3.1概要

引用计数式内存管理的本质部分在ARC中并没有改变, ARC只是自动地帮我们处理引用计数的相关部分

  • 一个应用程序可以ARC和MRC混合使用
  • 设置ARC有效的 编译方法:
    • 使用clang3.0及其以上版本
    • 指定编译器属性为'-fobjc-arc'

1.3.2内存管理的思考方式

  • 自己生成的对象, 自己所持有
  • 非自己生成的对象, 自己也能持有
  • 不再需要自己持有的对象时释放
  • 非自己持有的对象无法释放

这一思考模式在ARC时也是可行的, 只是在源码的技术方法上稍有不同. 具体有什么变化需要先理解ARC中追加的所有权声明

1.3.3所有权修饰符

Objective-C编程中为了处理对象, 可将变量类型定义为id类型或者各种对象类型

ARC有效时, id类型和对象类型同C语言其他类型不同, 其类型必须附加所有权修饰符, 所有权修饰符一共4种:

  • __strong修饰符
  • __weak修饰符
  • __unsafe_unretained修饰符
  • __autoreleasing修饰符

__strong修饰符

__strong修饰符是id类型和对象类型默认的所有权修饰符

id obj = [[NSObject alloc] init];
// id和对象类型在没有明确指定所有权修饰符时, 默认为__strong修饰符
id __strong obj = [[NSObject alloc] init];

在MRC模式下, 改源码可以记述为

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

如上述代码所示, 所有__strong修饰符的变量obj在超出其变量作用域时, 即在该变量被废弃时, 会释放其被赋予的对象
如'strong'这个名词所示, __strong修饰符表示对对象的"强引用". 持有强引用的变量在超出其作用于时被废弃, 随着强引用的失效, 引用的对象随之释放

关注下源代码中关于对象的所有者部分

{
  // 自己生成并且持有对象
  id __strong obj = [[NSObject alloc] init];
  
  // 取得非自己生成并持有的对象
  id __strong obj2 = [NSMutableArray array];
} /*
   * 变量超出其作用于, 强引用失效
   * 自动地释放自己持有的对象
   * 对象的所有者不存在, 因此废弃该对象
   */

__strong修饰符的变量, 不仅只在变量作用域中, 在复制上也能够正确的管理其对象的所有者.

另外__strong修饰符同之后的__weak修饰符和__autoreleasing修饰符一起, 可以保证将附有这些修饰符的自动变量初始化为nil

id __strong obj0;
id __weak obj1;
id __autoreleasing obj2;

// 下面代码与以上相同
id __strong obj0 = nil;
id __weak obj1 = nil;
id __autoreleasing obj2 = nil;

__weak修饰符

如果仅仅使用__strong修饰符, 无法解决 "循环引用" 的问题, 从而引发内存泄露

  • 所谓内存泄漏就是应当废弃的对象在超出其生命周期后继续存在

__weak修饰符与__strong修饰符相反, 提供弱引用. 弱引用不能持有对象实例.

  • 通过__weak修饰符从而解决循环引用的问题
  • __weak修饰符还有另一优点: 在持有某对象的弱引用时, 若该对象被废弃, 则此弱引用将自动失效且处于nil被赋值的状态

可以通过__weak修饰符可避免循环引用, 通过检查赋有__weak修饰符的变量是否为nil, 可以判断被复制的对象是否已被废弃

__unsafe_unretained修饰符

__unsafe_unretained修饰符, 是不安全的所有权修饰符. 尽管ARC的内存管理是编译器工作, 但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象

  • 附有__unsafe_unretained修饰符的变量同附有__weak修饰符的变量一样, 不能持有对象.
  • 附有__unsafe_unretained修饰的变量如果被废弃, 该引用不会被自动赋值为nil

在使用__unsafe_unretained修饰符时, 赋值给附有__strong修饰符的变量时, 有必要确认被复制的对象确实存在

__autoreleasing修饰符

ARC有效时不能使用autorelease方法, 也不能使用`NSAutoreleasePool'类. 虽然autorelease无法直接使用, 但是在arc有效时autorelease功能是起作用的

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

指定@autoreleasepool快来替代NSAutoreleasePool类对象生成, 持有以及废弃这一范围. 对象赋值给附有__autoreleasing修饰符的变量, 等价于在MRC时调用对象的autorelease方法, 即对象呗注册到autoreleasepool

在访问附有__weak修饰符的变量时必须访问注册到autoreleasepool的对象, 因为__weak修饰符只有对象的弱引用, 而在访问引用对象的过程中, 该对象有可能被废弃, 如果要把访问对象注册到autoreleasepool中, an么在@autore快结束之前都能却别该对象的存在

id的指针或对象的指针在没有显示指定时会被附加上__autoreleasing修饰符

赋值给对象指针时, 所有权修饰符必须一致

1.3.4规则

在ARC有效的情况下编译代码, 必须遵守以下原则

  • 不能使用 retain/release/retainCount/autorealease
  • 不能使用NSAllocateObject/NSDeallocateObject
  • 必须遵守内存管理方法的命名规则
  • 不要显式调用dealloc
  • 使用@autoreleasepool快替代NSAutoreleasePool
  • 不能使用区域(NSZone)
  • 对象类型不能为C语言结构体(struct/union)的成员
  • 显示转换idvoid *

不能使用 retain/release/retainCount/autorealease

编译器自动使用了这些方法, 再次使用不符合内存管理规范
在ARC有效时, 使用这些方法会编译出错

不能使用NSAllocateObject/NSDeallocateObject

一般通过NSObject类的alloc类方法来生成并持有Objective-C对象
在ARC有效时, 使用这些方法会编译出错

必须遵守内存管理方法的命名规则

如1.2.2所述, 在ARC无效时, 用于对象生成/持有的方法必须遵守以下命名规则

  • alloc
  • new
  • copy
  • mutableCopy

以上名称开始的方法, 在返回对象时, 必须返回给调用方法所应当持有的对象

在ARC有效时追加一条:

  • init

以init开始的方法规则要比上述方法更严格, 该方法必须是实例方法, 并且必须返回对象, 返回的对象应为id(instancetype)或该方法声明类的对象类型, 亦或是该类的父类型或子类型, 该返回对象不注册到autoreleasepool上, 基本知识对alloc方法返回的对象进行初始化处理并返回该对象

不要显式调用dealloc

无论ARC是否有效, 只要对象持有者不持有该对象, 对象就会被废弃, 对象被废弃时会自动调用该对象的dealloc方法

  • 在ARC无效时, dealloc方法中必须调用[super dealloc]
  • 在ARC有效时, dealloc方法中不能调用[super dealloc], 否则编译出错

使用@autoreleasepool快替代NSAutoreleasePool

在ARC有效时不能使用NSAutoreleasePool类, 参考1.3.3

不能使用区域(NSZone)

虽说ARC有效时不能使用区域(NSZone), 但是如1.2.3所属, 不论ARC是否有效, 区域在现在的运行时系统中已被忽略

对象类型不能为C语言结构体(struct/union)的成员

C语言结构体中如果存在Objective-C对象类型变量, 会引起编译错误

因ARC把内存管理的工作分配给编译器, 所以编译器必须能够知道并管理对象的生存周期

要把对象类型变量加入到结构体成员中时 可强制转换为void *或者附加__unsafe_unretained修饰符, 如

// 注意, 附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象, 如果使用时不注意赋值对象的所有者, 可能会遭遇内存泄漏或程序崩溃
struct Data {
  NSMutableArray __unsafe_unretained *array;
};

显示转换idvoid *

在ARC无效时, 以下代码不会出错

id obj = [[NSObject alloc] init];
void *p = obj;

id o = p;
[o release];

但是在ARC有效时会引起编译错误

id类型或对象类型变量赋值给void *或者逆向赋值时都需要进行特定的转换, 如果只想单纯的赋值, 则可进行__bridge转换

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;

id o = (__bridge id)p;

但是转换为void *的__bridge转换, 安全性甚至比__unsafe_unretained更低, 如果使用不注意很可能引起程序崩溃

__bridge还有另外两种转换

  • __bridge_retained
    • 可使要转换赋值的变量也持有所复制的对象
  • __bridge_transfer
    • 被转换的变量所持有的对象在该变量呗赋值给转换目标变量之后随之释放

通过以上两种转换, 不适用id类型或者对象类型变量也可以生成, 持有以及释放对象. 虽然可以这样做, 但是在ARC中并不推荐这种方法

// ARC有效
void *p = (__bridge_retained void *)[[NSObject alloc] init];
(void)(__bridge_gransfer id)p

// ARC无效
id p = [[NSObject alloc] init];
[p release];

这些转换多数在使用Objective-C对象与Core Foundation对象之间的相互转变中

这部分还讲了CoreFoundation和Foundation对象之间的转换及使用注意

1.3.5属性

当ARC有效时, Objective-C类的属性也发生了变化, 以下可作为属性声明中的属性来使用

属性声明的属性 所有权修饰符
assign __unsafe_unretained修饰符
copy __strong修饰符(但是赋值的是被复制的对象)
retain __strong修饰符
strong __strong修饰符
unsafe_unretained __unsafe_unretained修饰符
weak __weak修饰符

1.3.6数组

__unsafe_unretain修饰符以外的__strong/__weak/__autoreleasing修饰符保证其指定的变量初始化为nil.

根据不同的目的选择使用NSMutableArray/NSMutableDictionary/NSMutableSet等Foundation框架的容器, 这些容器会恰当的持有追加的对象并为我们管理这些对象

__autoreleasing修饰的情况下, 因为与设想的使用方式有差异, 最好不要使用动态数组, 由于__unsafe_unretained修饰符在编译器的内存管理对象之外, 所以与void *类型一样, 只能作为C语言的指针类型来使用

1.4ARC的实现


ARC是Objective-C运行时库和编译器进行的内存管理, ARC由一下工具, 类库来实现

  • clang(LLVM编译器)3.0以上
  • objc4 Objective-C运行时库493.9以上

本节围绕clang汇编输出和objc4库源码进行说明, 具体代码详见书(读这章之前需要一些runtime基础)

1.4.1 __strong修饰符

赋值给附有__strong修饰符的变量在实际使用中运行原理

这一块比较绕, 可以先看代码然后看后面的总结, 最后再绕回来看方法下面的论述

alloc/new/copy/mutableCopy等方法

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

通过程序汇编输出得到原源代码

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

通过原源代码所以, 两次调用objc_msgSend方法(alloc和init), 变量作用域结束时通过objc_release释放对象, 虽然ARC不能使用release方法, 但是由此可知, 编译器自动插入了release.

其它构造方法

{
  id __srong obj = [NSMutableArray array];
}

编译器原源代码

id objc = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

最开始的array方法调用和最后变量作用域结束时的release与之前相同, 但是中间的objc_retainAutoreleasedReturnValue函数是什么?

  • 它是用于 自己持有(retain)对象 的函数, 但它持有的对象应 为返回注册在autoreleasepool中对象的方法或是函数的返回值, 调用alloc/new/copy/mutableCopy以外的方法, 由编译器插入该函数.
  • 这种方法是成对的, 与之相对的是objc_autoreleaseReturnValue, 它用于alloc/new/copy/mutableCopy以外的类的构造方法等用于返回的对象的实现上
+ (id)array {
  return [[NSMutableArray Alloc] init];
}

转换后的原源码使用了objc_autoreleaseReturnValue函数

+ (id)array {
  id obj = id objc = objc_msgSend(NSMutableArray, @selector(array));
  objc_msgSend(obj, @selector(init));
  return objc_aotureleaseReturnValue(obj);
}

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

objc_autoreleaseReturnValue函数会检查使用该函数的方法/函数 调用方 的执行命名列表, 如果方法/函数的 调用方 在调用了方法/函数后紧接着调用objc_retainAutoreleasedReturnValue函数, 那么就不将返回的对象注册到autoreleasepool中, 而是直接传递到方法/函数的 调用方

objc_retainAutoreleasedReturnValue函数与objc_retain函数不同, 它即便不注册到autoreleasepool中而返回对象, 也能够正确的获取对象
通过objc_retainAutoreleasedReturnValue函数与objc_autoreleaseReturnValue函数的写作, 可以不将对象注册到autoreleasepool中而直接传递, 这一过程达到了最优化.

省略autoreleasepool注册

说白了, 就是objc_retainAutoreleasedReturnValue函数持有的对象应是函数/方法的返回值, 这个返回值应当注册在autoreleasepool中
objc_autoreleaseReturnValue函数会判断自己所在的这个函数/方法的调用方之后有没有调用objc_retainAutoreleasedReturnValue, 如果没有, 就将返回值注册到autoreleasepool中, 如果有就不注册了, 而且它能让objc_retainAutoreleasedReturnValue函数正确的获取到对象

1.4.2 __weak修饰符

延伸阅读: weak的生命周期:具体实现方法

  • 若附有__weak修饰符的变量所引用的对象被废弃, 则将nil赋值给该变量
  • 使用附有__weak修饰符的变量, 即是使用注册到autoreleasepool中的对象
{
  // 假设obj附加__strong修饰符且被赋值
  id __weak obj1 = obj;
}

转换后的原源代码

id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);

objc_initWeak函数内部

obj1 = 0;
objc_storeWeak(&obj1, obj;

objc_destroyWeak函数内部


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容