ARC总结

来源:互联网 发布:网络文摘 1997- 编辑:程序博客网 时间:2024/06/08 11:42


ARC完全指南  http://pan.baidu.com/s/1i37LZox


ARC 是新 LLVM 3.0 编译器的特性,完全消除了手动内存管理的烦琐。注意 ARC 是编译器特性,而不是 iOS 运行时特性(除了 weak 指针系统),它也不是其它语言中的垃圾收集器。因此 ARC 和手动内存管理性能是一样的,有些时候还能更加快速,因为编译器还以执行某些优化。


指针保持对象的生命


ARC的规则非常简单:只要还有一个变量指向对象,对象就会保持在内存中。

self.textField.text = @"Ray";

NSString *firstName = self.textField.text;
firstName 变量成为 NSString 对象的指针,也就是拥有者,该对象保存了文本输入框的内容

随后改变文本

self.textField.text = @"Rayman";

此时:firstName = @"Ray";

只有当 firstName 获得新值,或者超出作用域(本地变量方法返回时、实例变量对象释放时),String 对象不再拥有任何所有者,retain计数降为 0,这时对象会被释放。

我们称 firstName 和 textField.text 指针为“strong”,因为它们能够保持对象的生命。默认所有实例变量和本地变量都是 strong 类型的指针。

另外还有一种“weak”指针,weak 变量仍然指向一个对象,但不是对象的拥有者:
__weak NSString *weakName = self.textField.text;

当改变文本时, weakName的值为空值nil.

weak指针主要用于“父-子”关系,父亲拥有一个儿子的strong指针,因此是儿子的所有者;但为了阻止所有权回环,儿子需要使用 weak 指针指向父亲。典型例子是 delegate 模式,你的 View Controller 通过strong 指针拥有一个 UITableView,Table view 的 data source 和delegate 都是 weak 指针,指向你的 View Controller。

        ARC 也有一些限制。首先 ARC 只能工作于 Objective-C 对象,如果应用使用了 Core Foundation 或 malloc()/free(),此时需要你来管理内存。

使用 ARC 之后,C Struct 中不能使用 Objective-C 对象,下面代码就是非法的:
typedef struct
{
UIImage *selectedImage;
UIImage *disabledImage;
} ButtonImages;
解决办法是定义一个 Objective-C 类,不使用 C Struct。

我们可以直接在.m 类实现中定义 private 实例变量:
@implementation MainViewController
{
NSOperationQueue *queue;
NSMutableString *currentStringValue;
NSMutableArray *searchResults;
SoundEffect *soundEffect;
}
我们在使用时,虽然没有定义 property,也可以直接 [self.soundEffect play];

作为 property 的最佳实践,如果你定义某个东西为 property,则你应该在任何地方都按属性来使用它。唯一例外的是 init 方法、自定义的 getter 和setter 方法。因此很多时候我们会这样写 synthesize 语句:
@synthesize propertyName = _propertyName;
实际上_propertyName 实例变量甚至可以不定义,编译器会自动为 property定义 "_*" 的实例变量


readonly property

.h 文件:
@interface WeatherPredictor
@property (nonatomic, strong, readonly) NSNumber *temperature;
@end
.m 文件:
@interface WeatherPredictor ()
@property (nonatomic, strong, readwrite) NSNumber *temperature;
@end


• __bridge_transfer:给予 ARC 所有权
• __bridge_retained:解除 ARC 所有权


为了代码更加可读和容易理解,iOS 还提供了一个辅助函数:CFBridgingRelease()。函数所做事情和 __bridge_transfer 强制转换完全一样,但更加简洁和清晰。CFBridgingRelease() 函数定义为内联函数,因此不会导致额外的开销。函数之所以命名为 CFBridgingRelease(),是因为一般你会在需要使用 CFRelease()释放对象的地方,调用 CFBridgingRelease()来传递对象的所有权。


要将 Objective-C 对象和 void *互相转换,你也需要使用__bridge 转换,
如下:
MyClass *myObject = [[MyClass alloc] init];
[UIView beginAnimations:nil context:(__bridge void *)myObject];
在 animation delegate 方法中,你再将对象强制转回来:
- (void)animationDidStart:(NSString *)animationID context:(void *)context
{
MyClass *myObject = (__bridge MyClass *)context;
. . .
}

• 使用 CFBridgingRelease(),从 Core Foundation 传递所有权给Objective-C;
• 使用 CFBridgingRetain(),从 Objective-C 传递所有权给 CoreFoundation;
• 使用__brideg,表示临时使用某种类型,不改变对象的所有权。


