ARC下的内存管理与优化

作者: mysteryemm | 来源:发表于2017-07-21 17:59 被阅读457次

前言

最近闲来无事,温习下ARC下内存管理方面的知识,在此做个学习笔记,若有不对之处,还望各位童鞋不吝指正。

方法命名与内存的关系

通常情况下,为了体现更好的代码规范,方法的命名尽可能地达到见名知意的效果。如果我们声明一个创建对象的方法,那么有四个关键字需要额外注意下,就是allocnewcopymutableCopy。这里要分为两种情况进行讨论,暂且称之为方法命名的两种规则。
  1. 若方法名字是以allocnewcopymutableCopy这四个关键作为前缀,则方法返回的对象归调用者(caller)所有,也就是说caller要负责对创建的对象进行释放。
  2. 若方法名字不是以上述四个关键字作为前缀,则方法返回的对象不归caller所有,也就是说caller不需要负责对创建的对象进行释放,对象会被加到AutoreleasePool中自动释放。如果caller不想让对象被自动释放,那么就需要对其进行强引用(ARC下默认为strong相当于执行一次retain操作),然后再在适当的时候执行release操作对其释放。
  可能有的童鞋没有看明白是什么意思,不过没有关系,下面会通过具体的例子来进行阐述。如果到最后还是没有看明白,那么我可能会用三天时间来反思下我的文字表达能力[皱眉]~

解释与说明

1. 我们先来看第一种情况,即上述规则1。首先新建一个Person类,为了能够使用内存管理关键字来模拟ARC下编译器为我们设置的内存管理语义,故通过设置该编译单元(一个.m文件相当于一个编译单元)的Compiler Flags为-fno-objc-arc来禁用ARC机制,同时覆盖releasedealloc等方法。然后我们再在ARC文件中创建一个person对象,代码如下:

Caller
// ARC
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    if (YES) {  // if语句目的是让person对象的生命周期仅在当前作用域有效.
        Person *_person = [Person newPerson]; // 这里只对`new`关键字开头的方法进行测试, 其他三种方法可自行验证.
        NSLog(@"if语句执行中 %@", _person);
    }
    
    NSLog(@"if语句执行完");
    
}

@end
Callee
// MRC
@implementation Person

+ (Person *)newPerson {
    Person *person = [[Person alloc] init];
    return person;
}

- (instancetype)retain {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    return [super retain];
}

- (oneway void)release {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [super release];
}

- (void)dealloc {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [super dealloc];
}

@end

运行程序,控制台输出如下:

2017-07-24 00:13:33.889 MemoryManagerUnderARC[5499:456138] if语句执行中 <Person: 0x608000018010>
2017-07-24 00:13:33.889 MemoryManagerUnderARC[5499:456138] release
2017-07-24 00:13:33.889 MemoryManagerUnderARC[5499:456138] dealloc
2017-07-24 00:13:33.890 MemoryManagerUnderARC[5499:456138] if语句执行完

Caller中,由于创建对象方法的名字是以new作为前缀,所以我们创建的对象归_person所有 (_person实际上是一个指针变量,OC中我们常称之为对象,具体请参考这里)。而_person是个局部变量,所以作用域仅在if语句中有效,当if语句结束的时候,需要将其释放,如果不释放,那么在if语句之外就会因为无法访问该对象而引起内存泄露。�通过打印结果可看出,ARC下为我们自动执行release操作将对象释放。这里要注意一下,虽然ARC下变量的内存管理语义默认为strong,但是_person并没有执行retain操作,因为这种情况下创建的对象已经归它所有,所以它无需再进行一次retain操作。如果我们此时将_person的内存管理语义改成__weak或者是__unsafe_unretained,那么_person就不会拥有这个对象,该对象创建完就会立即释放,当然,编译器也会给我们警告提示。


2. 我们再来看第二种情况,即上述规则2。我们对代码进行修改,如下:

Caller
// ARC
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    if (YES) {  // if语句目的是让person对象的生命周期仅在当前作用域有效.
        Person *_person = [Person createPerson]; // 除上述四个关键字之外,随便使用一个前缀或者不用前缀.
        NSLog(@"if语句执行中 %@", _person);
    }
    
    NSLog(@"if语句执行完");
    
}

@end
Callee
// MRC
@implementation Person

+ (Person *)createPerson {
    Person *person = [[Person alloc] init];
    return [person autorelease]; //模拟ARC下代码,其实ARC下真正执行的并不是`autorelease`方法,而是`objc_autoreleaseReturnValue`,这里稍后会进行介绍。
}

- (instancetype)retain {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    return [super retain];
}

- (oneway void)release {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [super release];
}

- (void)dealloc {
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [super dealloc];
}

@end

再次运行程序,控制台输出结果如下:

2017-07-24 00:39:52.693 MemoryManagerUnderARC[5557:475107] retain
2017-07-24 00:39:52.693 MemoryManagerUnderARC[5557:475107] if语句执行中 <Person: 0x600000015550>
2017-07-24 00:39:52.693 MemoryManagerUnderARC[5557:475107] release
2017-07-24 00:39:52.694 MemoryManagerUnderARC[5557:475107] if语句执行完
2017-07-24 00:39:52.697 MemoryManagerUnderARC[5557:475107] release
2017-07-24 00:39:52.697 MemoryManagerUnderARC[5557:475107] dealloc

如规则2所描述的那样,创建的对象会被加到AutoreleasePool当中,_person无需负责该对象的释放操作。但是由于ARC下变量的内存管理语义默认为strong,所以_person执行了一次retain操作,表示不想让对象自动释放,那么当if语句执行完的时候,即_person作用域结束的时候,为了避免内存泄露,就必须得执行一次release操作来抵消刚才的那次retain操作。至于上述打印结果最后执行的那次release操作是由于当前runloop进入休眠时AutoreleasePool进行pop操作将自动释放池里的对象进行释放,通过设置断点可查看:

autorelease.png
  通过静态分析工具Analyze和Instruments对内存进行检测分析,没有发现内存问题,说明我们代码没毛病。看似完美无缺,但是细心的童鞋可能会有这样的疑问:既然_person不想让对象自动释放,那在创建对象的时候,为什么还要将对象添加到AutoreleasePool中?如果不加到AutoreleasePool中,那么_person也就无需执行一次retain操作来持有它,而只需要在作用域结束的时候执行一次release操作将其释放即可。
  所以,ARC下为了免除autoreleaseretain这两步多余的操作来提高性能,使用函数objc_autoreleaseReturnValue来替代autorelease、函数objc_retainAutoreleasedReturnValue来替代retain。这两个函数runtime源码中有介绍。

(1) objc_autoreleaseReturnValue函数内部实现如下:

id objc_autoreleaseReturnValue(id obj)
{
#if SUPPORT_RETURN_AUTORELEASE
    assert(tls_get_direct(AUTORELEASE_POOL_RECLAIM_KEY) == NULL);

    if (callerAcceptsFastAutorelease(__builtin_return_address(0))) {
        tls_set_direct(AUTORELEASE_POOL_RECLAIM_KEY, obj);
        return obj;
    }
#endif

    return objc_autorelease(obj);
}

该函数会通过检测caller的retain指令(至于如何检测该指令,大神孙源博客黑幕背后的Autorelease中有介绍),来判断caller是否想持有创建的对象。若是的话,就不会执行autorelease,而是将对象存储到TLS(线程局部存储)中。

(2)** objc_retainAutoreleasedReturnValue**函数内部实现如下:

id objc_retainAutoreleasedReturnValue(id obj)
{
#if SUPPORT_RETURN_AUTORELEASE
    if (obj == tls_get_direct(AUTORELEASE_POOL_RECLAIM_KEY)) {
        tls_set_direct(AUTORELEASE_POOL_RECLAIM_KEY, 0);
        return obj;
    }
#endif
    return objc_retain(obj);
}

该函数会先判断方法返回的对象和TLS中存储的是否一样,若一样,表示对象没有被加到AutoreleasePool中,则可以直接使用,否则就要执行一次retain操作。如此一来,就可以免除autoreleaseretain这两步操作,从而提高了性能。


还是在这种情况下,如果_person不想持有对象,我们将内存管理语义改为__unsafe_unretained,则对象就会因为AutoreleasePool的drain而自动释放。代码和打印结果如下:

  if (YES) {
        Person * __unsafe_unretained _person = [Person createPerson]; //ARC下这么写,相当于MRC下这么写Person * _person = [Person createPerson];
        NSLog(@"if语句执行中 %@", _person);
    }
    
    NSLog(@"if语句执行完");
2017-07-24 01:46:31.583 MemoryManagerUnderARC[5648:526071] if语句执行中 <Person: 0x60000001efb0>
2017-07-24 01:46:31.583 MemoryManagerUnderARC[5648:526071] if语句执行完
2017-07-24 01:46:31.586 MemoryManagerUnderARC[5648:526071] release //自动释放
2017-07-24 01:46:37.888 MemoryManagerUnderARC[5648:526071] dealloc

至此,对文章开头所说的那两种规则阐述完毕。路漫漫其修远兮,希望正在看的你能为在下点个喜欢,作为鼓舞和激励,在此十分感谢!

参考资料

https://en.wikipedia.org/wiki/Thread-local_storage
http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
https://opensource.apple.com/source/objc4/objc4-532.2/runtime/NSObject.mm

相关文章

  • ARC下的内存管理与优化

    前言 最近闲来无事,温习下ARC下内存管理方面的知识,在此做个学习笔记,若有不对之处,还望各位童鞋不吝指正。 方法...

  • 001性能优化-02-界面优化.md

    [TOC] 基础优化方向 使用 ARC 管理内存 在正确的地方使用 reuseIdentifiertablevie...

  • iOS开发之Autoreleasepool简介

    Autoreleasepool即自动释放池,是在ARC自动管理内存机制下用来管理程序中开辟的内存的,ARC工程每个...

  • iOS夯实:ARC时代的内存管理

    iOS夯实:ARC时代的内存管理 iOS夯实:ARC时代的内存管理

  • 《iOS开发进阶》阅读笔记(一)内存管理释疑

    ARC ARC能够解决iOS开发中90%的内存管理问题,但是另外还有10%的内存管理是需要手动管理的,主要就是与底...

  • ARC下内存管理

    ARC(Automatic Reference Counting):自动引用计数,可以说是WWDC2011和iOS...

  • ARC下的内存管理

    ARC的本质 ARC是编译器(时)特性,而不是运行时特性,更不是垃圾回收器(GC)。 Automatic Refe...

  • ARC下的内存管理

    前序:ARC是自动引用计数,MRC是引用计数。引用计数的原理是跟随OC的出生就存在的。 一、修饰符 1 __str...

  • ARC下的内存管理

    1.ARC下单对象内存管理 局部变量释放对象随之被释放 清空指针对象随之被释放 默认清空所有指针都是强指针 弱指针...

  • iOS面试技巧(2020年07月)

    问题和答案 什么是ARC?(ARC是为了解决什么问题而诞生的) ARC为自动内存管理,与之对应的是MRC(手动内存...

网友评论

    本文标题:ARC下的内存管理与优化

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