美文网首页
iOS内存管理

iOS内存管理

作者: 金约21依代 | 来源:发表于2017-12-15 01:06 被阅读0次

MRC

对象持有讨论

先看一个例子:

// 这个方法生成对象并返回
- (id)object
{
    // 通过 alloc/new/copy/mutableCopy 方法生成对象并自己持有
    id obj = [[NSObject alloc] init];
    
    /**
     autorelease使得自己不去持有这个对象
     自己持有对象的话,可以通过[obj release] 去释放对象,但当对象自身autorelease时,释放是由自身自动释放的,就不能通过release了(个人理解),这个时候去调用release会造成崩溃.
     */
    
    [obj autorelease];
    
    return obj;
}

// 方法调用:

/** 
 这里通过方法 object获取到对象,这个对象是存在的,但是自己并不持有这个对象,对自己不持有的对象是不能去释放他的.
 这个对象为什么存在,上面不是自己加了autorelease吗?
 其实这就是autorelease和release的区别:调用release会立即释放,但autorelease不会,调用autorelease会将对象注册到autoreleasepool中,当pool结束时会自动调用release.这里其实存在着一个哨兵指针. autoreleasepool 的释放是 NSRunloop 决定的.具体可以参考杨潇玉的博客.
 */
id obj1 = [obj0 object];

// 如果让obj1区持有这个对象呢? 可以调用retain,他可以将调用autorelease方法取得的对象变为自己持有
[obj1 retain];

总结:释放非自己持有的对象会造成程序崩溃,因此绝不能释放非自己持有的对象

关于内存释放的研究

NSAutoreleasePool什么时候释放?NSAutoreleasePool的生命周期是怎样的?是在方法体调用结束就释放?其实不是.

在大量产生autorelease对象时,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能被释放,因此有时会产生内存不足的现象.典型例子是读入大量图像时,图像文件读入到NSData对象,从中生成UIImage对象.这种清空下会产生大量autorelease对象.这种情况就需要再适当的地方生成,持有,废弃NSAutoreleasePool对象.

比如在MRC时可以这样写:

for(int i = 0; i<100; i++) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // 读入图像,大量产生autorelease对象
    //  ........
    [pool drain];   // 这句代码可以让autorelease对象被一起release
}

Zone

我们经常看到不少方法里有这个单词,比如NSZoneMalloc,NSDefaultMallocZone等等.这个单词是干什么的呢?

NSZone其实是为了防止内存碎片化而引入的结构.它ui内存分配的区域本身进行多重化管理,根据对象目的,大小分配内存,提高内存管理效率.

但是根据苹果官方所说,现在的运行时系统,内存管理的效率本身已经很高,使用区域来管理内存反而会引起内存使用效率低下及源代码复杂化.

ARC

首先看看ARC中的4个修饰符:

  • __strong (默认修饰符)
  • __weak
  • __unsafe_unretained
  • __autoreleasing

在MRC中有个问题,通过alloc的方式得到的对象会被自己所持有,而通过比如NSArrayarray方法得到的对象不会被持有:

**  MRC  **
// obj持有对象
id obj = [[NSObject alloc] init];

// array不持有对象
NSArray *arr = [NSMuableArray array];

那在ARC中是怎么样的呢?

**  ARC  **
// obj持有对象
id obj = [[NSObject alloc] init];
// 相当于:
id __strong obj = [[NSObject alloc] init]; // 由于obj为强引用,所以自己持有对象

/**
 这里的array也是持有对象的.
 array类方法让arr取得 非自己生成并持有  的对象
 但因arr为强引用,所以持有对象
 在超出作用域(方法体)后,强引用失效,会自动释放自己所持有的对象
*/
NSArray *arr = [NSMuableArray array];
// 相当于
NSArray *__strong arr = [NSarray array];

赋值方法对对象的引用

__strong
id __strong objA = [[NSObject alloc] init];  // objA持有对象A的强引用
id __strong objB = [[NSObject alloc] init];  // objB持有对象B的强引用
id __strong objC = nil;  // objC不持有任何对象

/**
 objA持有由objB赋值对象B的强引用
 因为objA被赋值,所以原先持有的对象A的强引用失效
 对象A的所有者不存在,因此废弃对象A
 
 此时,持有对象B的强引用的变量为
 objA 和 objB
*/
objC = objA;

/**
 objC 持有由objA赋值的对象B的强引用
 此时,持有对象B的强引用的变量为
 objA 和 objB 和 objC
*/
objB = objC;

/**
 nil被赋予了objB,所以objB对对象B的强引用失效
 
 此时,持有对象B的强引用的变量为
 objA 和 objC
 
 
 这里做一个理解,为什么objB = nil, objA还是和objC还是指向对象B呢? objA和objC 会变成nil吗?
 不会.objB = nil;这句话的意思是objB抛弃了原先的对象,重新指向了一个对象(nil),原先的对象B会通过retainCount - 1 的方式回应这次'抛弃'. 但如果B不是通过从新指向另一块内存的方式,而是改变自身,比如 [objB addObject],那么他是对自身对象B进行的操作,这个时候objA和objC也会相应被改变.
*/
objB = nil;

总结:

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

__strong修饰符基本可以完美的进行内存管理了,但是遇到循环引用就需要__weak了.

比如两个Person实例 personApersonB,都有一个id类型的obj属性, obj属性被分别赋值 personBpersonA,即:

Person personA = [[Person alloc] init]; // A 对象
Person personB = [[Person alloc] init]; // B 对象

personA.obj = personB;
personB.obj = personA;

这个时候A对象的持有者是两个 personApersonBobj 属性;
B对象的持有者也是两个 personBpersonAobj 属性;

personA 变量超出其作用域,强引用失效,自动释放对象A, 当personB 变量超出其作用域,强引用失效,自动释放对象B. 此时,持有 A 对象的强引用变量为对象Bobj,持有B对象的强引用为对象A的obj. 发生内存泄漏.

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

上面的例子,如果A 对象持有自身,即personA.obj = personA,这样也会造成循环引用.

还有一个例子,这里做一下分析:

id __weak objA = nil;
{
    id __strong objB = [[NSObject alloc] init];
    objA = objB;
    
    NSLog(@"A = %@",objA);
}

NSLog(@"A = %@",objA);

输出结果为:

A = <NSObject: 0x45f140e>
A = (null)

对于id __weak objA = nil;这句代码,我之前也一直不理解,先不论objA是否=nil, __weak修饰的对象如果没有强持有者不会立即释放掉吗,为什么objA 还存在?

其实,objA是指针,是在栈里的,objA指向的对象(比如对象A)是在堆内存里的,确实,对象A释放后objA也会随之销毁,但是由于objA是在栈里的,并且objA其实是autorelease的,该指针只会被标记为要释放,等待autorelease要释放(drain)时候,objA才会通过哨兵对象确定要被释放,从而发送release消息将它释放掉,所以在这个方法体里,objA还是存在的.所以,在使用附有__weak修饰符的变量时就必定要使用注册到autorelepool中的对象.其实,__weak申明的变量会自动将对象注册到autoreleasepool中.

所以,第一次打印,是打印的objA通过弱引用持有的对象,这个对象在方法体结束后被释放了,所以第二次打印为null.

<font size='3' color='0000ff'>weak自动置为nil的实现</font>

id __weak obj1 = obj;

这句话做了什么?

  1. 首先,obj1通过obj进行初始化,调用函数
objc_initWeak(&obj1, obj)

当释放的时候,第二个参数传递0,即

objc_destroyWeak(&obj1, 0)
  1. objc_storeWeak函数把obj1的地址和被赋值对象通过键值对的方式注册到weak表里.当对象释放的时候,通过废弃对象的地址作为键值进行检索,就能获取到对应__weak变量的地址,然后将所有附有__weak修饰的变量地址全部赋值nil,之后再把她从weak表中移除掉就好了.
  2. 大量使用__weak会消耗相应的CPU资源(要注册weak表,并且一个键值,可以注册多个变量的地址),所以一般只在需要避免循环引用的时候使用__weak.
__unsafe_unretained

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

同样用上面的例子做说明,第一行换成id __unsafe_unretained objA = nil;

打印结果为:

A = <NSObject: 0x45f140e>
A = <NSObject: 0x45f140e>

奇怪,第二次打印结果正常,这是怎么回事?

当方法体出来后,objB强引用失效,自动释放自己持有的对象,这个时候没有强引用去持有这个对象,对象释放.所以objA变量表示的对象已经被废弃,变成悬垂指针,这是一个错误访问.所以这一次的打印只是碰巧正常运行而已.虽然访问了已经被废弃的对象,但是应用程序在个别运行状况下才会崩溃.

__autoreleasing

ARC下,autorelease是不能用的(这个修饰符是可以使用的),包括NSAutoreleasePool类也不能用,MRC下是这样使用的:

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

drain方法废弃正在使用的NSautoreleasePool对象的过程为:

- (void)drain
{
    [self dealloc];
}

- (void)dealloc
{
    [self emptyPool];
    [array release];
}

- (void)emptyPool
{
    for (id obj in array){
        [obj release];
    }
}

ARC下,该源代码写成如下这样:

@autoreleasepool {
    
    id __autoreleasing obj = [[NSObject alloc] init]; // ARC无效时会调用autorelease方法.另外,__autoreleasing修饰符一般是可以非显示地使用的
    
}

在使用alloc/new/copy/mutableCopy以外的方法来取得对象的时候,该对象会被注册到autorelpool.编译器会检查方法名是否以这几个单词开始,如果不是的则自动将返回的对象注册到autoreleasepoll.

另外,init方法返回值的对象不会注册到autoreleasepool.

不管是否使用了ARC,调试用的非公开函数_objc_autoreleasePoolPrint()都是可以使用的.不过有可能报错Implicit declaration of function - C99,这种情况可以通过Build Setting -> C Language Dialect修改为GNU99GNU89解决.

使用ARC的时候,对象型变量补鞥呢作为C语言结构体(struct/union)的成员.因为ARC把内存管理的工作交给编译器,那么编译器必须知道并管理对象的生存周期.要把对象型变量加入到结构体成员中时,可以强制转换为void *或者附加__unsafe_unretained修饰符(因为__unsafe_unretained修饰符修饰的变量不属于编译器的内存对象).

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

// 也可以反过来赋值
id obj2 = p;
[obj2 release];
**  ARC  **
// 要使用__bridge桥接转换
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id obj2 = (__bridge id)p;

相关文章

网友评论

      本文标题:iOS内存管理

      本文链接:https://www.haomeiwen.com/subject/dnqrwxtx.html