iOS - 老生常谈内存管理(三):ARC 面世

前言

  ARC全称Automatic Reference Counting,自动引用计数内存管理,是苹果在 iOS 五、OS X Lion 引入的新的内存管理技术。ARC经过LLVM编译器和Runtime协做来进行自动管理内存。LLVM编译器会在编译时在合适的地方为 OC 对象插入retainreleaseautorelease代码来自动管理对象的内存,省去了在MRC手动引用计数下手动插入这些代码的工做,减轻了开发者的工做量,让开发者能够专一于应用程序的代码、对象图以及对象间的关系上。
  下图是苹果官方文档给出的从MRCARC的转变。html

摘要

ARC的工做原理是在编译时添加相关代码,以确保对象可以在必要时存活,但不会一直存活。从概念上讲,它经过为你添加适当的内存管理方法调用来遵循与MRC相同的内存管理规则。算法

为了让编译器生成正确的代码,ARC限制了一些方法的使用以及你使用桥接(toll-free bridging)的方式,请参阅《Managing Toll-Free Bridging》章节。ARC还为对象引用和属性声明引入了新的生命周期修饰符。express

ARCXcode 4.2 for OS X v10.6 and v10.7 (64-bit applications)以及iOS 4 and iOS 5应用程序中提供支持。但OS X v10.6 and iOS 4不支持weak弱引用。编程

Xcode 提供了一个迁移工具,能够自动将MRC代码转换为ARC代码(如删除retainrelease调用),而不用从新再建立一个项目(选择 Edit > Convert > To Objective-C ARC)。迁移工具会将项目中的全部文件转换为使用ARC的模式。若是对于某些文件使用MRC更方便的话,你能够选择仅在部分文件中使用ARC数组

ARC 概述

ARC会分析对象的生存期需求,并在编译时自动插入适当的内存管理方法调用的代码,而不须要你记住什么时候使用retainreleaseautorelease方法。编译器还会为你生成合适的dealloc方法。通常来讲,若是你使用ARC,那么只有在须要与使用MRC的代码进行交互操做时,传统的 Cocoa 命名约定才显得重要。安全

Person 类的完整且正确的实现可能以下所示:多线程

@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@property NSNumber *yearOfBirth;
@property Person *spouse;
@end
 
@implementation Person
@end
复制代码

(默认状况下,对象属性是strong。关于strong请参阅《全部权修饰符》章节。)app

使用ARC,你能够这样实现 contrived 方法,以下所示:框架

- (void)contrived {
    Person *aPerson = [[Person alloc] init];
    [aPerson setFirstName:@"William"];
    [aPerson setLastName:@"Dudney"];
    [aPerson setYearOfBirth:[[NSNumber alloc] initWithInteger:2011]];
    NSLog(@"aPerson: %@", aPerson);
}
复制代码

ARC会负责内存管理,所以 Person 和 NSNumber 对象都不会泄露。ide

你还能够这样安全地实现 Person 的 takeLastNameFrom: 方法,以下所示:

- (void)takeLastNameFrom:(Person *)person {
    NSString *oldLastname = [self lastName];
    [self setLastName:[person lastName]];
    NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);
}
复制代码

ARC会确保在 NSLog 语句以前不释放 oldLastName 对象。

ARC 实施新规则

ARC引入了一些在使用其余编译器模式时不存在的新规则。这些规则旨在提供彻底可靠的内存管理模型。有时候,它们直接地带来了最好的实践体验,也有时候它们简化了代码,甚至在你丝毫没有关注内存管理问题的时候帮你解决了问题。在ARC下必须遵照如下规则,若是违反这些规则,就会编译错误。

  • 不能使用 retain / release / retainCount / autorelease
  • 不能使用 NSAllocateObject / NSDeallocateObject
  • 须遵照内存管理的方法命名规则
  • 不能显式调用 dealloc
  • 使用 @autoreleasepool 块替代 NSAutoreleasePool
  • 不能使用区域(NSZone)
  • 对象型变量不能做为 C 语言结构体(struct / union)的成员
  • 显式转换 “id” 和 “void *” —— 桥接

不能使用 retain / release / retainCount / autorelease

ARC下,禁止开发者手动调用这些方法,也禁止使用@selector(retain)@selector(release)等,不然编译不经过。但你仍然能够对 Core Foundation 对象使用CFRetainCFRelease等相关函数(请参阅《Managing Toll-Free Bridging》章节)。

不能使用 NSAllocateObject / NSDeallocateObject

ARC下,禁止开发者手动调用这些函数,不然编译不经过。 你可使用alloc建立对象,而Runtime会负责dealloc对象。

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

MRC下,经过 alloc / new / copy / mutableCopy 方法建立对象会直接持有对象,咱们定义一个 “建立并持有对象” 的方法也必须以 alloc / new / copy / mutableCopy 开头命名,而且必须返回给调用方所应当持有的对象。若是在ARC下须要与使用MRC的代码进行交互,则也应该遵照这些规则。

为了容许与MRC代码进行交互操做,ARC对方法命名施加了约束: 访问器方法的方法名不能以new开头。这意味着你不能声明一个名称以new开头的属性,除非你指定一个不一样的getterName

// Won't work:
@property NSString *newTitle;
 
// Works:
@property (getter = theNewTitle) NSString *newTitle;
复制代码

不能显式调用 dealloc

不管在MRC仍是ARC下,当对象引用计数为 0,系统就会自动调用dealloc方法。大多数状况下,咱们会在dealloc方法中移除通知或观察者对象等。

MRC下,咱们能够手动调用dealloc。但在ARC下,这是禁止的,不然编译不经过。

MRC下,咱们实现dealloc,必须在实现末尾调用[super dealloc]

// MRC
- (void)dealloc
{
    // 其余处理
    [super dealloc];
}
复制代码

而在ARC下,ARC会自动对此处理,所以咱们没必要也禁止写[super dealloc],不然编译错误。

// ARC
- (void)dealloc
{
    // 其余处理
    [super dealloc]; // 编译错误:ARC forbids explicit message send of 'dealloc'
}
复制代码

使用 @autoreleasepool 块替代 NSAutoreleasePool

ARC下,自动释放池应使用@autoreleasepool,禁止使用NSAutoreleasePool,不然编译错误。

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 
复制代码
error:'NSAutoreleasePool' is unavailable: not available in automatic reference counting mode
复制代码

关于@autoreleasepool的原理,能够参阅《iOS - 聊聊 autorelease 和 @autoreleasepool》

不能使用区域(NSZone)

对于如今的运行时系统(编译器宏 __ OBJC2 __ 被设定的环境),无论是MRC仍是ARC下,区域(NSZone)都已单纯地被忽略。

NSZone 《Objective-C 高级编程:iOS 与 OS X 多线程和内存管理》书中对 NSZone 作了以下介绍:

