Objective-C高级编程之引用计数,看我就够了

2018-02-09 12:46:46来源:https://www.jianshu.com/p/c0ddaa71da2c作者:shenglanya人点击

分享





自动引用计数.png
1.1 什么是自动引用计数
概念:在 LLVM 编译器中设置 ARC(Automaitc Reference Counting) 为有效状态,就无需再次键入 retainrelease 代码。
1.2 内存管理 / 引用计数
1.2.1 概要

引用计数就像办公室的灯的照明

























对照明设备所做的动作对OC对象所做的动作
开灯生成对象
需要照明持有对象
不需要照明释放对象
关灯废弃对象


其中,A生成对象时,引用计数为 1, 当多一个人需要照明,如B需要照明,则引用计数 +1, 以此类推。当A不需要对象,A释放对象,引用计数 -1.当最后一个持有对象的人都不要这个对象了,则引用计数变为 0,丢弃对象。
1.2.2 内存管理的思考方式

客观正确的思考方式:


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























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



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


- (id) allocObject {
// 自己生成并持有对象
id obj = [[NSObject alloc] init];

return obj;
}



需要注意的是: NSMutableArray 类的 array 方法取得的对象不是自己所持有的。其内部实现原理为:


- (id)object {
// 自己生成并持有对象
id obj = [[NSObject alloc] init];

// 将对象注册到 autoreleasepool 中, pool结束时会自动调用 release,这样的方法自己就不会持有对象。
[obj autorelease];

// 返回这个自己不持有的对象。
return obj;
}



非自己生成的对象,自己也能持有:虽然一开始是不持有的,但是可以使用 retain 使其变成被自己所持有的,然后也可以使用 release 方法释放对象。


  // 取得非自己生成的对象
id obj = [NSMutableArray array];
// 取得的对象存在了,但是并非自己所持有的,引用计数还为 0, 但是该对象被放到了autoreleasepool 中,可以自动释放
[obj retain];
// 此时,自己就持有了这个对象,引用计数为 1
[obj release];
// 此时释放了这个对象,引用计数变为 0 ,对象就不可以再被访问了,但是对象也没有被立即废弃



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


// 取得非自己持有的对象
id obj = [NSMutableArray array];
[obj release];
// 会导致程序崩溃




释放.png
1.2.3 alloc/retain/release/dealloc 实现

分析 GNU 源码来理解 NSObject 类中的方法。



首先是 alloc id obj = [[NSObject alloc] init];


+ (id)alloc {
// alloc 在内部调用 allocWithZone
return [self allocWithZone:NSDefaultMallocZone()];
}
+ (id)allocWithZone:(NSZone *)zone {
// allocWithZone 在内部调用 NSAllocateObject
return NSAllocateObject(self, 0, z);
}
struct obj_layout {
NSUInteger retained;
};
inline id
NSAllocateObject (Class aClass, NSUInteger extreBytes, NSZone *zone) {
int size = 计算容纳对象所需内存的大小;
// 分配内存空间
id new = NSZoneMalloc(zone, size);
// 将该内存空间中的值初始化为 0
memset(new, 0, size);
// 返回作为对象而使用的指针
new = (id)&((struct obj_layout *) new)[1];
}
/**
其中, NSZoneMalloc, NSDefaultMallocZone() 等名称中包含的 Zone 是为了防止内存碎片化而引入的结构。对内存分配的区域本身进行多重化管理,根据对象使用的目的,大小,分配内存,从而提高内存管理的效率。
但是现在的运行时系统知识简单的忽略了区域的概念,运行时系统中的内存管理本身已经机具效率,再使用区域来管理内存反而会引起内存使用效率低下的问题。
*/


去掉NSZone后简化的代码


struct obj_layout {
NSUInteger retained;
};
+ (id)alloc {
int size = sizeof(struct obj_layout) + 对象大小;
// 这句的意思是,为 struct obj_layout 这个结构体分配一个 size 大小的内存空间,并且函数calloc()会将所分配的内存空间中的每一位都初始化为零,也就是这块内存中所有的值都为 0
struct obj_layout *p = (struct obj_layout *)calloc(1, size);
// 返回该对象指针
return (id)(p + 1);
}


[obj retain]; 的实现