定时器使用弱引用

@implementation AnimatedView
{
__weak NSTimer *timer;
CFTimeInterval startTime;
CFTimeInterval lastTime;
}
但是简单地将 timer 定义为 __weak 并不能解决所有权回环,AnimatedView不再是 NSTimer 对象的拥有者,但是 NSTimer 仍然被其它对象所拥有(run loop),而且由于 timer 仍然拥有 AnimatedView 的 strong 引用,它还是会一直保持AnimatedView 存在,除非 invalidate 定时器,否则 AnimatedView 对象永远无法被释放。
这里我们可以定义一个 stopAnimation 方法来打破 retain 回环,修改dealloc 和 stopAnimation 方法如下:
- (void)stopAnimation
{
[timer invalidate], timer = nil;
}
- (void)dealloc
{
NSLog(@"dealloc AnimatedView");
}
在 AnimatedView 被释放之前,使用这个类的用户(例如DetailViewController)应该先调用 stopAnimaTion 来停止定时器


 Block 会捕获(capture)自己使用到的所有变量。如果这些变量是指针,Block 会对指向的每个对象执行一次retain。

不要在 Block 中使用 self,这意味着你不能在 Block 中使用任何 property、实例变量(因为实例变量会使用 self->ivar)、方法(method)。而只能使用局部变量,先把要使用的对象保存在局部变量中,然后在 Block 中使用这个局部变量:
NSString *text = self.artistName;
self.animatedView.block = ^(CGContextRef context, CGRect rect,
CFTimeInterval totalTime, CFTimeInterval deltaTime)
{
CGPoint textPoint = CGPointMake((rect.size.width - textSize.width)/2,
(rect.size.height - textSize.height)/2);
[text drawAtPoint:textPoint withFont:font];
};
这样就可以了,Block 只使用了局部变量,没有引用 self,因此 Block 不会捕获 DetailViewController 对象,后者在 View 关闭时能够自动被释放。


但很多时候你不能避免在 Block 中使用 self,在 ARC 以前,你可以使用以下技巧:

__block DetailViewController *blockSelf = self;
self.animatedView.block = ^(CGContextRef context, CGRect
rect, CFTimeInterval totalTime, CFTimeInterval deltaTime)
{
. . .
[blockSelf.artistName drawAtPoint:textPoint withFont:font];
};
__block 关键字表示 Block 不 retain 这个变量,因此可以使用blockSelf.artistName 来访问 artistName 属性,而 Block 也不会捕获 self 对象。不过 ARC 中不能使用这个方法,因为变量默认是 strong 引用,即使标记为__block 也仍然是 strong 类型的引用。这时候__block 的唯一功能是允许你修改已捕获的变量(没有__block 则变量是只读的)。
ARC 的解决办法是使用 __weak 变量:
__weak DetailViewController *weakSelf = self;
self.animatedView.block = ^(CGContextRef context, CGRect rect, CFTimeInterval totalTime, CFTimeInterval deltaTime)
{
DetailViewController *strongSelf = weakSelf;
if (strongSelf != nil)
{
CGPoint textPoint = CGPointMake((rect.size.width
- textSize.width)/2, (rect.size.height - textSize.height)/2);
[strongSelf.artistName drawAtPoint:textPoint withFont:font];
}
};
weakSelf 变量引用了 self,但不会进行 retain。我们让 Block 捕获 weakSelf而不是 self,因此不存在所有权回环。但是我们在 Block 中不能直接使用weakSelf,因为这是一个 weak 指针,当 DetailViewController 释放时它会自动变成 nil。虽然向 nil 发送 message 是合法的,我们在 Block 中仍然检查了对象是否存在。这里还有一个技巧,我们临时把 weakSelf 转换为 strong 类型的引用strongSelf,这样我们在使用 strongSelf 的时候,可以确保DetailViewController 不会被其它人释放掉!
对于简单的应用,这人技巧可能没太大必要,我们可以直接使用 weakSelf。
因为 AnimatedView 属于 controller 的 view hierarchy,DetailViewController不可能在 AnimatedView 之前被释放。
但是假如 Block 被异步使用,则创建 strong 引用来保持对象存活就是必要的。此外,如果我们直接使用 artistName 实例变量,可能会编写如下代码:
__weak DetailViewController *weakSelf = self;