NSZone 是为防止内存碎片化而引入的结构。对内存分配的区域自己进行多重化管理,根据使用对象的目的、对象的大小分配内存,从而提升了内存管理的效率。 可是,如今的运行时系统已经忽略了区域的概念。运行时系统中的内存管理自己已极具效率,使用区域来管理内存反而会引发内存使用效率低下以及源代码复杂化等问题。 下图是使用多重区域防止内存碎片化的例子:

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

C 语言的结构体(struct / union)成员中,若是存在 Objective-C 对象型变量,便会引发编译错误。

备注: 我使用的时候编译经过,难道编译器优化了吗?

struct Data {
    NSMutableArray *mArray;
};
复制代码
error:ARC forbids Objective-C objs in struct or unions NSMutableArray *mArray;
复制代码

虽然是 LLVM 编译器 3.0,但不论怎样,C 语言的规约上没有方法来管理结构体成员的生存周期。由于ARC把内存管理的工做分配给编译器,因此编译器必须可以知道并管理对象的生存周期。例如 C 语言的自动变量(局部变量)可以使用该变量的做用域管理对象。可是对于 C 语言的结构体成员来讲,这在标准上就是不可实现的。所以,必需要在结构体释放以前将结构体中的对象类型的成员释放掉,可是编译器并不能可靠地作到这一点,因此对象型变量不能做为 C 语言结构体的成员。

这个问题有如下三种解决方案:

  • ① 使用 Objective-C 对象替代结构体。这是最好的解决方案。

若是你仍是坚持使用结构体,并把对象型变量加入到结构体成员中,可使用如下两种方案:

  • ② 将 Objective-C 对象经过Toll-Free Bridging强制转换为void *类型,请参阅《Managing Toll-Free Bridging》章节。
  • ③ 对 Objective-C 对象附加__unsafe_unretained修饰符。
struct Data {
    NSMutableArray __unsafe_unretained *mArray;
};
复制代码

附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。若是管理时不注意赋值对象的全部者,便有可能遭遇内存泄漏或者程序崩溃。这点在使用时应多加注意。

显式转换 “id” 和 “void *” —— 桥接

MRC下,咱们能够直接在 idvoid * 变量之间进行强制转换。

id obj = [[NSObject alloc] init];
    void *p = obj;
    id o = p;
    [o release];
复制代码

但在ARC下,这样会引发编译报错:在Objective-C指针类型idC指针类型void *之间进行转换须要使用Toll-Free Bridging,请参阅《Managing Toll-Free Bridging》章节。

id obj = [[NSObject alloc] init];
    void *p = obj; // error:Implicit conversion of Objective-C pointer type 'id' to C pointer type 'void *' requires a bridged cast
    id o = p;      // error:Implicit conversion of C pointer type 'void *' to Objective-C pointer type 'id' requires a bridged cast
    [o release];   // error:'release' is unavailable: not available in automatic reference counting mode
复制代码

全部权修饰符

ARC为对象和弱引用引入了几个新的生命周期修饰符(咱们称为 “全部权修饰符”)。弱引用weak不会延长它指向的对象的生命周期,而且该对象没有强引用(即dealloc)时自动置为nil。 你应该利用这些修饰符来管理程序中的对象图。特别是,ARC不能防止强引用循环(之前称为Retain Cycles,请参阅《从 MRC 提及 —— 使用弱引用来避免 Retain Cycles》章节)。明智地使用弱引用weak将有助于确保你不会建立循环引用。

属性关键字

ARC中引入了新的属性关键字strongweak,以下所示:

// 如下声明同:@property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
 
// 如下声明相似于:@property(assign)MyClass *myObject;
// 不一样的是,若是 MyClass 实例被释放,属性值赋值为 nil,而不像 assign 同样产生悬垂指针。
@property(weak) MyClass *myObject;
复制代码

strongweak属性关键字分别对应__strong__weak全部权修饰符。在ARC下,strong是对象类型的属性的默认关键字。

ARC中,对象类型的变量都附有全部权修饰符,总共有如下 4 种。

__strong
__weak
__unsafe_unretained
__autoreleasing
复制代码
  • __strong是默认修饰符。只要有强指针指向对象,对象就会保持存活。
  • __weak指定一个不使引用对象保持存活的引用。当一个对象没有强引用时,弱引用weak会自动置为nil
  • __unsafe_unretained指定一个不使引用对象保持存活的引用,当一个对象没有强引用时,它不会置为nil。若是它引用的对象被销毁,就会产生悬垂指针。
  • __autoreleasing用于表示经过引用(id *)传入,并在返回时(autorelease)自动释放的参数。

在对象变量的声明中使用全部权修饰符时,正确的格式为:

ClassName * qualifier variableName;
复制代码

例如:

MyClass * __weak myWeakReference;
    MyClass * __unsafe_unretained myUnsafeReference;
复制代码

其它格式在技术上是不正确的,但编译器会 “原谅”。也就是说,以上才是标准写法。

__strong

__strong修饰符为强引用,会持有对象,使其引用计数 +1。该修饰符是对象类型变量的默认修饰符。若是咱们没有明确指定对象类型变量的全部权修饰符,其默认就为__strong修饰符。

id obj = [NSObject alloc] init];
    // -> id __strong obj = [NSObject alloc] init];
复制代码

__weak

若是单单靠__strong完成内存管理,那必然会发生循环引用的状况形成内存泄漏,这时候__weak就出来解决问题了。 __weak修饰符为弱引用,不会持有对象,对象的引用计数不会增长。__weak能够用来防止循环引用。

如下单纯地使用__weak修饰符修饰变量,编译器会给出警告,由于NSObject的实例建立出来没有强引用,就会当即释放。

id __weak weakObj = [[NSObject alloc] init]; // ⚠️Assigning retained object to weak variable; object will be released after assignment
    NSLog(@"%@", obj);
    // (null)
复制代码

如下NSObject的实例已有强引用,再赋值给__weak修饰的变量就不会有警告了。

id __strong strongObj = [[NSObject alloc] init];
    id __weak weakObj = strongObj;
复制代码

当对象被dealloc时,指向该对象的__weak变量会被赋值为nil。(具体的执行过程请参阅:《iOS - 老生常谈内存管理(四):源码分析内存管理方法》

备注:__weak仅在ARC中才能使用,在MRC中是使用__unsafe_unretained修饰符来代替。

__unsafe_unretained

__unsafe_unretained修饰符的特色正如其名所示,不安全且不会持有对象。

注意: 尽管ARC内存管理是编译器的工做,可是附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。这一点在使用时要注意。

“不会持有对象” 这一特色使它和__weak的做用类似,能够防止循环引用。
“不安全“ 这一特色是它和__weak的区别,那么它不安全在哪呢?

咱们来看代码:

id __weak weakObj = nil;
    id __unsafe_unretained uuObj = nil;
    {
        id __strong strongObj = [[NSObject alloc] init];
        weakObj = strongObj;
        unsafeUnretainedObj = strongObj;
        NSLog(@"strongObj:%@", strongObj);
        NSLog(@"weakObj:%@", weakObj);
        NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj);
    }
    NSLog(@"-----obj dealloc-----");
    NSLog(@"weakObj:%@", weakObj);
    NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj); // Crash:EXC_BAD_ACCESS