- (id)retain {
NSIncrementExtraRefCount(self);
}
inline void
NSIncrementExtraRefCount(id anObject) {
// 首先 (struct obj_layout *) anObject 找到的是这个对象的尾部, 所以需要 [-1] 减去该对象的大小,来寻址到该对象的头部,然后再判断该结构体中 retained 这个变量的值是否已经大于了系统最大值,如果没有,就 retained++, 使得引用计数 +1.
if (((struct obj_layout *) anObject)[-1].retained == UINT_MAX - 1) {
[NSException raise: NSInternalInconsistencyException format:@"NSIncrementExtraRefCount() asked to increment too far"];

((struct obj_layout *) anObject) [-1].retained++;
}
}


[obj release] 的实现


- (void)release {
if (NSDecrementExtraRefCountWasZero(self)) {
[self delloc];
}
}
BOOL
NSDecrementExtraRefCountWasZero(id anObject) {
if (((struct obj_layout *) anObject)[-1].retained == 0) {
return YES;
} else {
((struct obj_layout *) anObject)[-1].retained--;
return NO;
}
}


[obj dealloc]; 的实现


- (void)dealloc {
NSDeallocateObject(self);
}
inLine void
NSDeallocateObject (id anObject) {
// 指针 o 指向 anObject 的内存地址,然后释放这个指针指向的内存
struct obj_layout *o = &((struct obj_layout *) anObject) [-1];
free(o);
}



1.2.4 苹果的实现

首先看 alloc 的实现:


// 依次调用这四个方法
+ alloc
+ allocWithZone:
class_Instance
calloc



retainCount / retain / release 的实现


- retainCount
__CFDoExtrernRefOperation
CFBaseicHashGetCountOfKey


- retain
__CFDoExternRefOperation
CFBasicHashAddValue;
- release
__CFDoExternRefOperation
CFBasicHashRemoveValue

// 这些函数的前缀 CF 表示他们都包含于 Core Foundation 框架的源代码中

所以其内部实现可能如下:


int __CFDoExternRefOperation(uintptr_r op, id obj) {
CFBasicHashRef table = 取得对象的散列表(obj);
int count;

switch (op) {
case OPERATION_retainCount:
count = CFBasicHashGetCountOfKey(table, obj);
return count;
case OPERATION_retain:
CFBasicHashAddValue(table, obj);
return obj;
case OPERATION_release:
count = CFBasicHashRmoveValue(table, obj);
// 如果count == 0, 返回 YES, 则会调用 dealloc
return 0 == count;
}
}
// 举例说明 retainCount
- (NSUInteger)retainCount {
return (NSUInteger)__CFDExternRefOperation(OPERATION_retainCount, self);
}


由此可看出,苹果在计数内部大概是以散列表的方式来管理引用计数的。复习散列表
比较

通过内存块头部管理引用计数的好处:


少量代码即可完成


能够统一管理引用计数需要的内存块和对象所用的内存块



通过引用计数表管理引用计数的好处


对象所用的内存块的分配不需要考虑它的头部(跟内存块头部管理引用计数相比,就是少了一个用来计数的头部)
引用计数表各记录中存有内存块的地址,可以从各个记录追溯到各个对象的内存块。这使得计时出现故障导致了对象所占用的内存块损坏了,在 内存块头部管理引用计数 时,我们这样就没有办法访问这块内存了,但是在 引用计数表管理引用计数 时,我们就可以通过这个计数表来寻址内存块的位置。
另外,在利用工具检测内存泄漏时,引用计数表也可以用来检测各个对象是否有持有者


1.2.5 autorelease

autorelease 会像 C语言 的自动变量一样来对待对象实例。当其超出作用域时,就会对对象进行release 的调用。


autorelease 的具体使用方法:


生成并持有 NSAutoreleasePool 对象


调用已经分配对象的 autorelease 实例方法


废弃 NSAutoreleasePool 对象(对对象自动调用 release)


// 代码如下
NSAutoreleasePool pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; => 等价于 [obj release];



我们在编程中,并不需要显式的调用 pool 对象,因为在 RunLoop 中,这一切都为我们处理好了。在一个 RunLoop 循环中,会进行 NSAutoreleasePool 对象的生成,应用程序的主线程进行处理,废弃 NSAutoreleasePool 对象。


尽管是这样,我们有的时候也需要显式的调用 NSAutoreleasePool 对象,因为有时会产生大量的 autorelease 对象,只要不废弃 NSAutoreleasePool 对象,那么这些生成的对象就不能被释放,会导致内存疯长的现象。最典型的例子就是在读取大量图像的同时改变它的尺寸。