self.animatedView.block = ^(CGContextRef context, CGRect
rect, CFTimeInterval totalTime, CFTimeInterval deltaTime)
{
[weakSelf->artistName drawAtPoint:textPoint withFont:font];
};
在 DetailViewController 被释放之前,这段代码都能正常工作,一旦DetailViewController 被释放,nil->artistName 肯定会导致应用崩溃!因此,如果你需要在 ARC 环境中使用 Block,并且想避免捕获 self,推荐采用如下代码模式:
__weak id weakSelf = self;
block = ^()
{
id strongSelf = weakSelf;
if (strongSelf != nil)
{
// do stuff with strongSelf
}
};


假设有个 DelayedOperation 类,它等待"delay"秒,然后执行 block。在 block中你会 autorelease 释放掉 DelayedOperation 实例。由于 block 捕获了"operation"实例,从而保持了对象的生命,下面代码模式在 ARC 之前是可以正常工作的:
DelayedOperation *operation = [[DelayedOperation alloc] initWithDelay:5
block:^
{
NSLog(@"Performing operation");
// do stuff
[operation autorelease];
}];
但在 ARC 中,你不允许调用 autorelease,因此代码变成如下:
DelayedOperation *operation = [[DelayedOperation alloc] initWithDelay:5
block:^
{
NSLog(@"Performing operation");
// do stuff
}];
猜猜会发生什么?block 永远不会被执行到。DelayedOperation 实例在创建后立即就会被释放,因为没有人保持对象的生命。解决办法是让 block 捕获operation 实例对象,并在完成任务之后设置为 nil,从而释放该 operation 对象:
__block DelayedOperation *operation = [[DelayedOperation
alloc] initWithDelay:5 block:^
{
NSLog(@"Performing operation");
// do stuff
operation = nil;
}];
现在 block 会保持对象存活,注意"opoeration"变量必须声明为__block,因为我们要在 block 里面修改它为 nil。

+ (id)sharedInstance
{
static GradientFactory *sharedInstance;
static dispatch_once_t done;
dispatch_once(&done, ^{
sharedInstance = [[GradientFactory alloc] init];
});
return sharedInstance;
}

即使多个线程同时执行这个 block,Grand Central Dispatch 库的
dispatch_once()函数也能确保 alloc 和 init 只会被执行一次。


retain 的对象在没有变量指向它时就会立即释放,但 autorelease 对象则是在 autorelease pool 排干(drain)时才会被释放。在 ARC 之前,你需要调用NSAutoreleasePool 对象的 [drain] 或 [release] 方法,现在则是直接在@autoreleasepool 块退出时进行 drain:

@autoreleasepool
{
NSString *s = [NSString stringWithFormat:. . .];
} // the string object is deallocated here
但是如果你像下面这样编写代码,则即使NSString对象在@autoreleasepool块中创建,stringWithFormat:方法也确实返回了一个 autorelease 对象,但变量 s 是 strong 类型的,只要 s 没有退出作用域,string 对象就会一直存在:
NSString *s;
@autoreleasepool
{
s = [NSString stringWithFormat:. . .];
}
// the string object is still alive here
使用 __autoreleasing 可以使 autorelease pool 释放掉该对象,它告诉编译器这个变量指向的对象可以被 autorelease,此时变量不是 strong 指针,string 对象会@autoreleasepool 块的末尾被释放。不过注意变量在对象释放后,仍然会继续指向一个死掉的对象。如果你继续使用它,应用就会崩溃。代码
如下:
__autoreleasing NSString *s;
@autoreleasepool
{
s = [NSString stringWithFormat:. . .];
}
// the string object is deallocated here
NSLog(@"%@", s); // crash!


最后?
ARC 是对 Objective-C 语言的一个重要改进,虽然 iOS 5 中的其它新东西也很 cool,但 ARC 几乎完全改进了我们编写 App 的方式。虽然现在 ARC 仍然处于过渡期,使用的过程中会遇到一些兼容性问题。但根据 Apple 的态度,以及 iOS平台的发展,ARC 在不久的将来肯定会成为主流!
最后再教你一招!如果你维护一个可重用的库,并且不想切换到 ARC(还不想吗?),你可以使用预处理指令在必要时保持与 ARC 兼容:
#if __has_feature(objc_arc)
// do your ARC thing here
#endif
或者假如你还想支持老的 GCC compiler:
#if defined(__has_feature) && __has_feature(objc_arc)
// do your ARC thing here
#endif




























0 0
原创粉丝点击