Objective C 总结(十四):内存管理
Objective-C的对象都是动态创建的,Cocoa 采用了引用计数的技术进行对象生存周期的管理,对象新创建时引用计数为1,发送remain消息后对象引用计数+1,发送release消息后对象引用计数-1,当对象引用计数为0时,Objective-C运行会向对象发送dealloc消息进行销毁对象回收内存。注意:自己可以重写dealloc方法,但不要自己调用它。
Objective-C提供了三种内存管理方式:manual retain-release(MRR,手动管理),automatic reference counting(ARC,自动引用计数),garbage collection(垃圾回收)。iOS不支持垃圾回收;ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存。
NSObject提供了基本的引用计数管理方法,
- (id) retain; - (void) release; - (unsigned) retainCount; - (id) autorelease;
int main (int argc, const char *argv[]) { NSAutoreleasePool *pool; pool = [[NSAutoreleasePool alloc] init]; // do something [pool release]; return (0); } // main
NSAutoreleasePool相当于一个对象集合类,在发送release消息时会向池中所有对象发送release消息。提供了简洁的写法
@autoreleasepool { // Code that creates autoreleased objects. }
管理规则:
- 用new, alloc, copy创建的对象,初始引用计数为1,当不再需要对象时,需要向对象发送release消息。
- retain了某个对象,要负责release这个对象。
- 工厂方法创建的对象,要应用autorelease消息,不需要向对象发送release了。
+ nameWithString: (NSString *)str { return [[NSString alloc] initWithString: str] autorelease]; }
- NSAutoreleasePool的release时间是确定的,要么在收到release时release,要么在事件循环结束时使用AppKit release。
- 在Mac OS X平台上提供的垃圾回收器,启用了后内存管理的指令都会变成空指令。垃圾回收器与NSAutoreleasePool一样,也是在事件循环结束时触发的。
ARC
ARC is supported in Xcode 4.2 for OS X v10.6 and v10.7 (64-bit applications) and for iOS 4 and iOS 5. Weak references are not supported in OS X v10.6 and iOS 4.
ARC的一个基本原则: 只要某个对象被任一strong指针引用,那么它将不会被销毁。当对象没有被任何strong指针引用时,那么就将被销毁。
ARC强制执行一些新的规则:
- 你不能再调用 dealloc 或者实现、调用 retain, release, retainCount, autorelease, 同样@selector(retain), @selector(release)也是不允许的, 当然你可以实现 dealloc 方法,来管理你的实例变量,或者你会调用 [systemClassInstance setDelegate:nil]等.定制的 dealloc 方法不需要写 [super dealloc],这个动作会默认调用。
ARC只在Cocoa框架中有效,其它框架仍然需要自己管理,如 CFRetain, CFRelease 和相关Core Foundation方法。 - 你不能使用 NSAllocateObject 或 NSDeallocateObject,创建对象用 alloc,运行时负责release对象。
- 你不能在 C 结构体中使用对象指针,因此下面代码是不可用的
typedef struct { UIImage *selectedImage; UIImage *disabledImage; } ButtonImages;
- 不能随意在 id 与 void * 之间随意转换。编译器同样是无法管理 void * 这类 Core Foundation类型的东东,都要用 Managing Toll-Free Bridging 进行生命同期的管理。
- 你不能再使用 NSAutoreleasePool 对象,ARC 提供了性能更好的 @autoreleasepool block 替换原来这类使用方式。
- 你不能使用内存区。你不需要再使用 NSZone 等这类对象,因为现在Objective-C运行时已经忽略NSZone了,所以没必要再使用NSZone了
- 不能使用 new 开头的属性名,但你可以手动指定 getter 方法名
// 非法: @property NSString *newTitle; // OK: @property (getter=theNewTitle) NSString *newTitle;
ARC引入了新的对象生存周期修饰
__strong
__weak
__unsafe_unretained
__autoreleasing
// 修饰属性时不需要__前缀,修饰局部变量是要加__前缀
@property(strong) MyClass *myObject; @property(weak) MyClass *myObject;
- strong。是默认使用的修饰。对象保持alive,与strong指针一样的生存周期。
- weak。指定一个引用,但不会保持对象alive,当weak引用到一个没有strong引用的对象时,weak引用会设置为nil。
- unsafe_unretained。指定一个引用,但不会保持对象alive,当引用的对象没有strong引用时,不会设置为nil。
- autoreleasing。用于指示参数按引用传递(id *)并且在返回时autoreleased。
当在stack上使用__weak变量时要小心,看下面代码
NSString * __weak string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; NSLog(@"string: %@", string);
字符串在实例化后没有strong指针引用它,因此它马上就被释放了,在NSLog中string变量的值就是nil。
按引用传递对象时也要小心,看下面代码
NSError *error; BOOL OK = [myObject performOperationWithError:&error]; if (!OK) { // Report the error. // ...
error默认是
NSError * __strong e;
方法声明是这样的
- (BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
编译器重写后的代码就是
NSError * __strong error; NSError * __autoreleasing tmp = error; BOOL OK = [myObject performOperationWithError:&tmp]; error = tmp; if (!OK) { // Report the error. // ...
当本地变量声明(strong *error)和函数的参数((NSError * autoreleasing )error)不匹配的时候,编译器会创建一个临时变量。当你获得一个strong变量的地址时,你可以初始化一个id strong 的指针来声明 ,这样你就可以获得指针的原型,或者你可以声明一个变量为 __autoreleasing。
避免strong引用循环
你可以使用生命周期修饰符来避免Strong引用周期。例如,当你制作了一组父子结构的对象,而且父类要引用子类,则会出现Strong引用周期;反之,当 你将一个父类指向子类为strong引用,子类指向父类为weak引用,就可以避免出现Strong引用周期。当对象包含block objects时,这样的情况会变的更加隐性。
在手动内存管理模式下, __block id x
; x不会被 retaining,在ARC模式下,__block id x
, 默认被retaining。为了使手动内存管理模式代码可以在ARC模式下正常工作, 你可以用 __unsafe_unretained
来修饰__block id
x;。就和”unsafe_unretained”字面上的意思一样, 不过,这样一个non-retained变量是危险的(因为它会变成一个野指 针) 会带来不良后果。有两种更好一点的方法来处理,一是使用weak (当你不需要支持iOS 4或OS X v10.6), 二是设__block值为nil,结束他的生命周期。
MyViewController *myController = [[MyViewController alloc] init…]; // ... myController.completionHandler = ^(NSInteger result) { [myController dismissViewControllerAnimated:YES completion:nil]; }; [self presentViewController:myController animated:YES completion:^{ [myController release]; }];
你可以使用 __block修饰符然后设置myController的值为nil 替代上面的方式:
MyViewController * __block myController = [[MyViewController alloc] init…]; // ... myController.completionHandler = ^(NSInteger result) { [myController dismissViewControllerAnimated:YES completion:nil]; myController = nil; };
再者,你也可以使用一个__weak临时变量
MyViewController *myController = [[MyViewController alloc] init…]; // ... MyViewController * __weak weakMyViewController = myController; myController.completionHandler = ^(NSInteger result) { [weakMyViewController dismissViewControllerAnimated:YES completion:nil]; };
MyViewController *myController = [[MyViewController alloc] init…]; // ... MyViewController * __weak weakMyController = myController; myController.completionHandler = ^(NSInteger result) { MyViewController *strongMyController = weakMyController; if (strongMyController) { // ... [strongMyController dismissViewControllerAnimated:YES completion:nil]; // ... } else { // Probably nothing... } };
有些情况,如果类不兼容__weak时可以使用__unafe_unretained,
Stack变量初始化为nil
strong, weak, autoreleasing stack变量隐式初始化为nil,一般就是指局部变量
- (void)myMethod { NSString *name; NSLog(@"name: %@", name); }
使用编译器标记启用或禁用ARC
启用-fobjc-arc
禁用-fno-objc-arc
Managing Toll-Free Bridging
由于ARC不能管理Core Foundation Object的生命周期,所以在Core Foundation和ARC之间,我们需要使用到bridge,bridge_retained和__bridge_transfer三个转换关键字。
- __bridge,转换类型指针,不转换内存管理权
- __bridge_retained 或 CFBridgingRetain 将Objective-C类型转换成Core Foundation类型,同样也转移内存管理权,后续需要使用CFRelease或者相关方法来释放对象;
- __bridge_transfer or
CFBridgingRelease 将非Objective-C类型转换成Objective-C类型,同时转移内存管理到ARC。
- (void)logFirstNameOfPerson:(ABRecordRef)person { NSString *name = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty); NSLog(@"Person's first name: %@", name); [name release]; }
可以用下面代码替换
- (void)logFirstNameOfPerson:(ABRecordRef)person { NSString *name = (NSString *)CFBridgingRelease(ABRecordCopyValue(person, kABPersonFirstNameProperty)); NSLog(@"Person's first name: %@", name); }
编译器会处理从Cocoa返回的CF对象
NSMutableArray *colors = [NSMutableArray arrayWithObject:(id)[[UIColor darkGrayColor] CGColor]]; [colors addObject:(id)[[UIColor lightGrayColor] CGColor]];
使用Ownership Keywords转换方法参数
NSArray *colors = [NSArray arrayWithObjects: [UIColor darkGrayColor], nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, locations);