图像文件读到 NSData 对象,并且从中生成 UIImage 对象,改变这个对象的尺寸后,就会生成新的 UIIamge 对象。这种情况下就会产生大量的 autorelease 对象。这时就有必要在合适的地方生成,持有或废弃 NSAutoreleasePool 对象。

另外,在 Cocoa 框架中也有很多类方法用于返回 autorelease 对象。比如


id array = [NSMutableArray arrayWithCapasity:1];
// 等价于
id array = [[[NSMuatbleArray alloc] initWithCapasity:1] autorelease];


1.2.6 autorelease 的实现

首先来看 GNU 的源代码


首先看一下 autorelease 方法的实现


[obj autorelease];
// 表面上的实现方法
- (id)autorelease {
[NSAutoreleasePool addObject:self];
}
/**
实际上, autorelease 内部是用 Runtime 的 IMP Caching 方法实现的。在进行方法调用时,为了解决类名/方法名几区的方法运行是的函数指针,要在框架初始化时对他们进行缓存
*/
id autorelease_class = [NSAutoreleasePool class];
SEL autorelease_sel = @selector(addObject:);
IMP autorelease_imp = [autorelease_class methodForSelector:autorelease_sel];
// 实际的方法调用时使用缓存的结果值
- (id)autorelease {
(*autorelease_imp)(autorelease_class, autorelease_sel, self);
}



再看 NSAutoreleasePool 的 addObject 类方法实现


+ (void)addObject:(id)obj {
NSAutoreleasePool *pool = 取得正在使用的 NSAutoreleasePool 对象;
if (pool) {
[pool addObject:anObj];
} else {
NSLog("不存在正在使用的 NSAutoreleasePool 对象");
}
}

注意:当多个 NSAutoreleasePool 对象嵌套使用时,理所当然会调用最里层的 NSAutoreleasePool 对象

addObject 实例方法实现


// 当调用 NSObject类的 autorelease 实例方法时,这个对象就会被加到 NSAutoreleasePool 对象数组中
- (void)addObject:(id)obj {
[array addObject:obj];
}


drain 实例方法废弃正在使用的 NSAutoreleasePool 对象的过程


// 执行顺序: drain() -> dealloc() -> emptyPool() -> [obj release] -> [emptyPool release]
- (void)drain {
[self dealloc];
}
- (void)dealloc {
[self emptyPool];
[array release];
}
- (void)emptyPool {
for (id obj in array) {
[obj release];
}
}


1.2.7 苹果的实现

C++的实现


class AutoreleasePoolPage {
static inline void *push() {
// 生成或持有 NSAutoreleasePool 对象
}
static inline id autorelease(id obj) {
// 对应 NSAutoreleasePool 类的 addObject 类方法
AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的 AutoreleasePoolPage 实例;
autoreleasePoolPage -> add(obj);
}
static inline void *pop(void *token) {
// 废弃 NSAutoreleasePool 对象
releaseAll();
}
id *add(id obj) {
// 添加对象到 AutoreleasePoolPage 的内部数组中
}
void releaseAll() {
// 调用内部数组对象的 release 类方法
}
};
// 具体调用
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void *objc_autoreleasePoolPop(void *ctxt) {
return AutoreleasePoolPage::push(ctxt);
}
id *objc_autorelease(void) {
return AutoreleasePoolPage::autorelease(obj);
}



观察 NSAutoreleasePool 类方法和 autorelease 方法的运行过程


NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// == objc_autoreleasePoolPush()
id obj = [[NSObject alloc] init];
[obj autorelease];
// == objc_autorelease(obj)
[pool drain];
// == objc_autoreleasePoolPop(pool);


另外:[[NSAutoreleasePool showPools]]; 可以用来确认已经被 autorelease 的对象的状况。


问题: 如果 autorelease NSAutoreleasePool 对象会如何?


答: 会崩溃。因为通常在使用 Foundation 框架时,无论调用哪个对象的 autorelease 方法,本质都是调用 NSObject 类的 autorelease 方法。 但是 autorelease 方法已经被 NSAutoreleasePool 类所重载。所以运行时会出现错误。

