ARC概要
1、在Objective-C语言中,我们是通过控制一个对象的retainCount来管理一个对象的生命周期,当对象的retainCount为0时,该对象的内存将会被释放。因此,我们通常需要编写诸如
[personObject retain]和
if ([personObject retainCount]==1)
{
[personObject release];
personObject = nil;
}else{
[personObject release];
};
这样的代码。
2、从LLVM编译器3.0开始,Apple为我们引入了ARC机制,这种机制对MRC(即手动内存管理),进行了封装,使我们不用再直接控制一个对象的retainCount来管理一个对象的生命周期,而是通过管理一个对象的"持有者"来管理一个对象的生命周期。
3、很多人误以为在ARC机制中,编译器会自动插入诸如上文的代码,这种理解是错误的,在ARC机制中,编译器的确会为我们的代码插入相关代码来对对象的retainCount进行管理,但是并不是插入直接管理retainCount的代码。正是因为如此,在ARC机制中,编译器不允许我们向对象发送retain和release消息,也不能发送retainCount消息来获取一个对象的retainCount。试想,如果编译器是直接插入retain和release来管理对象,其为什么不允许我们发送retainCount来获取对象的retainCount呢?
ARC中的持有者
1、所有权修饰符
在ARC机制中,声明一个对象指针时,出了指明指针指向对象的类型,还应指明该指针的所有权修饰符。所有权修饰符会告诉编译器,该指针是否会对对象进行持有。在ARC机制中的所有权修饰符如下:
__strong
__autoreleasing
__unsafe_unretained
__weak
2、所有权修饰符之__strong
声明一个指针的所有权修饰符为__strong
Person _strong *personObject;
所有权修饰符为__strong的指针,当其指向一个对象时,其会成为该对象的持有者。
personObject = [Person new];
上面的代码,编译器在编译时,会对其进行预处理,插入一些代码来控制该Person对象的“持有者数量“,在这里我们假设每一个NSObject对象内部都有一个referencingCount来保存该对象的“持有者”数量
在Swift语言中,内存管理是通过自动引用计数来进行管理的,因此,我们可以通过Swift语言中Foundation框架对NSObject类提供的接口来获取一些信息
public class NSObject : NSObjectProtocol {
public class func load()
public class func initialize()
public init()
public func finalize()
public func copy() -> AnyObject
public func mutableCopy() -> AnyObject
public class func instancesRespondToSelector(aSelector: Selector) -> Bool
public class func conformsToProtocol(`protocol`: Protocol) -> Bool
public func methodForSelector(aSelector: Selector) -> IMP
public class func instanceMethodForSelector(aSelector: Selector) -> IMP
public func doesNotRecognizeSelector(aSelector: Selector)
@available(OSX 10.5, *)
public func forwardingTargetForSelector(aSelector: Selector) -> AnyObject?
public class func isSubclassOfClass(aClass: AnyClass) -> Bool
@available(OSX 10.5, *)
public class func resolveClassMethod(sel: Selector) -> Bool
@available(OSX 10.5, *)
public class func resolveInstanceMethod(sel: Selector) -> Bool
public class func hash() -> Int
public class func superclass() -> AnyClass?
public class func description() -> String
public class func debugDescription() -> String
}
我们可以发现,对于NSObject实例对象,Apple并没有提供Objective-C语言中的retain和release方法来控制一个对象的retainCount方法,因此正如我在"ARC概述"中所说,在ARC机制下,编译器并不是通过插入向一个对象发送retain、release来直接管理对象的retainCount.
因此我们可以对在ARC机制下,NSObject的类接口做一个简单的猜想
@interface NSObject
{
int retainCount;
int referencingCount;
}
@end;
和retainCount不同,当我们通过[ClassObject alloc];创建一个对象时,该对象的referencingCount为0(retainCount为1)
当编译器遇到“personObject = [Person new];”时,编译器可能会插入下面的代码来对对象的referencingCount进行管理
objc_addReferencingCount(personObject);
在这里,我假设objc_addReferencingCount()是一个C语言函数,对传入的参数的referencingCount进行+1操作。即其实现如下
void objc_addReferencingCount(id* objc)
{
objc->referencingCount++;
}
后面,当编译器遇到"personObject = nil;"编译器可能会在该句代码之前插入下面的代码。
objc_reduceReferencingCount(personObject)
在这里,我同样假设objc_reduceReferencingCount是一个C语言函数,对传入的参数进行类似release的操作,即其实现如下
void objc_reduceReferencingCount(id* objc)
{
objc->referencingCount--;
if (objc->referencingCount==0)
{
objc_msgSend(objc,@selector(release));
}
}
接着,当编译器遇到"personObject = otherPersonObject"时,编译器会插入的代码我们可以很容易猜出
objc_reduceReferencingCount(personObject);
objc_addReferencingCount(otherPersonObject);
最后,当该指针变量的生命周期结束时,编译器会在插入objc_reduceReferencingCount来对其指向的对象的的referencingCount进行减1.
if(YES){
Person ben = [Person new];
person jack = [Person new];
ben = jack;
}
编译其进行预处理的结果为
if(YES){
Person ben = [Person new];
objc_addReferencingCount(ben);
Person jack = [Person new];
objc_addReferencingCount(jack);
objc_reduceReferencingCount(ben);
objc_addReferencingCount(jack);
ben = jack;
objc_reduceReferencingCount(ben);
objc_reduceReferencingCount(jack);
}
因此,编译器会根据所有者修饰符为__strong的指针来控制对象的referencingCount和objc_addReferencingCount的完整实现如下
void objc_addReferencingCount(id* objc)
{
if(objc==nil)
{
objc->referencingCount++;
}
}
void objc_reduceReferencingCount(id* objc)
{
if(objc==nil)
{
return;
}
objc->referencingCount--;
if (objc->referencingCount==0)
{
objc_msgSend(objc,@selector(release));
}
}
3、所有者修饰符之__autoreleasing
上面,我们谈到了所有者修饰符为__strong的指针,编译器会对对其进行的相关操作。
现在有一个问题
if(YES){
Person ben = [Person new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[ben show];
[ben walk];
});
}
在if语句开启的局部作用域的默认,我们开启了一个异步线程,而在主线程中,ben指向的对象被的referencingCount已经为0,这时候该对象的内存已经被清空,且ben指针也已被清空,那么在异步线程中向指向[ben show];和[ben walk];肯定会造成运行时异常不是吗?
对于ben指针变量被销毁,这个很容易解决,编译器只需要在Block中创建一个指针,将ben的值赋值给该新指针即可。
为了解决,“ben指针指向的对象的referencingCount为0,导致对象被销毁,无法在异步线程中访问该对象”的这个问题,下面来介绍所有者修饰符__autoreleasing
在MRC中,Apple为我们带了NSAutoReleasePool类,使得我们在开发中,只需要确保一个对象在程序执行结束前,其的retainCount<=1即可以被释放,以免造成内存泄漏。在ARC机制中,我们只需要确保一个对象的referencingCount>0,其就不会被释放。这种区别体现在:在MRC+NSAutoReleasePool我们需要控制其retainCount来使得对象及时地被释放,在ARC中,我们需要控制其referencingCount>0来使得对象不被释放,即MRC默认不释放对象,ARC默认释放对象。
前面,我讲到ARC机制对对象的retainCount进行了封装,使得我们不能向NSObject对象发送retain和release消息。因此,在ARC机制中,我们想使用NSAutoReleasePool对象,但我们是不能编写[NSAutoReleasePoolObject release];这样的代码的。如何解决?
使用@autoreleasepool Block。
@autoreleasepool {
//代码
}
使用@autoreleasepool Block在ARC机制中的作用
我们可以像通过“向对象发送autorelease消息”使得对象的referencingCount在@autoreleasepool Block结束前始终>=1。
如何实现呢?通过所有者修饰符为__autoreleasing的指针来实现。
Person __autoreleasing *peronObject;
在ARC机制中,当我们将一个对象赋值给一个所有者修饰符为__autoreleasing的指针时,该对象的referencingCount会+1。
验证
Person* __autoreleasing kaka = [Person new];
NSLog(@"kaka retainCount %ld ",_objc_rootRetainCount(kaka));
代码的执行结果
2015-11-25 14:54:16.821 Test_ARC[9110:93379] kaka retainCount 1
有一点我要讲清楚的是,将一个对象赋值给所有者修饰符为__autoreleasing的指针时,该指针并没有成为该对象的“持有者”,而是NSAutoReleasePool中的_objects类属性成为该对象的“持有者”。
验证一:__autoreleasing指针不会成为任何对象的持有者
Person* kaka = [Person new];
Person* caicai = [Person new];
Person* __autoreleasing point1 = kaka;
point1 = caicai;
NSLog(@"kaka retainCount %ld ",_objc_rootRetainCount(kaka));
代码执行结果:
2015-11-25 14:59:35.268 Test_ARC[9411:96732] kaka retainCount 2
验证二:是NSAutoReleasingPool中的相关属性成为该对象的“持有者”
Person* kaka = [Person new];
Person* caicai = [Person new];
Person* __autoreleasing point1 = kaka;
point1 = caicai;
_objc_autoreleasePoolPrint();
代码执行结果:
objc[9788]: ##############
objc[9788]: AUTORELEASE POOLS for thread 0x100080000
objc[9788]: 3 releases pending.
objc[9788]: [0x101800000] ................ PAGE (hot) (cold)
objc[9788]: [0x101800038] ################ POOL 0x101800038
objc[9788]: [0x101800040] 0x100600230 Person
objc[9788]: [0x101800048] 0x100600290 Person
objc[9788]: ##############
```
因此,对于之前我提到的问题,编译器可能会通过声明一个所有权为__autoreleasing的指针来解决。
###函数传值以及Block捕获的优化
刚刚我谈到可以通过一个所有权为__autoreleasing的指针来解决我们的问题,但是该解决方案却还是有不足的地方。这个不足的体现在,当NSAutoReleasePool成为一个对象的持有者之后,需要等到autoreleasepool Block结束,其才会被释放对该对象的持有,该对象才会释放。
但是对于我提出的那个问题,我希望能够在dispatch_asycn函数所带的Block执行完毕后该对象就被释放,如何解决?
为此,ARC机制专门对此进行了优化。
我们先来看一下其优化后的代码
if(YES){
Person ben = [Person new];
objc_autoreleaseReturnValue(ben);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
objc_retainAutoreleaseRetrunValue(ben);
[ben show];
[ben walk];
});
}
objc_autoreleaseReturnValue(ben);和objc_retainAutoreleaseRetrunValue(ben);是什么意思呢?简单理解,就是找个对象A成为ben的持有者(referencingCount++),然后再使该对象A释放对ben的持有(referencingCount--)。这样我们就可以实现不使NSAutoReleaePoll成为一个对象的持有者,也能安全地进行传递对象。
事实上ARC机制在很多情况下都使用了这种优化。譬如,NSArray *arrayObjectB = [arrayObjectA copy];这种情况编译器也是通过这种优化,来确保NSArray对象的copy方法返回的参数不被立刻释放。
##4、所有者修饰符之__unsafe_unretained
有时候,我们只是希望一个指针可以指向一个对象,但并不希望改变referencingCount,这时我们可以将该指针的所有者修饰符指定为__unsafe_unretained。
示例代码:
Person ben = [Person new];
NSLog(@"ben retainCount:%d",_objc_rootRetainCount(ben));
Person __unsafe_unretained wildPoint = ben;
NSLog(@"ben retainCount:%d",_objc_rootRetainCount(ben));
代码执行结果:
2015-11-25 16:40:41.784 Test_ARC[2298:24597] ben retainCount:1
2015-11-25 16:40:41.785 Test_ARC[2298:24597] ben retainCount:1
##5、所有者修饰符之__weak
上面我讲到了所有者修饰符__unsafe_unretained,我们很容易发现使用这种所有者修饰符的指针有一个不安全的地方,那就是当其指向的对象被释放时,其依旧指向该对象被释放的地址,如果这时我们尝试通过该指针向对象发送消息,很容易造成程序运行异常。
为了解决这个问题,ARC机制提供了另外一种所有者修饰符__weak。
__weak的出现就是为了解决__unsafe_unretained所带了的不安全问题。
在ARC机制中,所有者修饰符为__weak的指针,不会改变其指向对象的referencingCount,但是当其指向的对象被释放时,其为被赋值为nil。这就确保了其被不会像__unsafe_unretained一样,存在安全隐患。
那ARC机制是如何实现当__weak指向的变量被释放时,该指针被赋值为nil。
我是这么理解
@implementation NSObject : NSObject
{
Set _weakTable;
int retainCount;
int referencingCount;
}
-(void)destoryWeak
{
for id* point in self->_weakTable
{
*point = nil;
}
}
-(void)dealloc
{
[self destoryWeak];
}
@end
void addWeakPoint(id* point,id object)
{
[object->_weakTable addObject:point];
}
void removeWeakPoint(id* point,id object)
{
[object->_weakTable removeOject:point];
}
即NSObject对象内部存在一张散列表,用于存储指向该对象的__weak指针,当该对象将要被释放时,在其dealloc方法中调用destoryWeak方法
上面的addWeakPoint和removeWeakPoint两个C语言函数则是用于向一个NSObject对象的weakTable中进行添加和删除__weak指针。
示例代码:
Person *ben = [Person new];
Person* ben2 = [Person new];
Person* __weak weakPoint = ben;
weakPoint = ben2;
编译器可能会对其插入代码,使其变成这样:
Person *ben = [Person new];
objc_addReferencingCount(ben);
Person* ben2 = [Person new];
objc_addReferencingCount(ben2);
Person* __weak weakPoint = ben;
addWeakPoint(&weakPoint,ben);
removeWeakPoint(&weak,ben);
weakPoint = ben2;
addWeakPoint(&weakPoint,ben2);
```
ARC中关于__weak指针的一点优化
当我们访问一个__weak指针指向的变量时,该__weak指针可能随时会被设置为nil。因此,编译器会将对其进行函数传值和Block捕值一样的优化
示例:
Person* ben = [Person new];
Person* __weak weakPoint = ben;
NSLog(@"ben retainCount:%d",_objc_rootRetainCount(ben));
NSLog(@"ben retainCount:%d",_objc_rootRetainCount(weakPoint));
NSLog(@"ben retainCount:%d",_objc_rootRetainCount(ben));
代码执行结果:
2015-11-25 17:27:33.028 Test_ARC[4889:47866] ben retainCount:1
2015-11-25 17:27:33.030 Test_ARC[4889:47866] ben retainCount:2
2015-11-25 17:27:33.030 Test_ARC[4889:47866] ben retainCount:1
Property
前面我们了解了ARC中的所有者修饰符,由于我们常常在类的接口中使用@property来生成属性和属性的property。因此,我们需要了解在ARC中,如何指定使用@property生成的属性的所有者修饰符
参数 所有者修饰符
assign __unsafe_unretained
retain __strong
copy __strong
weak __weak
unsafe_unretained __unsafe_unretained
strong __strong
写在最后
这篇文章只是我个人对ARC的一些理解,很多实现肯定与Apple的实现不同,请各位不同喷我。
ARC机制的核心其实就是“持有者计数”。