/* strongObj:<NSObject: 0x6000038f4340> weakObj:<NSObject: 0x6000038f4340> unsafeUnretainedObj:<NSObject: 0x6000038f4340> -----obj dealloc----- weakObj:(null) (lldb) */
复制代码

以上代码运行崩溃EXC_BAD_ACCESS。缘由是__unsafe_unretained修饰的对象在被销毁以后,指针仍然指向原对象地址,咱们称它为 “悬垂指针”。这时候若是继续经过指针访问原对象的话,就会致使Crash。而__weak修饰的对象在被释放以后,会将指向该对象的全部__weak指针变量全都置为nil。这就是__unsafe_unretained不安全的缘由。因此,在使用__unsafe_unretained修饰符修饰的对象时,须要确保它未被销毁。

Q: 既然 __weak 更安全,那么为何已经有了 __weak 还要保留 __unsafe_unretained ?

  • __weak仅在ARC中才能使用,而MRC只能使用__unsafe_unretained
  • __unsafe_unretained主要跟 C 代码交互;
  • __weak对性能会有必定的消耗,当一个对象dealloc时,须要遍历对象的weak表,把表里的全部weak指针变量值置为nil,指向对象的weak指针越多,性能消耗就越多。因此__unsafe_unretained__weak快。当明确知道对象的生命周期时,选择__unsafe_unretained会有一些性能提高。

    A 持有 B 对象,当 A 销毁时 B 也销毁。这样当 B 存在,A 就必定会存在。而 B 又要调用 A 的接口时,B 就能够存储 A 的__unsafe_unretained指针。 好比,MyViewController 持有 MyView,MyView 须要调用 MyViewController 的接口。MyView 中就能够存储__unsafe_unretained MyViewController *_viewController

    虽然这种性能上的提高是很微小的。但当你很清楚这种状况下,__unsafe_unretained也是安全的,天然能够快一点就是一点。而当状况不肯定的时候,应该优先选用__weak

__autoreleasing

自动释放池

首先讲一下自动释放池,在ARC下已经禁止使用NSAutoreleasePool类建立自动释放池,而用@autoreleasepool替代。

  • MRC下可使用NSAutoreleasePool或者@autoreleasepool。建议使用@autoreleasepool,苹果说它比NSAutoreleasePool快大约六倍。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // Code benefitting from a local autorelease pool.
    [pool release]; // [pool drain]
复制代码

Q: 释放NSAutoreleasePool对象,使用[pool release][pool drain]的区别?

Objective-C 语言自己是支持 GC 机制的,但有平台局限性,仅限于 MacOS 开发中,iOS 开发用的是 RC 机制。在 iOS 的 RC 环境下[pool release][pool drain]效果同样,但在 GC 环境下drain会触发 GC 而release不作任何操做。使用[pool drain]更佳,一是它的功能对系统兼容性更强,二是这样能够跟普通对象的release区别开。(注意:苹果在引入ARC时称,已在 OS X Mountain Lion v10.8 中弃用GC机制,而使用ARC替代)

  • ARC下只能使用@autoreleasepool
@autoreleasepool {
        // Code benefitting from a local autorelease pool.
    }
复制代码

关于@autoreleasepool的底层原理,能够参阅《iOS - 聊聊 autorelease 和 @autoreleasepool》

__autoreleasing 使用

MRC中咱们能够给对象发送autorelease消息来将它注册到autoreleasepool中。而在ARCautorelease已禁止调用,咱们可使用__autoreleasing修饰符修饰对象将对象注册到autoreleasepool中。

@autoreleasepool {
        id __autoreleasing obj = [[NSObject alloc] init];
    }
复制代码

以上代码在MRC中等价于:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain];
    // 或者
    @autoreleasepool {
        id obj = [[NSObject alloc] init];
        [obj autorelease];
    }
复制代码
__autoreleasing 是二级指针类型的默认修饰符

前面咱们说过,对象指针的默认全部权修饰符是__strong。 而二级指针类型(ClassName **id *)的默认全部权修饰符是__autoreleasing。若是咱们没有明确指定二级指针类型的全部权修饰符,其默认就会附加上__autoreleasing修饰符。

好比,咱们常常会在开发中使用到NSError打印错误信息,咱们一般会在方法的参数中传递NSError对象的指针。如NSStringstringWithContentsOfFile类方法,其参数NSError **使用的是__autoreleasing修饰符。