1.3 ARC 规则
1.3.1概要
实际上 引用计数式内存管理 的本质部分在 ARC 中并没有改变,就像 自动引用计数 这个名称一样,ARC 所做的,只是自动的帮助我们处理了 引用计数 相关部分。
1.3.2内存管理的思考方式
引用计数式内存的思考方式就是思考 ARC 所引起的变化
自己生成的对象,自己所持有
非自己生成的对象,自己也能持有
不再需要自己持有的对象时释放该对象
非自己持有的对象无法释放

本质上和内存管理的思考方式一样,只是实现方式上有些许不同
1.3.3所有权修饰符

ARC 有效时,id 类型和对象类型与 C语言 中的其他类型不同,其类型必须附加 所有权修饰符 。共有以下四种


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

__strong 修饰符



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


// 在 ARC 有效的环境下
id obj = [[NSObject alloc] init] <==> id __strong obj = [[NSObject alloc] init];
// 在 ARC 无效的环境下
{
id obj = [[NSObject alloc] init]
[obj release];
}


当被__strong 修饰符修饰的,自己生成的,对象在超过其作用域时:


{
// 自己生成并持有对象
id __strong obj = [[NSObject alloc] init];

/**
因为变量 obj 为强引用,所以自己持有对象
*/
}
// 因为变量超出作用域,强引用失效,所以释放对象,因为对象此时没有其他的所有者了,对象被废弃。
// 正好遵循内存管理的原则


当对象的所有者和对象的生命周期是明确的,取得非自己生成并持有的对象时:


{
// 首先取得非自己生成的对象,但是由于__strong修饰符修饰着这个对象,所以自己持有这个对象
id __strong obj = [NSMutableArray array];
}
/**
当超出对象的作用域时,强引用失效,所以释放对象。
但是由于 [NSMutableArray array] 所生成的对象并非自己所持有的,而是自动的加到 autoreleasePool 中,所以会在一个 RunLoop 周期结束后,自动废弃对象。
*/


有 __strong修饰符的变量之间可以相互赋值


// 首先 obj0 强引用指向 对象A , obj1 强引用指向 对象B,表示 obj1 持有 B, obj2 不持有任何对象
id __strong obj0 = [[NSObject alloc] init]; // 对象A
id __strong obj1 = [[NSObject alloc] init]; // 对象B
id __strong obj2 = nil;
// 此时 obj0 与 obj1 强引用同一个对象 B, 没有人持有 对象A 了,所以 对象A 被废弃。
obj0 = obj1;
// 此时 obj2 指向 obj0 所持有的对象, 所以 对象B 现在被三个引用所持有。
obj2 = obj0;
// 现在 obj1 对 对象B 的强引用失效,所以现在持有 对象B 的强引用变量为 obj0,obj2
obj1 = nil;
// 同理,现在只有 obj2 持有对象B
obj0 = nil;
// 没有引用指向 对象B 了,废弃 对象B
obj2 = nil;


__strong修饰符 也可以修饰 OC类成员变量,也可以在方法的参数上,使用附有 _strong 修饰符的变量


@interface Test : NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init
{
self = [super init];
return self;
}
- (void)setObject:(id)obj {
obj_ = obj;
}
@end

// 调用函数
{

// 首先test 持有 Test 对象的强引用
id __strong test = [[Test alloc] init];

// Test对象 的 obj_ 成员,持有 NSObject 对象的强引用
[test setObject:[[NSObject alloc] init]];
}

/**
此时test强引用超出了其作用域,它失效了。
所以此时没有强引用指向 Test对象 了, Test对象会被废弃

废弃 Test 对象的同时, Test对象 的 obj_ 成员也被废弃。
所以它释放了指向 NSObject 的强引用

因为 NSObject 没有其他所有者了,所以 NSObject 对象也被废弃。
*/

_strong修饰符 与 _weak, _autoreleasing 修饰符一样,初始化时,即使不明确指出,他们也都会自动将该引用指向nil。通过 _strong修饰符,完美的满足了 引用计数的思考方式


id类型和对象类型的所有权修饰符默认都为 __strong 所以不需要再显式的指明修饰对象的修饰符为 _strong



__weak 修饰符



_weak修饰符 的出现就是为了解决 _strong修饰符在内存管理中所带来的循环引用问题。如上例:


@interface Test : NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init
{
self = [super init];
return self;
}
- (void)setObject:(id)obj {
obj_ = obj;
}
@end

