“理解”iOS内存管理

2,426 阅读14分钟

引言:
我们都知道Objective-C通过“引用计数”来管理对象释放。基本原理就是管理对象的持有者个数(引用计数),引用计数为0时释放对象。现在有ARC(自动引用计数),则无需我们自己显式持有(retain)和释放(release)对象,ARC通过对对像加上所有权修饰符(__strong等),编译器通过对象的所有权修饰符将会自动键入引用计数管理(根据所有权修饰符自动键入retain、release、autorelease)

本文主要叙述引用计数的实现原理,ARC和MRC在使用上的区别,以及编译器在ARC中为我们做了什么。

Objective-C对象的MRC

  • retain
  • release
  • autorelease

autorelease的实现

使用栈(后进先出)来管理NSAutoreleasePool对象,因此可以随时拿到最近(hotPage)的NSAutoreleasePool,调用对象的autorelease方法,会在hotPage中的内部数组将对象加入进去。当NSAutoreleasePool出栈时,调用内部数组中元素的release方法即可。

retain/release的实现

引用计数表:使用散列表实现引用计数表,key为对象的地址的散列值,value为引用计数和内存块地址。
retain:通过对象的地址在引用计数表中找到引用计数,如果retainCount超过最大值,则抛异常,否则retainCount加1。
release:通过对象的地址在引用计数表中找到引用计数,如果retainCount值为0,则抛异常。否则retainCount减1,如果retainCount减1后为0,则从引用计数表冲移除,并调用对象的dealloc方法。

Objective-C对象的ARC

Objective-C对象的ARC是通过所有权修饰符来管理对象的持有和释放。所有权修饰符一共有4种:

  • __strong 修饰符,默认的修饰符
  • __weak 修饰符
  • __unsafe_unretained 修饰符
  • __autoreleasing 修饰符

__strong修饰符的实现

取得自己生成并且持有对象:
使用ARC:

{
id obj1 = [NSObject new];
//相当于
//id __strong obj1 = [NSObject new];
}

不使用ARC:

{
id obj1 = [NSObject new];
//在变量作用域结束时插入release
[obj1 release];
}

取得非自己生成并持有对象:
使用ARC:

- (void)create {
    test = [self object];
    NSLog(@"ARC------------------ARC");
    NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}

- (id)object {
    id obj = [[MyObject alloc] init];
    return obj;
}

- (void)printRetainCount {
    NSLog(@"ARC------------------ARC");
    NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}

不使用ARC:

- (void)create {
    if (test) {
        NSLog(@"MRC------------------MRC");
        NSLog(@"release");
        [test release];
    }
    test = [self object];
    [test retain];
    NSLog(@"MRC------------------MRC");
    NSLog(@"after create count = %ld", [test retainCount]);
}

- (id)object {
    id obj = [[MyObject alloc] init];
    [obj autorelease];
    return obj;
}

- (void)printRetainCount {
    NSLog(@"MRC------------------MRC");
    NSLog(@"retain count = %ld", [test retainCount]);
}

打印结果:

**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:47.545 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:47.546 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:49.602 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:49.603 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:35:51.212 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.213 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] after create count = 1**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.214 ARCLearn[53676:16388070] release**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] myobject dealloc**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:35:51.215 ARCLearn[53676:16388070] after create count = 2**
**2016-12-13 15:36:03.540 ARCLearn[53676:16388070] ARC------------------ARC**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] retain count = 1**
**2016-12-13 15:36:03.542 ARCLearn[53676:16388070] MRC------------------MRC**
**2016-12-13 15:36:03.543 ARCLearn[53676:16388070] retain count = 1**

可以看到使用ARC的对象持有,在返回对象时,并没有把对象注册到autoreleasepool中,下面为ARC的autoreleasepool打印:

**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 13 releases pending.**
**objc[54013]: [0x7fa3c9811000]  ................  PAGE  (hot) (cold)**
**objc[54013]: [0x7fa3c9811038]  ################  POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040]    0x60800002e3c0  __NSCFString**
**objc[54013]: [0x7fa3c9811048]  ################  POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050]    0x7fa3ca800850  UIScreen**
**objc[54013]: [0x7fa3c9811058]    0x7fa3ca800850  UIScreen**
**objc[54013]: [0x7fa3c9811060]    0x6000002712c0  __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068]    0x7fa3c8600bf0  UIWindow**
**objc[54013]: [0x7fa3c9811070]    0x60000008e6f0  __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078]    0x6000000567d0  __NSSetM**
**objc[54013]: [0x7fa3c9811080]    0x600000052de0  __NSSetM**
**objc[54013]: [0x7fa3c9811088]    0x600000053b30  __NSSetM**
**objc[54013]: [0x7fa3c9811090]    0x600000271640  __NSCFString**
**objc[54013]: [0x7fa3c9811098]    0x600000271640  __NSCFString**
**objc[54013]: ##############**

下面为MRC的autoreleasepool打印:

**objc[54013]: ##############**
**objc[54013]: AUTORELEASE POOLS for thread 0x10c6ba3c0**
**objc[54013]: 14 releases pending.**
**objc[54013]: [0x7fa3c9811000]  ................  PAGE  (hot) (cold)**
**objc[54013]: [0x7fa3c9811038]  ################  POOL 0x7fa3c9811038**
**objc[54013]: [0x7fa3c9811040]    0x60800002e3c0  __NSCFString**
**objc[54013]: [0x7fa3c9811048]  ################  POOL 0x7fa3c9811048**
**objc[54013]: [0x7fa3c9811050]    0x7fa3ca800850  UIScreen**
**objc[54013]: [0x7fa3c9811058]    0x7fa3ca800850  UIScreen**
**objc[54013]: [0x7fa3c9811060]    0x6000002712c0  __NSCFDictionary**
**objc[54013]: [0x7fa3c9811068]    0x7fa3c8600bf0  UIWindow**
**objc[54013]: [0x7fa3c9811070]    0x60000008e6f0  __NSMallocBlock__**
**objc[54013]: [0x7fa3c9811078]    0x6000000567d0  __NSSetM**
**objc[54013]: [0x7fa3c9811080]    0x600000052de0  __NSSetM**
**objc[54013]: [0x7fa3c9811088]    0x600000053b30  __NSSetM**
**objc[54013]: [0x7fa3c9811090]    0x600000271640  __NSCFString**
**objc[54013]: [0x7fa3c9811098]    0x600000271640  __NSCFString**
**objc[54013]: [0x7fa3c98110a0]    0x608000017200  MyObject**
**objc[54013]: ##############**

在autoreleasepool里有MyObject对象。
为什么ARC没有把对象放到autoreleasepool里了?它是怎样持有非自己生成的对象的?
代码:

{
    id __strong obj = [NSMutableArray array];
}

转换为编译器模拟代码:

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

objc_retainAutoreleasedReturnValue函数,顾名思义:让obj持有(retain)池中(autoreleased)返回的值(retuenValue);
代码:

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

转换为编译器模拟代码:

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