NSString *str = [NSString stringWithContentsOfFile:<#(nonnull NSString *)#>
                                              encoding:<#(NSStringEncoding)#> 
                                              error:<#(NSError *__autoreleasing _Nullable * _Nullable)#>];
复制代码

示例:咱们声明一个参数为NSError **的方法,但不指定其全部权修饰符。

- (BOOL)performOperationWithError:(NSError **)error;
复制代码

接着咱们尝试调用该方法,发现智能提示中的参数NSError **附有__autoreleasing修饰符。可见,若是咱们没有明确指定二级指针类型的全部权修饰符,其默认就会附加上__autoreleasing修饰符。

NSError *error = nil;
    BOOL result = [self performOperationWithError:<#(NSError *__autoreleasing *)#>];
复制代码
注意

须要注意的是,赋值给二级指针类型时,全部权修饰符必须一致,不然会编译错误。

NSError *error = nil;
    NSError **error1 = &error;                 // error:Pointer to non-const type 'NSError *' with no explicit ownersh
    NSError *__autoreleasing *error2 = &error; // error:Initializing 'NSError *__autoreleasing *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer
    NSError *__weak *error3 = &error;          // error:Initializing 'NSError *__weak *' with an expression of type 'NSError *__strong *' changes retain/release properties of pointer
    NSError *__strong *error4 = &error;        // 编译经过
复制代码
NSError *__weak error = nil;
    NSError *__weak *error1 = &error;          // 编译经过
复制代码
NSError *__autoreleasing error = nil;
    NSError *__autoreleasing *error1 = &error; // 编译经过
复制代码

咱们前面说过,二级指针类型的默认修饰符是__autoreleasing。那为何咱们调用方法传入__strong修饰的参数就能够编译经过呢?

NSError *__strong error = nil;
    BOOL result = [self performOperationWithError:<#(NSError *__autoreleasing *)#>];
复制代码

其实,编译器自动将咱们的代码转化成了如下形式:

NSError *__strong error = nil;
    NSError *__autoreleasing tmp = error;
    BOOL result = [self performOperationWithError:&tmp];
    error = tmp;
复制代码

可见,当局部变量声明(__strong)和参数(__autoreleasing)之间不匹配时,会致使编译器建立临时变量。你能够将显示地指定局部变量全部权修饰符为__autoreleasing或者不显式指定(由于其默认就为__autoreleasing),或者显示地指定参数全部权修饰符为__strong,来避免编译器建立临时变量。

- (BOOL)performOperationWithError:(NSError *__strong *)error;
复制代码

可是在MRC引用计数内存管理规则中:使用alloc/new/copy/mutableCopy等方法建立的对象,建立并持有对象;其余状况建立对象但并不持有对象。为了在使用参数得到对象时,遵循此规则,咱们应该指定二级指针类型参数修饰符为__autoreleasing

《从 MRC 提及 —— 你不持有经过引用返回的对象》章节中也说到,Cocoa 中的一些方法指定经过引用返回对象(即,它们采用ClassName **id *类型的参数),常见的就是使用NSError对象。当你调用这些方法时,你不会建立该NSError对象,所以你不持有该对象,也无需释放它。而__strong表明持有对象,所以应该使用__autoreleasing

另外,咱们在显示指定__autoreleasing修饰符时,必须注意对象变量要为自动变量(包括局部变量、函数以及方法参数),不然编译不经过。

static NSError __autoreleasing *error = nil; // Global variables cannot have __autoreleasing ownership
复制代码

使用全部权修饰符来避免循环引用

前面已经说过__weak__unsafe_unretained修饰符能够用来循环引用,这里再来啰嗦几句。

若是两个对象互相强引用,就产生了循环引用,致使两个对象都不能被销毁,内存泄漏。或者多个对象,每一个对象都强引用下一个对象直到回到第一个,产生大环循环引用,这些对象也均不能被销毁。

ARC中,“循环引用” 是指两个对象都经过__strong持有对方。

解决 “循环引用” 问题就是采用 “断环” 的方式,让其中一方持有另外一方的弱引用。同MRC,父对象对它的子对象持有强引用,而子对象对父对象持有弱引用。

ARC中,“弱引用” 是指__weak__unsafe_unretained

delegate 避免循环引用

delegate避免循环引用,就是在委托方声明delegate属性时,使用weak关键字。

@property (nonatomic, weak) id<protocolName> delegate;
复制代码
block 避免循环引用

Q: 为何 block 会产生循环引用?

  • ① 相互循环引用: 若是当前block对当前对象的某一成员变量进行捕获的话,可能会对它产生强引用。根据block的变量捕获机制,若是block被拷贝到堆上,且捕获的是对象类型的auto变量,则会连同其全部权修饰符一块儿捕获,因此若是对象是__strong修饰,则block会对它产生强引用(若是block在栈上就不会强引用)。而当前block可能又因为当前对象对其有一个强引用,就产生了相互循环引用的问题;
  • ② 大环引用: 咱们若是使用__block的话,在ARC下可能会产生循环引用(MRC则不会)。因为__block修饰符会将变量包装成一个对象,若是block被拷贝到堆上,则会直接对__block变量产生强引用,而__block若是修饰的是对象的话,会根据对象的全部权修饰符作出相应的操做,造成强引用或者弱引用。若是对象是__strong修饰(如__block id x),则__block变量对它产生强引用(在MRC下则不会),若是这时候该对象是对block持有强引用的话,就产生了大环引用的问题。在ARC下能够经过断环的方式去解除循环引用,能够在block中将指针置为nilMRC不会循环引用,则不用解决)。可是有一个弊端,若是该block一直得不到调用,循环引用就一直存在。

ARC 下的解决方式:

  • __weak或者__unsafe_unretained解决:
__weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
    };
复制代码
__unsafe_unretained id uuSelf = self;
    self.block = ^{
        NSLog(@"%p",uuSelf);
    };
复制代码

注意__unsafe_unretained会产生悬垂指针,建议使用weak

 对于 non-trivial cycles,咱们须要这样作:

__weak typeof(self) weakSelf = self;
    self.block = ^{
        if(!strongSelf) return;
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%p",weakSelf);
    };
复制代码
  • __block解决(必需要调用block): 缺点:必需要调用block,并且block里要将指针置为nil。若是一直不调用block,对象就会一直保存在内存中,形成内存泄漏。
__block id blockSelf = self;
    self.block = ^{
        NSLog(@"%p",blockSelf);
        blockSelf = nil;
    };
    self.block();
复制代码

MRC 下的解决方式:

  • __unsafe_unretained解决:同ARC
  • __block解决(在MRC下使用__block修饰对象类型,在block内部不会对该对象进行retain操做,因此在MRC环境下能够经过__block解决循环引用的问题)
__block id blockSelf = self;
    self.block = ^{
        NSLog(@"%p",blockSelf);
    };
复制代码

更多关于block的内容,能够参阅《OC - Block 详解》

属性

说到属性,不得不提一下@synthesize@dynamic这两个指令。

@synthesize 和 @dynamic

  • @property:帮咱们自动生成属性的settergetter方法的声明。
  • @synthesize:帮咱们自动生成settergetter方法的实现以及下划线成员变量。 之前咱们须要手动对每一个@property添加@synthesize,而在 iOS 6 以后 LLVM 编译器引入了 “property autosynthesis”,即属性自动合成。换句话说,就是编译器会自动为每一个@property添加@synthesize

Q: @synthesize如今有什么做用呢?

若是咱们同时重写了settergetter方法,则编译器就不会为这个@property添加@synthesize,这时候就不存在下划线成员变量,因此咱们须要手动添加@synthesize

@synthesize propertyName = _propertyName;
复制代码

 有时候咱们不但愿编译器为咱们@synthesize,咱们但愿在程序运行过程当中再去决定该属性存取方法的实现,就可使用@dynamic

  • @dynamic :告诉编译器不用自动进行@synthesize,等到运行时再添加方法实现,可是它不会影响@property生成的settergetter方法的声明。@dynamic是 OC 为动态运行时语言的体现。动态运行时语言与编译时语言的区别:动态运行时语言将函数决议推迟到运行时,编译时语言在编译器进行函数决议。
@dynamic propertyName;
复制代码

属性“内存管理”关键字与全部权修饰符的对应关系

属性“内存管理”关键字 全部权修饰符
assign __unsafe_unretained
unsafe_unretained __unsafe_unretained
weak __weak
retain __strong
strong __strong
copy __strong

更多关于属性关键字的内容,能够参阅《OC - 属性关键字和全部权修饰符》

管理 Outlets 的模式在 iOS 和 OS X 平台下变得一致

ARC下,iOSOS X平台中声明outlets的模式变得一致。你应该采用的模式为:在nib或者storyboard中,除了来自文件全部者的top-level对象的outlets应该使用strong,其它状况下应该使用weak修饰outlets。(详情见 Nib Files in Resource Programming Guide

栈变量初始化为 nil

使用ARCstrongweakautoreleasing的栈变量如今会默认初始化为nil。例如:

- (void)myMethod {
    NSString *name;
    NSLog(@"name: %@", name);
}
复制代码

打印name的值为null,而不是程序Crash

使用编译器标志启用和禁用 ARC

使用-fobjc-arc编译器标志启用ARC。若是对你来讲,某些文件使用MRC更方便,那你能够仅对部分文件使用ARC。对于使用ARC做为默认方式的项目,可使用-fno-objc-arc编译器标志为指定文件禁用ARC。以下图所示:

ARC支持 Xcode 4.2 及更高版本、OS X v10.6 及更高版本 (64-bit applications) 、iOS 4 及更高版本。但 OS X v10.6 和 iOS 4 不支持weak弱引用。Xcode 4.1 及更早版本中不支持ARC

Managing Toll-Free Bridging

Toll-Free Bridging

你在项目中可能会使用到Core Foundation样式的对象,它可能来自Core Foundation框架或者采用Core Foundation约定标准的其它框架如Core Graphics

编译器不会自动管理Core Foundation对象的生命周期,你必须根据Core Foundation内存管理规则调用CFRetainCFRelease。请参阅《Memory Management Programming Guide for Core Foundation》

MRC下,咱们能够直接在Objective-C指针类型idC指针类型void *之间进行强制转换,如Foundation对象和Core Foundation对象进行转换。因为都是手动管理内存,无须关心内存管理权的移交问题。

ARC下,进行Foundation对象和Core Foundation对象的类型转换,须要使用Toll-Free Bridging桥接)告诉编译器对象的全部权语义。你要选择使用__bridge__bridge_retained__bridge_transfer这三种桥接方案中的一种来肯定对象的内存管理权移交问题,它们的做用分别以下:

桥接方案 用法 内存管理权 引用计数
__bridge F <=> CF 不改变 不改变
__bridge_retained
(或 CFBridgingRetain)
F => CF ARC 管理 => 手动管理
(你负责调用 CFRelease 或
相关函数来放弃对象全部权)
+1
__bridge_transfer
(或 CFBridgingRelease)
CF => F 手动管理 => ARC 管理
(ARC 负责放弃对象的全部权)
+1 再 -1,不改变
  • __bridge(经常使用):不改变对象的内存管理权全部者。 原本由ARC管理的Foundation对象,转换成Core Foundation对象后继续由ARC管理; 原本由开发者手动管理的Core Foundation对象,转换成Foundation对象后继续由开发者手动管理。 下面以NSMutableArray对象和CFMutableArrayRef对象为例:
// 原本由 ARC 管理
    NSMutableArray *mArray = [[NSMutableArray alloc] init];
    // 转换后继续由 ARC 管理 
    CFMutableArrayRef cfMArray = (__bridge CFMutableArrayRef)(mArray); 

    // 原本由开发者手动管理
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); 
    // 转换后继续由开发者手动管理
    NSMutableArray *mArray = (__bridge NSMutableArray *)(cfMArray); 
    ...
    // 在不须要该对象时须要手动释放
    CFRelease(cfMArray); 