// 调用函数,打印变量结果如下:
{

// 首先test1 持有 Test 对象的强引用, test2 持有 Test 对象的强引用
id __strong test1 = [[Test alloc] init]; // 对象A
id __strong test2 = [[Test alloc] init]; // 对象B
/**
TestA 对象中的 obj_ 成员变量持有着 test2指向的 对象B, 同时,test2指向的对象B中的 obj_又强引用着 对象A, 所以造成了循环引用。
*/
[test1 setObject:test2];
[test2 setObject:test1];
}
/**
当跳出作用域后,test1释放它对 对象A 的强引用
test2释放它对 对象B 的强引用
但是此时 对象A中的 obj_A 对 对象B 的强引用本应该被释放,但是由于在 对象B 中强引用了对象A,所以 obj_A 不会被释放,会一直强引用 对象B, 而同理,对象B 中的 obj_B 也不会被释放,所以它将一直强引用着 对象A, 所以此时外部没有谁引用着 对象A 和 对象B, 但是他们自己在互相引用着,这样就造成了内存泄漏!(所谓内存泄漏,指的就是应该被废弃的对象,却在超出其生存周期变量作用域时还继续存在着)
*/
/**
打印变量结果如下:
其中 test1 对象中强引用 test2 对象, test2对象 又强引用 test1 对象,造成无尽的循环。
*/





互相强引用.png



循环.png

而下面这种情况:


{
id test = [[Test alloc] init];
[test setObject:test];
}
/**
当强引用test 的作用域结束后,它释放了对 Test 对象的引用。
但是 Test对象 内部还保留着 对 Test对象 的强引用,所以 Test对象 被引用着,所以不会被回收
*/
// 也会发生内存泄漏!




自己引用自己.png

所以此时,就非常需要一个 __weak修饰符 来避免循环引用


// 弱引用与强引用正好相反,不能够持有对象实例。
// 这样写会发出警告:Assigning retained object to weak variable; object will be released after assignment
// 表示因为没有人持有着 NSObject 对象,所以该对象一旦被创建就会立即被销毁
id __weak obj = [[NSObject alloc] init];
// 正确的使用弱引用的方式
{
// 自己生成并持有 NSObject 对象
id obj = [[NSObject alloc] init];

// 因为 NSObject 对象已经被 obj 强引用着, 所以此时 obj1 对它使用弱引用也没有关系,
// 不会使它的引用计数 +1
id __weak obj1 = obj;
}
/**
当超出变量的作用域时, obj 对 NSObject对象 的强引用消失,
此时没有人持有 NSObject对象 了。
NSObject对象 被废弃
*/


对上述循环引用的例子进行修改如下:


@interface Test : NSObject
{
id __weak obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (instancetype)init
{
self = [super init];
return self;
}
- (void)setObject:(id)obj {
obj_ = obj;
}
@end

// 调用函数,打印变量结果如下:
{

// 首先test1 持有 Test 对象的强引用, test2 持有 Test 对象的强引用
id __strong test1 = [[Test alloc] init]; // 对象A
id __strong test2 = [[Test alloc] init]; // 对象B
/**
TestA 对象中的 obj_ 成员变量弱引用着 test2指向的 对象B, 同时,test2指向的 对象B 中的 obj_又弱引用着 对象A。
*/
[test1 setObject:test2];
[test2 setObject:test1];
}
/**
当跳出作用域后,test1释放它对 对象A 的强引用
test2释放它对 对象B 的强引用
此时,由于 对象中的 obj_变量只拥有对对象的弱引用,所以 没有谁持有着 对象A,和对象B,他们被释放,没有造成循环引用!
*/


__weak修饰符 的另一优点:当持有某个对象的弱引用时,如果该对象被废弃,则弱引用将自动失效,并且会被置为 nil的状态(空弱引用)


id __weak obj1 = nil;
{
// 自己生成并持有对象
id __strong obj0 = [[NSObject alloc] init];

// obj1 现在也指向 NSObject对象
obj1 = obj0;

// 此时打印 obj1 有值
NSLog(@"A = %@", obj1);
}

/**
当变量 obj0 超出作用域,它不再持有 NSObject对象,
由于 obj1 是弱引用,所以它也不持有 NSObject对象
由于没人持有 NSObject对象, NSObject对象被废弃

被废弃的同时, obj1 变量的弱引用失效, obj1 被重新赋值为 nil
*/
NSLog(@"B = %@", obj1);
/**
结果打印如下:
2017-12-14 15:16:39.859875+0800 littleTest[10071:1377629] A = <NSObject: 0x10054da70>
2017-12-14 15:16:39.860432+0800 littleTest[10071:1377629] B = (null)
*/




互相弱引用.png

__unsafe_unretained 修饰符



_unsafe_unretained 修饰符 是不安全的修饰符,在 iOS4 以前用来代替 _weak修饰符


id __unsafe__unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];

