自从引进了iOS5,iPhone,iPad,iPodTouch编程改变了很多。整个runtime和objective-c的编码方式发生了戏剧性的改变。ARC(Automatic Reference Counting - 自动引用计数)技术现在引入到LLVM编译器(LLVM Compiler)中,在某些方面,使程序更加灵活,另一方面使runtime更加脆弱。 在这章中,我们将谈论,在objective-c runtime ARC下,如何使用对象,以及怎么管理它们。
从手工引用计数到自动引用计数(ARC) Problem
学习Automatic Reference Counting(自动引用计数器),苹果的新的编译器解决了令人头痛的问题:在Objective-C中,当使用对象的时候,处理对象,以及管理内存。
Solution
由最新的LLVM编译器引进的新的属性存储技术:strong, weak,和unsafe_unretained Discussion
在最新的LLVM编译器中,是使用了Automatic Reference Counting(ARC),我们需要处理strong,weak,或者unsafe和unretained存储技术。在ARC模式之下的对象,是由 :
strong:这种类型的对象在run-time的时候自动增加引用计数,并在它的范围结束之前有效,之外就会被自动释放【will be valid until the end of its scope, where it will automatically be released】 。熟悉Objective-C传统的内存管理办法,这个关键字和retain关键字相似。
weak:这是 “归零弱引用 - zeroing weak referencing”。如果变量使用这个关键字定义,当对象(变量指向的内存)被销毁了,这个变量被设置为nil。例如,你有一个strong的string属性和一个weak的string属性,并将weak属性的值设置为strongoing属性的值,当strong属性被销毁了,那么weak属性就会被设置为nil。
unsafe_unretained:简单的将一个变量指向另一个变量。设置对象的新数值,不会增加它的引用计数,它只是简单的为变量分配了一个对象。
默认,所有的局部变量(local variables)是strong变量。相反,属性必须明确指定它们的存储属性( storage attribute )。换句话,除了存储属性(a storage attribute,默认的是 strong properties),编译器不会承担所有属性的管理。所以,你需要确定将你的属性指定为存储属性(a storage attribute)。让我们看一看strong属性的例子。 #import <UIKit/UIKit.h>
@interface Moving_from_Manual_Reference_Counting_to_ARCAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) NSString *string1;
@property (nonatomic, strong) NSString *string2;
@end
如果使用@"String 1"初始化string1,并将string1的属性值分配给string2,那么有着存储属性(storage attribute)的string2属性将会保存string1的值,即使在string1销毁后,string2仍然保存@"String 1"值。 #import "Moving_from_Manual_Reference_Counting_to_ARCAppDelegate.h"
@implementation Moving_from_Manual_Reference_Counting_to_ARCAppDelegate @synthesize window = _window;
@synthesize string1;
@synthesize string2; - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.string1 = @"String 1";
self.string2 = self.string1; self.string1 = nil;
NSLog(@"String 2 = %@", self.string2); self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
输出为:String 2 = String 1
在声明属性(declaring properties)的时候strong, weak, unsafe_unretained会经常被用到。即使在声明局部变量的时候你也可以使用这些存储说明符(storage specifiers),但是你需要对说明符做一点修改。strong等效的嵌入说明符为__strong,weak等效的嵌入说明符为__weak,unsafe_unretained等效的嵌入说明符为__unsafe_unretained。(记住这些关键字是以2个下划线开头),例子如下: - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ /* All local variables are by default strong so just emphasis that.
We really don't have to mention __strong for the first variable but to make it clear,
we will set it. No harm in doing so. */ __strong NSString *yourString = @"Your String";
__weak NSString *myString = yourString;
yourString = nil;
__unsafe_unretained NSString *theirString = myString;
/* All pointers will be nil at this time */ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
} 当程序员声明的object被销毁了,这时候要解决很多问题,但是,ARC中的“归零弱引用 - zeroing weak referencing”很有用,帮助程序员解决这种情况下的很多问题。weak引用:当指针指向的对象被销毁了,那么weak引用的对象(a zeroing reference's object)会被设置为nil。所有指向被回收对象的weak引用(the weak referencing pointers),都会被设置为nil。 unsafe_unretained存储说明符是真正安全的,就像它的名字寓意。它安全的原因是:当unsafe_unretained变量指向的对象(object)被回收销毁了,这个变量会被设置为nil,并且将会指向内存中的a dangling location。访问这个地址可能引起程序的崩溃。为了避免这种情况,你应该使用“归零弱引用存储说明符 - zeroing weak referencing storage specifier”,weak或者内嵌(inline equivalent)说明符。 我们看一下“归零弱引用存储说明符 - zeroing weak referencing storage specifier”的例子: #import <UIKit/UIKit.h>
@interface Moving_from_Manual_Reference_Counting_to_ARCAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) NSString *string1;
@property (nonatomic, weak) NSString *string2; @end 如果程序第一次运行,我们将会初始化strong类的string1属性,然后将string1分配给string2.然后我们将string1的值设为nil。然后我们等待,这绝对是至关重要的。如果我们在将string1设置为nil之后立即答应string2的值,可能你会得到不正确的结果,而不是nil。所以,你需要确保你的应用程序的runloop除去所有无效的对象。为了实现这个目标,我们在应用程序将要进入后台的时候,我们将打印string2的值。(这是在用户将另一个应用程序移到前台时,你的应用程序进入后台)。一旦程序在后台运行,我们知道runloop已经除去了内存中所有无效对象,并且我们得到的结果将是正确的: /* 3 */
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
self.string1 = [[NSString alloc] initWithUTF8String:"String 1"];
self.string2 = self.string1; self.string1 = nil;
/* All pointers will be nil at this time */
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible]; return YES;
} - (void)applicationDidEnterBackground:(UIApplication *)application{
NSLog(@"String 2 = %@", self.string2); } 现在运行程序,等到1-2秒钟后,点击Home按钮。你将会注意到接下来的结果将会被打印到控制台中: String 2 = (null) 这很容易证明ARC的“归零弱引用 - zeroing weak referencing”,实现的很完美。现在检查unsafe_unretained存储说明符是多么的危险,代码例子: #import <UIKit/UIKit.h>
@interface Moving_from_Manual_Reference_Counting_to_ARCAppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window;
@property (nonatomic, strong) NSString *string1;
@property (nonatomic, unsafe_unretained) NSString *string2; @end AppDelegate的实现部分,就像我们之前实现的一样,在程序进入后台的时候打印string2的值,并重复打开和最小化程序的操作,程序和crash!这就意味但我们的应用程序被送到后台的时候,我们试图打印内存中无效对象(string2指向的对象)的内容。既然,string2属性不安全并且 unretained(没有retain),所以string2不知道它指向的对象(in string1),当string1被设置为nil,已经被回收销毁。 除了,上述的3个存储说明符(storage specifiers),我们还可以使用__autoreleasing说明符。当我们想要向方法传递对象的引用的时候,这个存储说明符(storage specifiers)是最方便的。例如,如果方法需要向调用方法(the caller method),传递一个NSError的error对象,方法将会向调用方法(the caller method)传递一个未初始化并且未分配的NSError实例对象。这就意味着调用者不需要分配这个error对象,所以,我们的方法就应该这样做。为了实现这个,error参数需要是自动释放对象,在合适的时间点的时候,通过runtime释放。 - (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{ //the caller method,声明参数的类型是自动释放的类型 NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil]; NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil]; NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys]; *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
}
- (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSError *error = nil;
[self generateErrorInVariable:&error];
NSLog(@"Error = %@", error);
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible];
return YES;
}