复制代码

__bridge的安全性与赋值给__unsafe_unretained修饰符相近甚至更低,若是使用不当没有注意对象的释放,就会因悬垂指针而致使Crash

__bridge转换后不改变对象的引用计数,好比咱们将id类型转换为void *类型,咱们在使用void *以前该对象被销毁了,那么咱们再使用void *访问该对象确定会Crash。因此void *指针建议当即使用,若是咱们要保存这个void *指针留着之后使用,那么建议使用__bridge_retain

而在使用__bridgevoid *类型转换为id类型时,必定要注意此时对象的内存管理仍是由开发者手动管理,记得在不须要对象时进行释放,不然内存泄漏!

如下给出几个 “使用__bridgevoid *类型转换为id类型” 的示例代码,要注意转换后仍是由开发者手动管理内存,因此即便离开做用域,该对象还保存在内存中。

// 使用 __strong
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    NSMutableArray *mArray = (__bridge NSMutableArray *)(cfMArray);
    
    NSLog(@"%ld", CFGetRetainCount(cfMArray));     // 2, 由于 mArray 是 __strong, 因此增长引用计数
    NSLog(@"%ld", _objc_rootRetainCount(mArray));  // 1, 可是使用 _objc_rootRetainCount 打印出来是 1 ?
     
    // 在不须要该对象时进行释放
    CFRelease(cfMArray); 
    NSLog(@"%ld", CFGetRetainCount(cfMArray));     // 1, 由于 __strong 做用域还没结束,还有强指针引用着
    NSLog(@"%ld", _objc_rootRetainCount(mArray));  // 1
    
   // 在 __strong 做用域结束前,还能够访问该对象
   // 等 __strong 做用域结束,该对象就会销毁,再访问就会崩溃
复制代码
// 使用 __strong
    CFMutableArrayRef cfMArray;
    {
        cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
        NSMutableArray *mArray = (__bridge NSMutableArray *)(cfMArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray));  // 2, 由于 mArray 是 __strong, 因此增长引用计数
    }
    NSLog(@"%ld", CFGetRetainCount(cfMArray));      // 1, __strong 做用域结束
    CFRelease(cfMArray); // 释放对象,不然内存泄漏
    // 可使用 CFShow 函数打印 CF 对象
    CFShow(cfMArray);    // 再次访问就会崩溃
复制代码
// 使用 __weak
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    NSMutableArray __weak *mArray = (__bridge NSMutableArray *)(cfMArray);
    
    NSLog(@"%ld", CFGetRetainCount(cfMArray));     // 1, 由于 mArray 是 __weak, 因此不增长引用计数
    NSLog(@"%ld", _objc_rootRetainCount(mArray));  // 1
    
    /* * 使用 mArray */
    
    // 在不须要该对象时进行释放
    CFRelease(cfMArray);
    
    NSLog(@"%@",mArray);  // nil, 这就是使用 __weak 的好处,在指向的对象被销毁的时候会自动置指针为 nil,再次访问也不会崩溃
复制代码
// 使用 __weak
    NSMutableArray __weak *mArray;
    {
        CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
        mArray = (__bridge NSMutableArray *)(cfMArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray));  // 1, 由于 mArray 是 __weak, 因此不增长引用计数
    }
    CFMutableArrayRef cfMArray =  (__bridge CFMutableArrayRef)(mArray);
    NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 1, 可见即便出了做用域,对象也还没释放,由于内存管理权在咱们
    
    CFRelease(cfMArray); // 释放对象,不然内存泄漏
复制代码
  • __bridge_retained:用在Foundation对象转换成Core Foundation对象时,进行ARC内存管理权的剥夺。 原本由ARC管理的Foundation对象,转换成Core Foundation对象后,ARC再也不继续管理该对象,须要由开发者本身手动释放该对象,不然会发生内存泄漏。