obj1 = obj0;

NSLog(@"A = %@", obj1);
}

NSLog(@"B = %@", obj1);
/**
该源码无法正确执行,因为 __unsafe_unretained修饰符 使变量既不强引用对象,也不弱引用对象。
当 obj0 超出作用域时, NSObject 无引用,所以被释放
在此同时, obj1 有时会错误访问对象,形成下面这种打印
2017-12-14 15:38:21.462724+0800 littleTest[10140:1399554] A = <NSObject: 0x10044eea0>
2017-12-14 15:38:21.463007+0800 littleTest[10140:1399554] B = <NSObject: 0x10044eea0>
有时会发生错误,直接使程序崩溃。
造成这两种情况的本质为: obj1 访问的对象已经被废弃了,造成了 垂悬指针!
*/

所以,需要注意的是,当使用 _unsafe_unretained 修饰符 访问对象时,必须要确保该对象确实是真实存在的。



__autoreleasing 修饰符



在 ARC 有效时,我们不可以使用如下代码,因为这些代码是在非 ARC 环境下使用的:


NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];


作为替换,在 ARC 有效时, 我们会使用


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

他们的对应关系如图:






等价于.png

我们可以非显式的使用 __autoreleasing 修饰符 。


情况一:当取得非自己生成并持有的对象时,虽然可以使用 alloc/new/copy/mutableCopy 以外的方法来取得对象,但是该对象已经被注册到 autoreleasePool 中。这和在 ARC无效 时,调用 autorelease 方法取得的结果相同。这是因为编译器会自动检查方法名是否以alloc/new/copy/mutableCopy 开始,如果不是,则自动将对象注册到 autoreleasePool 中。


    @autoreleasepool {
// 首先取得非自己生成的对象,因为 obj 的强引用,所以它持有这个对象
// 因为这个对象的方法名不是以 alloc/new/copy/mutableCopy 开头的,所以他被自动注册到 autoreleasePool中了。
{
id __strong obj = [NSMutableArray array];
}
/**
当变量超出其作用域时,他失去对这个对象的强引用。所以它会释放自己所持有的对象
但是此时 autoreleasePool 中还持有着对这个对象的引用,所以它不会立即被废弃
*/
}
/**
当 autoreleasePool 的作用域也结束后,没有人持有这个对象了,所以它被废弃了。
*/


验证上述说法,首先:创建对象的方式为 [NSMutableArray array]


// 在  autoreleasepool 的作用域外定义一个 obj1 持有弱引用  
id __weak obj1 = nil;