objc_autoreleaseReturnValue函数,顾名思义:把obj注册在池中(调用obj的autorelease方法),并返回。
因此按照上面的理解objc_autoreleaseReturnValue将返回对象注册到池子中,objc_retainAutoreleasedReturnValue`持有池中的对象。

但是事实不是那么简单:
但是objc_autoreleaseReturnValue远远不是autorelease那么简单。objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用了objc_retainAutoreleasedReturnValue,那就不会将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。
因此objc_retainAutoreleasedReturnValue也不是retain那么简单,即使放回对象不注册到autoreleasepool中,也能正确的获取对象。
通过objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。

__weak修饰符的实现

现在我们添加一个weakTest变量,使用weak所有权修饰符
在ARC中实现:

@interface ARC() {
    id test;
    id __weak weakTest;
}

@end

@implementation ARC

- (void)create {
    test = [self object];
    weakTest = test;
    _objc_autoreleasePoolPrint();
    NSLog(@"ARC------------------ARC");
    NSLog(@"after create count = %ld", _objc_rootRetainCount(test));
}

- (id)object {
    id obj = [[MyObject alloc] init];
    return obj;
}

- (void)printRetainCount {
    NSLog(@"ARC------------------ARC");
    NSLog(@"retain count = %ld", _objc_rootRetainCount(test));
}

@end

打印结果:

**2016-12-13 16:24:30.934 ARCLearn[55038:16489628] ARC------------------ARC**
**2016-12-13 16:24:30.935 ARCLearn[55038:16489628] after create count = 1**

由此可以看出__weak并没有增加引用个数。
添加empty函数,使test为nil:

- (void)empty {
    if (weakTest) {
        NSLog(@"weak is %p", weakTest);
    }
    test = nil;
    if (!weakTest) {
        NSLog(@"weak change to nil");
    }
}

打印结果:

**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] weak is 0x608000011530**
**2016-12-13 16:33:06.301 ARCLearn[55349:16509066] myobject dealloc**
**2016-12-13 16:33:06.302 ARCLearn[55349:16509066] weak change to nil**

由此可以看出若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量
是如何实现将nil值赋值给引用为0的__weak对象?

{
    id __weak obj1 = obj;
}

转换为编译器模拟代码:

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

其中objc_initWeak(&obj1,obj)的实现:

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

objc_destroyWeak的实现:

objc_storyWeak(&obj1, 0);

所以合在一起是:

{
    id obj1;
    obj1 = 0;
    objc_storyWeak(&obj1, obj);
    objc_storyWeak(&obj1, 0);
}

其中objc_storyWeak就是针对weak表(散列表)操作:

  • 注册到表中:objc_storeWeak函数把第二个参数的地址(散列值)作为键,将第一个参数的地址加入链表中(键值为一个链表,由于一个对象可同时赋值给多个附有__weak修饰符的变量),完成weak表的注册。
  • 从表中移除:如果第二个参数为0,则把第二个参数的地址的键从weak表中删除,并且将键值置为nil(将链表中的值都置为nil)。
  • 从表中查询:使用weak表,将废弃对象的地址作为键进行检索,就能告诉的获取对应的附有__weak修饰符的变量的地址。

废弃引用为0的对象的流程:

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

__autoreleasing修饰符的实现

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

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

    id __strong obj1 = [[NSObject alloc] init];
    id __autoreleasing obj2 = obj1;

    id __strong obj3 = [NSMutableArray array];
    id __autoreleasing obj4 = obj3;
}

ARC无效:

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

    id obj1 = [[NSObject alloc] init];
    id obj2 = obj1;
    [obj2 retain];
    [obj2 autorelease];

    id obj3 = [NSMutableArray array];
    [obj3 retain];
    id obj4 = obj3;
    [obj4 retain];
    [obj4 autorelease];
    [obj3 release];
}

注意:显式的附加autoreleasing修饰符同显式地添加strong修饰符一样罕见。我们经常非显式的使用__autoreleasing修饰符。

隐式使用__autoreleasing:

  • 编译器检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是则自动将返回值的对象注册到autoreleasepool(objc_retainAutoreleasedReturnValue和objc_autoreleaseReturnValue成对出现时就不会注册到autoreleasepool)。
  • id的指针或对象的指针在没有显式指定时会被附加上__autoreleasing修饰符。

如:

NSError *error = nil;
BOOL result = [obj performOperationWithError: &error];

performOperationWithError声明为:

- (BOOL)performOperationWithError:(NSError **)error;

自动添加__autoreleasing的代码:

- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error {
    /* 错误发生 */
    *error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
    return NO;
}

非ARC的实现:

- (BOOL)performOperationWithError:(NSError **)error {
    /* 错误发生 */
    *error = [[NSError alloc] initwithDomain:MyAppDomain code:errorCode userInfo:nil];
    [*error autorelease];
    return NO;
}

因为声明为NSError __autoreleasing 类型的error作为*error被赋值,所以能够返回注册到autoreleasepool中的对象。

注意:赋值给对象指针时,所有权修饰符必须一致。
编译器会自动加上所有权修饰符:

NSError * __strong error = nil;
NSError * __autoreleasing *pError = &error;

所有权修饰符不一致会产生编译错误:
Initializing 'NSError *__autoreleasing *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer

__unsafe_unretained 修饰符

unsafe_unretained修饰符正如其名unsafe所示,是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但附有unsafe_unretained修饰符的变量不属于编译器的内存管理对象。
ARC实现:

id obj = [[NSObject alloc] init];
id __unsafe_unretained obj1 = obj;

非ARC实现:

id obj = [[NSObject alloc] init];
id obj1 = obj;

所以就是...编译器啥都不做。这样当对象的引用计数为0,被废弃后,obj1就是不安全的了(悬垂指针),因为不会被置为nil。

注意:使用时确保对象确实存在。

什么时候使用__unsafe_unretained?

  • 在iOS4的应用程序中,必须使用unsafe_unretained修饰的变量,来替代weak修饰符。
  • 不支持__weak修饰符的类,例如NSMachPork类,因为这些类重写了retain/release并实现该类独自的引用计数机制。
  • allowsWeakReference/retainWeakReference实例方法(没有写入NSObject接口说明文档中)返回NO的情况。

Core Foundation对象的ARC

Core Foundation对象主要使用在用C语言编写的Core Foundation框架中,并使用引用计数的对象。

  • 在ARC无效时,Core Foundation框架中的retain/release分别是CFRetain/CFRelease。
  • Foundation框架的API生成并持有的对象可以用Core Foundation框架的API释放。当然,反过来也是可以的。
  • 因为Core Foundation对象与Objective-C对象没有区别,所以ARC无效时,只用简单的C语言的转换也能实现互换。因为这种转换不需要使用额外的CPU资源,因此也被称为“免费桥”(Toll-free Bridge)。

ARC无效时:

id obj = [[NSObject alloc] init];
void *p = obj; //void *相当与oc的id

id o = p;
[o release];

ARC有效时,会引起编译错误,要使用“__bridge”转换。
ARC有效时的代码:

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

id o = (__bridge id)p;
[o release];

注意:bridge的安全性与赋值给unsafe_unretained修饰符相近,甚至会更低,如果管理时不注意赋值对象的所有者,就会因悬垂指针而导致程序崩溃。

__bridge转换中还有另两中转换,分别为:

  • bridge_retained转换,相当于赋值给strong变量。
    ARC有效:
    id obj = [[NSObject alloc] init];
    void *p = (__bridge_retained void*)obj;
    ARC无效:
    id obj = [[NSObject alloc] init];
    void *p = obj;
    [(id)p retain];
  • bridge_transfer转换,相当于赋值给strong变量后,该变量随之释放。
    ARC有效
    void *p = 0;
    id obj = (__bridge_transfer id)p;
    ARC无效
    void *p = 0;
    id obj = (id)p;
    [obj retain];
    [(id)p release];

简书个人主页:www.jianshu.com/users/b92ab…