// 原本由 ARC 管理
    NSMutableArray *mArray = [[NSMutableArray alloc] init];       
    // 转换后由开发者手动管理 
    CFMutableArrayRef cfMArray = (__bridge_retained CFMutableArrayRef)(mArray); 
// CFMutableArrayRef cfMArray = (CFMutableArrayRef)CFBridgingRetain(mArray); // 另外一种等效写法
    ...
    CFRelease(cfMArray);  // 在不须要该对象的时候记得手动释放
复制代码

__bridge_retained顾名思义会对对象retain,使转换赋值的变量也持有该对象,对象的引用计数 +1。因为转换后由开发者进行手动管理,因此再不须要该对象的时候记得调用CFRelease释放对象,不然内存泄漏。

id obj = [[NSObject alloc] init];
    void *p = (__bridge_retained void *)(obj);
复制代码

以上代码若是在MRC下至关于:

id obj = [[NSObject alloc] init];

    void *p = obj;
    [(id)p retain];
复制代码

查看引用计数

ARC下,咱们可使用_objc_rootRetainCount函数查看对象的引用计数(该函数有时候不许确)。对于Core Foundation对象,咱们可使用CFGetRetainCount函数查看引用计数。

uintptr_t _objc_rootRetainCount(id obj);
复制代码

打印上面代码的obj对象的引用计数,发现其引用计数确实增长。

id obj = [[NSObject alloc] init];
    NSLog(@"%ld", _objc_rootRetainCount(obj)); // 1
    void *p = (__bridge_retained void *)(obj);
    NSLog(@"%ld", _objc_rootRetainCount(obj)); // 2
    NSLog(@"%ld", CFGetRetainCount(p));        // 2
复制代码

如下给出几个示例代码:

CFMutableArrayRef cfMArray;
    {
        NSMutableArray *mArray = [[NSMutableArray alloc] init];
        cfMArray = (__bridge_retained CFMutableArrayRef)(mArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 2, 由于 mArray 是 __strong, 并且使用了 __bridge_retained
    }
    NSLog(@"%ld", CFGetRetainCount(cfMArray));     // 1, __strong 做用域结束
    CFRelease(cfMArray); // 在不须要使用的时候释放,防止内存泄漏
复制代码

若是将上面的代码由__bridge_retained改成使用__bridge会怎样?

CFMutableArrayRef cfMArray;
    {
        NSMutableArray *mArray = [[NSMutableArray alloc] init];
        cfMArray = (__bridge CFMutableArrayRef)(mArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 1, 由于 mArray 是 __strong, 且使用 __bridge 仍是由 ARC 管理,不增长引用计数
    }
    NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 程序崩溃,由于对象已销毁
复制代码
  • __bridge_transfer:用在Core Foundation对象转换成Foundation对象时,进行内存管理权的移交。 原本由开发者手动管理的Core Foundation对象,转换成Foundation对象后,将内存管理权移交给ARC,开发者不用再关心对象的释放问题,不用担忧内存泄漏。
// 原本由开发者手动管理
    CFMutableArrayRef cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    // 转换后由 ARC 管理
    NSMutableArray *mArray = (__bridge_transfer NSMutableArray *)(cfMArray);
// NSMutableArray *mArray = CFBridgingRelease(cfMArray); // 另外一种等效写法
复制代码

__bridge_transfer做用如其名,移交内存管理权。它的实现跟__bridge_retained相反,会release被转换的变量持有的对象,但同时它在赋值给转换的变量时会对对象进行retain,因此引用计数不变。也就是说,对于Core Foundation引用计数语义而言,对象是释放的,可是ARC保留了对它的引用。

id obj = (__bridge_transfer void *)(p);
复制代码

以上代码若是在MRC下至关于:

id obj = (id)p;
    [obj retain];
    [(id)p release];
复制代码

下面也给出一个示例代码:

CFMutableArrayRef cfMArray;
    {
        cfMArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
        NSMutableArray *mArray = (__bridge_transfer NSMutableArray *)(cfMArray);
        NSLog(@"%ld", CFGetRetainCount(cfMArray));    // 1, 由于 cfMArray 指针指向的对象存在,因此仍然能够经过该指针访问
        NSLog(@"%ld", _objc_rootRetainCount(mArray)); // 1, mArray 为 __strong
    }
    // __strong 做用域结束,ARC 对该对象进行了释放
    NSLog(@"%ld", CFGetRetainCount(cfMArray)); // 再次访问就会崩溃
复制代码

若是将上面的代码由__bridge_transfer改成使用__bridge会怎样? 其实在__bridge讲解中已经给出了示例代码,若是不释放就会形成内存泄漏。

以上提到了能够替代__bridge_retained__bridge_transfer的两个函数:CFBridgingRetainCFBridgingRelease,下面咱们来看一下函数实现:

/* Foundation - NSObject.h */
#if __has_feature(objc_arc) // ARC

// After using a CFBridgingRetain on an NSObject, the caller must take responsibility for calling CFRelease at an appropriate time.
NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) {
    return (__bridge_retained CFTypeRef)X;
}

NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) {
    return (__bridge_transfer id)X;
}

#else // MRC

// This function is intended for use while converting to ARC mode only.
NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) {
    return X ? CFRetain((CFTypeRef)X) : NULL;
}

// Casts a CoreFoundation object to an Objective-C object, transferring ownership to ARC (ie. no need to CFRelease to balance a prior +1 CFRetain count). NS_RETURNS_RETAINED is used to indicate that the Objective-C object returned has +1 retain count. So the object is 'released' as far as CoreFoundation reference counting semantics are concerned, but retained (and in need of releasing) in the view of ARC. This function is intended for use while converting to ARC mode only.
NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) NS_RETURNS_RETAINED {
    return [(id)CFMakeCollectable(X) autorelease];
}

#endif
复制代码

能够看到在ARC下,这两个函数就是使用了__bridge_retained__bridge_transfer

小结:ARC下,必须恰当使用Toll-Free Bridging桥接)在Foundation对象和Core Foundation对象之间进行类型转换,不然可能会致使内存泄漏。

建议:

  • Foundation对象转为Core Foundation对象时,若是咱们当即使用该Core Foundation对象,使用__bridge;若是咱们想保存着之后使用,使用__bridge_retained,可是要记得在使用完调用CFRelease释放对象。
  • Core Foundation对象转为Foundation对象时,使用__bridge_transfer

编译器处理从 Cocoa 方法返回的 CF 对象

编译器知道返回Core Foundation对象的Objective-C方法遵循历史 Cocoa 命名约定。例如,编译器知道,在iOS中,UIColorCGColor方法返回的CGColor并不持有(由于方法名不是以alloc/new/copy/mutableCopy开头)。因此你仍然必须使用适当的类型转换,如如下示例所示:

NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
    [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
复制代码

不然编译警告:

NSMutableArray *colors = [NSMutableArray arrayWithObject:[[UIColor darkGrayColor] CGColor]];
    // Incompatible pointer types sending 'CGColorRef _Nonnull' (aka 'struct CGColor *') to parameter of type 'id _Nonnull'
    // 不兼容的指针类型,将 CGColorRef(又称 struct CGColor *)做为 id 类型参数传入
复制代码

使用桥接转换函数参数

当在函数调用中在Objective-CCore Foundation对象之间进行转换时,须要告诉编译器参数的全部权语义。Core Foundation对象的全部权规则请参阅《Memory Management Programming Guide for Core Foundation》Objective-C的全部权规则请参阅《从 MRC 提及 —— 内存管理策略》章节。

以下实例所示,NSArray对象做为CGGradientCreateWithColors函数的参数传入,它的全部权不会传递给该函数,所以须要使用__bridge进行强制转换。

NSArray *colors = <#An array of colors#>;
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
复制代码

如下实例中使用了Core Foundation对象以及Objective-CCore Foundation对象之间进行转换,同时须要注意Core Foundation对象的内存管理。

- (void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGFloat locations[2] = {0.0, 1.0};
    NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]];
    [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
    CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);
    CGColorSpaceRelease(colorSpace);  // Release owned Core Foundation object.
    CGPoint startPoint = CGPointMake(0.0, 0.0);
    CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
    CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint,
                                kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
    CGGradientRelease(gradient);  // Release owned Core Foundation object.
}
复制代码

ARC 的实现

ARC仅仅依靠LLVM编译器是没法完成内存管理工做的,它还须要Runtime的支持。就好比__weak修饰符,若是没有Runtime,那么在对象dealloc时就不会将__weak变量置为nilARC由如下工具、库来实现:

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

转换项目时的常见问题

除了以上说明的几点ARC新规则之外,ARC下还要注意如下几个问题,也是MRC转换到ARC项目的常见问题:

  • ARC要求你在init方法中将[super init]的结果分配给self
self = [super init];
    if (self) {
    ...
复制代码
  • 你没法实现自定义retainrelease方法。 实现自定义retainrelease方法会破坏weak弱指针。你想要这么作的缘由可能以下:

    • ① 性能
      请不要再这样作了,NSObjectretainrelease方法的实现如今要快得多。若是你仍然发现问题,请提交错误给苹果。
    • ② 实现自定义weak弱指针系统
      请改用__weak
    • ③ 实现单例类
      请改用shared instance模式。或者,使用类方法替代实例方法,这样能够避免建立对象。
  • “直接赋值” 的实例变量变成强引用了。

ARC以前,实例变量是弱引用(非持有引用) —— 直接将对象分配给实例变量并不延长对象的生命周期。为了使属性变strong,你一般会实现或使用@synthesize合成 “调用适当内存管理方法” 的访问器方法。相反,有时你为了维持一个弱引用,你可能会像如下实例这样实现访问器方法。

@interface MyClass : Superclass {
    id thing; // Weak reference.
}
// ...
@end

@implementation MyClass
- (id)thing {
    return thing;
}
- (void)setThing:(id)newThing {
    thing = newThing;
}
// ...
@end
复制代码

对于ARC,实例变量默认是strong强引用 —— 直接将对象分配给实例变量会延长对象的生命周期。迁移工具在将MRC代码转换为ARC代码时,没法肯定它该使用strong仍是weak,因此默认使用strong。 若要保持与MRC下一致,必须将实例变量使用__weak修饰,或使用weak关键字的属性。

@interface MyClass : Superclass {
    id __weak thing;
}
// ...
@end

@implementation MyClass
- (id)thing {
    return thing;
}
- (void)setThing:(id)newThing {
    thing = newThing;
}
// ...
@end
复制代码

或者:

@interface MyClass : Superclass
@property (weak) id thing;
// ...
@end

@implementation MyClass
@synthesize thing;
// ...
@end
复制代码

ARC 补充

__weak 黑科技

在全部权修饰符中咱们简单介绍了__weak修饰符。实际上,除了在MRC下没法使用__weak修饰符之外,还有其余没法使用__weak修饰符的状况。

例如,有一些类是不支持__weak修饰符的,好比NSMachPort。这些类重写了retain / release并实现该类独自的引用计数机制。可是赋值以及使用附有__weak修饰符的变量都必须恰当地使用 objc4 运行时库中的函数,所以独自实现引用计数机制的类大多不支持__weak修饰符。

NSMachPort __weak *port = [NSMachPort new]; 
    // 编译错误:Class is incompatible with __weak references 类与弱引用不兼容
复制代码

不支持__weak修饰符的类,其类的声明中添加了NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE宏,该宏的定义以下。

// Marks classes which cannot participate in the ARC weak reference feature.
#if __has_attribute(objc_arc_weak_reference_unavailable)
#define NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE __attribute__((objc_arc_weak_reference_unavailable))
#else
#define NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE
#endif
复制代码

若是将不支持__weak的类的对象赋值给__weak修饰符的变量,一旦编译器检测出来就会报告编译错误。可是在 Cocoa 框架中,不支持__weak修饰符的类极为罕见,所以没有必要太过担忧。

__weak黑科技来了!!!!!

还有一种状况也不能使用__weak修饰符。就是当对象的allowsWeakReference/retainWeakReference实例方法返回NO时,这两个方法的声明以下:

- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;
复制代码

这两个方法的默认实现是返回YES

若是咱们在类中重写了allowsWeakReference方法并返回NO,那么若是咱们将该类的实例对象赋值给__weak修饰符的变量,那么程序就会Crash

例如咱们在HTPerson类中作了此操做,则如下代码运行就会Crash

HTPerson __weak *p = [[HTPerson alloc] init];
复制代码
// 没法对 HTPerson 类的实例持有弱引用。多是此对象被过分释放,或者正在销毁。
objc[18094]: Cannot form weak reference to instance (0x600001d7c2a0) of class HTPerson. It is possible that this object was over-released, or is in the process of deallocation.
(lldb) 
复制代码

因此,对于全部allowsWeakReference方法返回NO的类的实例都绝对不能使用__weak修饰符。

另外,若是实例对象的retainWeakReference方法返回NO,那么赋值该对象__weak修饰符的变量将为nil,表明没法经过__weak变量访问该对象。

好比如下示例代码:

HTPerson *p1 = [[HTPerson alloc] init];    
    HTPerson __weak *p2 = p1;
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);

/* 打印以下: <HTPerson: 0x600002e0dd20> <HTPerson: 0x600002e0dd20> <HTPerson: 0x600002e0dd20> <HTPerson: 0x600002e0dd20> <HTPerson: 0x600002e0dd20> */
复制代码

因为p1__strong持有对象的强引用,因此在p1做用域结束前,该对象都存在,使用__weak修饰的p2访问该对象没问题。

下面在HTPerson类中重写retainWeakReference方法:

@interface HTPerson ()
{
    NSUInteger count;
}

@implementation HTPerson
- (BOOL)retainWeakReference
{
    if (++count > 3) {
        return NO;
    }
    return [super retainWeakReference];    
}
@end
复制代码