@autoreleasepool {

{
id __strong obj = [NSMutableArray array];
obj1 = obj;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
/**
打印结果:
2017-12-14 16:32:07.118513+0800 littleTest[10242:1444479] (
)
2017-12-14 16:32:07.118819+0800 littleTest[10242:1444479] (
)
2017-12-14 16:32:07.118850+0800 littleTest[10242:1444479] (null)
结果表明:当obj0超出其作用域时,它失去了对对象的引用。但是由于该对象被自动注册到 autoreleasepool 中,使得第二个 NSLog 打印时 obj1 依旧弱引用着这个对象,当第三个 NSLog 打印时,由于 autoreleasepool 已经被清空,所以这个对象也被销毁了, obj1 又被重置为 nil
*/




此时,创建对象的方式为:[[NSMutableArray alloc] init]


id __weak obj1 = nil;

@autoreleasepool {

{
id __strong obj = [[NSMutableArray alloc] init];
obj1 = obj;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
/**
打印结果如下:
2017-12-14 16:36:09.584864+0800 littleTest[10257:1449554] (
)
2017-12-14 16:36:09.585131+0800 littleTest[10257:1449554] (null)
2017-12-14 16:36:09.585149+0800 littleTest[10257:1449554] (null)

这是因为,使用 alloc/new/copy/mutableCopy 方法创建对象时,不会将该对象自动的放入 autoreleasePool 中,这就使得当 obj0 超出其作用域后,就没有人强引用着 NSMutableArray 对象了,该对象也就被废弃了。
*/



以下为 取得非自己生成并持有的对象 时所调用方法:


+ (id)array {
return [[NSMutableArray alloc] init];
}
/**
这段代码也没有使用 __autorelease修饰符,所以这个方法内部的对象不会被注册到 autoreleasePool 中。
*/
// 上述方法也可以写成如下形式:
+ (id)array {
id obj = [[NSMutableArray alloc] init];
return obj;
}
/**
因为 return 使得 obj 超出作用域,所以它所指向的对象 NSMutableArray 会被自动释放,但是因为 return 将这个对象作为函数的返回值返回给主调函数,所以这个对象不会被废弃。并且由于这个对象的生成方法是将其作为返回值,不是由alloc/new/copy/mutableCopy 方法创建的,所以 NSMutableArray 对象会被自动添加到 autoreleasePool 中
*/



情况二: 在访问有 __weak修饰符 的变量时,实际上必定会访问注册到 autoreleasePool 中的对象


id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
// 等价于
id __weak obj1 = obj0;
id _autoreleasing temp = obj1;
NSLog(@"class=%@", [temp class]);
/**
出现这种情况的原因是因为:__weak修饰符 只持有对象的弱引用,这样没法保证它访问对象的过程中,对象不被废弃。所以我们将他要访问的对象放到 autoreleasePool 中,这样就会使得 @autoreleasePool块 结束之前都能保证该对象的存在。
*/


情况三:由于 id obj <==> id __strong obj 所以我们希望能推出 id *obj <==> id __strong *obj 但是实际上并非如此,实际情况是 id *obj <==> id __autoreleasing obj 同理:NSObject **obj <==> NSObject * __autoreleasing *obj ,像这样的,id 的指针或对象的指针在没有显示的指定时,会被附加上 __autoreleasing修饰符


// 例如 NSString 中的这个方法
stringWithContentsOfFile:(nonnull NSString *) encoding:(NSStringEncoding) error:(NSError * _Nullable __autoreleasing * _Nullable)
// 使用这个方式的源代码如下:
NSError *error = nil;
BOOL result = [obj performOperationWithError:&error];
// 函数声明如下
- (BOOL)performOperationWithError:(NSError **)error;
// 等价于
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
/**
之所以使用 __autoreleasing 作为修饰符,是因为我们这个方法声明的参数的是 error 这个指针变量的指针,也就是 需要传递 error 的地址。而在这个方法内部的执行如下,它改变了 error 这个指针所指向的内容。使其指向了一个由 alloc 生成的对象。而我们需要明白的内存管理的思考方式为:除了由 alloc/new/copy/mutableCopy 生成的对象外,其他方式生成的对象都必需要注册到 autoreleasePool 中。并取得非自己所持有的对象。所以将变量声明为 (NSError * __autoreleasing *)error 就可以实现这一目的。
*/
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error {
*error = [[NSError alloc] initWithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}
/**
可能对于不熟悉 C语言 的小伙伴来说,不是很明白为什么这里非要将函数参数声明为 “指针的指针”,这是因为当我们仅仅把参数声明为指针时,方法就变为如下,当我们给函数传递指针时,默认会生成跟指针类型相同的实例变量。当我们在这个方法中操作指针时,我们以为操作的是指针,实际上只是这复制的实例变量。也就是说,在这个例子中 error 就是这个复制的实例变量。当这个方法结束时,error 会被释放,其所指向的内容也会一并被释放。所以此时外部的 error 依旧指向 nil。没有任何改变。
而当我们使用 (NSError * __autoreleasing *)error 作为参数时,虽然复制的实例变量情况还是存在,但是这次复制的是“指针的指针”,也就是说,它指向跟参数指针相同指针地址, 在函数内部使用 *error 获取到了指针地址,使其指向了 NSError对象 。这样,虽然函数当出了其作用域时,那个复制的实例变量被销毁了,但是它改变了函数外部 error 指针所指向的对象,使其从 nil 变成了 NSError对象。
*/
- (BOOL)performOperationWithError:(NSError *)error {
error = [[NSError alloc] initWithDomain:MyAppDomain code:errorCode userInfo:nil];
return NO;
}


对于函数传递指针及指针的指针 还不明白的请看这里


最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台