再次运行以上代码,发现从第 4 次开始,经过__weak变量就没法访问到对象,由于这时候retainWeakReference方法返回值为NO

HTPerson *p1 = [[HTPerson alloc] init];    
    HTPerson __weak *p2 = p1;
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);
    NSLog(@"%@", p2);

/* 打印以下: <HTPerson: 0x600003e23ba0> <HTPerson: 0x600003e23ba0> <HTPerson: 0x600003e23ba0> (null) (null) */
复制代码

查看引用计数

ARC下,咱们可使用_objc_rootRetainCount函数查看对象的引用计数。

uintptr_t _objc_rootRetainCount(id obj);
复制代码

但实际上并不能彻底信任该函数取得的数值。对于已释放的对象以及不正确的对象地址,有时也返回 “1”。另外,在多线程中使用对象的引用计数数值,由于存在竞争条件的问题,因此取得的数值不必定彻底可信。 虽然在调试中_objc_rootRetainCount函数颇有用,但最好在了解其所具备的问题的基础上来使用。

苹果对 ARC 一些问题的回答

Q: 我应该如何看待 ARC ?它将 retains/releases 调用的代码放在哪了?

尝试不要去思考ARCretains/releases调用的代码放在哪里,而是思考应用程序算法,思考对象的strongweak指针、全部权、以及可能产生的循环引用。

Q: 我还须要为个人对象编写 dealloc 方法吗?

有时候须要。 由于ARC不会自动处理malloc/freeCore Foundation对象的生命周期管理、文件描述符等等,因此你仍然能够经过编写dealloc方法来释放这些资源。 你没必要(实际上不能)释放实例变量,但可能须要对系统类和其余未使用ARC编写的代码调用[self setDelegate:nil]ARC下的dealloc方法中不须要且不容许调用[super dealloc]Runtime会自动处理。

Q: ARC 中仍然可能存在循环引用吗?

是的,ARC自动retain/release,也继承了循环引用问题。幸运的是,迁移到ARC的代码不多开始泄漏,由于属性已经声明是否retain

Q: block 是如何在 ARC 中工做的?

ARC下,编译器会根据状况自动将栈上的block复制到堆上,好比block做为函数返回值时,这样你就没必要再调用Block Copy

须要注意的一件事是,在ARC下,NSString * __block myString这样写的话,block会对NSString对象强引用,而不是形成悬垂指针问题。若是你要和MRC保持一致,请使用__block NSString * __unsafe_unretained myString或(更好的是)使用__block NSString * __weak myString

Q: 我能够在 ARC 下建立一个 retained 指针的 C 数组吗?

能够,以下示例所示:

// Note calloc() to get zero-filled memory.
__strong SomeClass **dynamicArray = (__strong SomeClass **)calloc(entries, sizeof(SomeClass *));
for (int i = 0; i < entries; i++) {
     dynamicArray[i] = [[SomeClass alloc] init];
}
 
// When you're done, set each entry to nil to tell ARC to release the object.
for (int i = 0; i < entries; i++) {
     dynamicArray[i] = nil;
}
free(dynamicArray);
复制代码

这里有一些注意点:

  • 在某些状况下,你须要编写__strong SomeClass **,由于默认是__autoreleasing SomeClass **
  • 分配的内存区域必须初始化为 0(zero-filled)。
  • free数组以前,必须将每一个元素赋值为nilmemsetbzero将不起做用)。
  • 你应该避免使用memcpyrealloc

Q: ARC 速度上慢吗?

不。编译器有效地消除了许多无关的retain/release调用,而且已经投入了大量精力来加速 Objective-C 运行时。特别的是,当方法的调用者是ARC代码时,常见的 “return a retain/autoreleased object” 模式要快不少,而且实际上并不将对象放入自动释放池中。

备注:MRC下,经过alloc/new/copy/mutableCopy或以这些命名开头的方法建立的对象直接持有,而经过其它方法建立的对象会经过调用autorelease加入到自动释放池中。而ARC下不能调用autorelease方法,那么ARC怎么作到这一点呢?

《Objective-C 高级编程:iOS 与 OS X 多线程和内存管理》 书中是说:在ARC下,编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,若是不是则自动将返回值的对象注册到@autoreleasepool

但通过测试,发现并非如此。并且,之前在MRC下经过array类方法建立的NSMutableArray对象会被加入到@autoreleasepool,可是在ARC下并不会。

因此,根据方法名并不能判断ARC会不会将对象加入到@autoreleasepool。若是咱们须要这么作,建议使用__autoreleasing修饰符。

须要注意的一个问题是,优化器不是在常见的调试配置中运行的,因此预计在-O0模式下将会比-Os模式下看到更多的retain/release调用。

Q: ARC 在 ObjC++ 模式下工做吗?

是。你甚至能够在类和容器中放置strong/weakid对象。ARC编译器会在复制构造函数和析构函数等中合成retain/release逻辑以使其运行。

Q: 哪些类不支持 weak 弱引用?

你当前没法建立对如下类的实例的weak弱引用:NSATSTypesetter、NSColorSpace、NSFont、NSMenuView、NSParagraphStyle、NSSimpleHorizontalTypesetter 和 NSTextView。

注意: 此外,在 OS X v10.7 中,你没法建立对 NSFontManager,NSFontPanel、NSImage、NSTableCellView、NSViewController、NSWindow 和 NSWindowController 实例的weak弱引用。此外,在 OS X v10.7 中,AV Foundation 框架中的任何类都不支持weak弱引用。

此外,你没法在ARC下建立 NSHashTable、NSMapTable 和 NSPointerArray 类的实例的weak弱引用。

Q: 当我继承一个使用了 NSCopyObject 的类,如 NSCell 时,我须要作些什么?

没什么特别的。ARC会关注之前必须显式添加额外retain的状况。使用ARC,全部的复制方法只须要复制实例变量就能够了。

Q: 我能够对指定文件选择退出ARC而使用MRC吗?

能够。当你迁移项目到ARC或建立一个ARC项目时,因此Objective-C源文件的默认编译器标志将设置为-fobjc-arc,你可使用-fno-objc-arc编译器标志为指定的类禁用ARC。操做以下图所示:

Q: 在 Mac 上是否弃用了 GC (Garbage Collection) 机制?

OS X Mountain Lion v10.8 中不推荐使用GC机制,而且将在 OS X 的将来版本中删除GC机制。ARC是推荐的替代技术。为了帮助现有应用程序迁移,Xcode 4.3 及更高版本中的ARC迁移工具支持将使用GC的 OS X 应用程序迁移到ARC

注意: 对于面向 Mac App Store 的应用,Apple 强烈建议你尽快使用ARC替换GC,由于 Mac App Store Guidelines 禁止使用已弃用的技术,不然不会经过审核,详情请参阅 Mac App Store Review Guidelines