ARC - strong和weak指针
提示:本文中所说的"实例变量"即是"成员变量","局部变量"即是"本地变量"
一、简介
ARC是自iOS 5之后增加的新特性,完全消除了手动管理内存的烦琐,编译器会自动在适当的地方插入适当的retain、release、autorelease语句。你不再需要担心内存管理,因为编译器为你处理了一切
注意:ARC 是编译器特性,而不是 iOS 运行时特性(除了weak指针系统),它也不是垃圾收集器。因此 ARC 和手动内存管理性能是一样的,有时还能更加快速,因为编译器还可以执行某些优化
二、原理
ARC 的规则非常简单:只要有变量指向对象,对象就会存在内存。当指针指向新值,或者指针不再存在时,相关联的对象就会自动释放。这条规则对于实例变量、synthesize属性、局部变量都是适用的
三、strong指针
控制器中有个文本输入框框属性
1.如果用户在文本框中输入mj这个字符串
那么就可以说,nameField的text属性是NSString对象的指针,也就是拥有者,该对象保存了文本输入框的内容
2.如果执行了如下代码
- NSString *name = self.nameField.text;
3.随后用户改变了输入框的内容,比如
此时nameFeild的text属性就指向了新的NSString对象。但原来的NSString对象仍然还有一个所有者(name变量),因此会继续保留在内存中
4.当name变量获得新值,或者不再存在时(如局部变量方法返回时、实例变量对象释放时),原先的NSString对象就不再拥有任何所有者,retain计数降为0,这时对象会被释放
如,给name变量赋予一个新值
- name = @"Jake";
我们称name和nameField.text指针为"Strong指针",因为它们能够保持对象的生命。默认所有实例变量和局部变量都是Strong指针
四、weak指针
weak型的指针变量仍然可以指向一个对象,但不属于对象的拥有者
1.执行下面的代码
__weak NSString *name = self.nameField.text;
name变量和nameField.text属性都指向同一个NSString对象,但name不是拥有者
2.如果文本框的内容发生变化,则原先的NSString对象就没有拥有者,会被释放,此时name变量会自动变成nil,称为空指针
weak型的指针变量自动变为nil是非常方便的,这样阻止了weak指针继续指向已释放对象,避免了野指针的产生,不然会导致非常难于寻找的Bug,空指针消除了类似的问题
3.weak指针主要用于“父-子”关系,父亲拥有一个儿子的strong指针,因此父亲是儿子的所有者;但为了阻止所有权循环,儿子需要使用weak指针指向父亲。典型例子是delegate模式,你的ViewController通过strong指针(self.view)拥有一个UITableView, UITableView的dataSource和delegate都是weak指针,指向你的ViewController
五、strong和weak指针的使用注意
1.下面代码是有问题的:
- __weak NSString *str = [[NSString alloc] initWithFormat:@"1234"];
- NSLog(@"%@", str); // 打印出来是"(null)"
2.一般的指针变量默认就是strong类型的,因此一般我们对于strong变量不加__strong修饰,以下两行代码是等价的:
- NSString *name = self.nameField.text;
- __strong NSString *name = self.nameField.text;
3.属性可以是strong或weak,写法如下
- @property (nonatomic, strong) NSString *name;
- @property (nonatomic, weak) id delegate;
4.以下代码在ARC之前是可能会行不通的,因为在手动内存管理中,从NSArray中移除一个对象时,这个对象会发送一条release消息,可能会被立即释放。随后NSLog()打印该对象就会导致应用崩溃
- id obj = [array objectAtIndex:0];
- [array removeObjectAtIndex:0];
- NSLog(@"%@", obj);
六、ARC小结
1.有了ARC,我们的代码可以清晰很多,你不再需要考虑什么时候retain或release对象。唯一需要考虑的是对象之间的关联,也就是哪个对象拥有哪个对象?
2.ARC也有一些限制:
1> 首先ARC只能工作于Objective-C对象,如果应用使用了Core Foundation或malloc()/free(),此时还是需要你来手动管理内存
2> 此外ARC还有其它一些更为严格的语言规则,以确保ARC能够正常地工作
3.虽然ARC管理了retain和release,但并不表示你完全不需要关心内存管理的问题。因为strong指针会保持对象的生命,某些情况下你仍然需要手动设置这些指针为nil,否则可能导致应用内存不足。无论何时你创建一个新对象时,都需要考虑谁拥有该对象,以及这个对象需要存活多久
4.ARC还能很好地结合C++使用,这对游戏开发是非常有帮助的。对于iOS 4,ARC有一点点限制(不支持weak指针),但也没太大关系
七、ARC使用注意总结
要想将非ARC的代码转换为ARC的代码,大概有2种方式:
1.使用Xcode的自动转换工具
2.手动设置某些文件支持ARC
一、Xcode的自动转换工具
Xcode带了一个自动转换工具,可以将旧的源代码转成ARC模式
1.ARC是LLVM 3.0编译器的特性,而现有工程可能使用老的GCC 4.2或LLVM-GCC编译器,因此首先需要设置使用LLVM 3.0编译器:
(现使用的XCode4.5,LLVM 3.0已经升级到LLVM 4.1)
最好也选上Warnings中的Other Warning Flags 为 -Wall,这样编译器就会检查所有可能的警告,有助于我们避免潜在的问题
2.Build Options下面的Run Static Analyzer选项也最好启用,这样每次Xcode编译项目时,都会运行静态代码分析工具来检查我们的代码
3.设置"Objective-C Automatic Reference Counting"选项为YES,不过Xcode自动转换工具会自动设置这个选项,这里只是说明一下如何手动设置
4.打开Xcode的自动转换工具
5.Xcode会显示一个新窗口,让你选择哪些文件需要转换
点击Check按钮,Xcode可能会弹出对话框提示项目不能转换为ARC,需要你准备好转换(这里暂时省略详细说明)
6.如果没有什么警告、错误了,就会弹出一下提示窗口:
7.点击Next,几秒钟后,Xcode会提示所有文件的转换预览,显示源文件的所有改变。左边是修改后的文件,右边是原始文件。在这里你可以一个文件一个文件地查看Xcode的修改,以确保Xcode没有改错你的源文件:
点击Save即可完成转换
8.自动转换之后,Xcode会移除所有retain、release、autorelease调用,这可能会导致代码出现其它警告、无效语法等,这些都需要自己手工进行修改
注意:Xcode的自动转换工具最好只使用一次,多次使用可能会出现比较诡异的问题。假如你第一次转换没有转换所有的文件,当你稍后试图再次转换剩余的文件时,Xcode实际上不会执行任何转换操作。因此最好一次就完成转换,没有转换的文件可以考虑手工进行修改
二、手动开启某些文件的ARC
在Compiler Flags一列加上-fobjc-arc就表示开启这个.m文件的ARC
三、禁止某些文件的ARC
在Compiler Flags一列加上-fno-objc-arc就表示禁止这个.m文件的ARC
本章介绍引入ARC后@property的使用,跟ARC之前的还是很不一样的
一、.h和.m文件的变化说明
1.对于.h头文件,主要是将属性定义由retain变为strong
- @property (retain, nonatomic)
- @property (strong, nonatomic)
2.在ARC之前,我们经常在.m中使用分类拓展来增加私有的property
- @interface MJViewController ()
- @property (nonatomic, retain) NSArray *data;
- @end
我们可以直接在.m类实现中定义private实例变量,不必写分类拓展了:
- @implementation MJViewController {
- NSArray *data;
- }
- - (void)viewDidUnload {
- [super viewDidUnload];
- data = nil;
- }
二、IBOutlet
在ARC中,所有IBOutlet属性都推荐使用weak, 这些view对象已经属于View Controller的view hierarchy,不需要再次定义为strong。因此,这些定义为weak的IBOutlet属性都不需要在IBOutlet中设置为nil
三、@property的修饰符小结
• strong : 该属性值对应 __strong 关键字,即该属性所声明的变量将成为对象的持有者,等同于"retain"
• weak : 该属性对应 __weak 关键字,与 __weak 定义的变量一致,该属性所声明的变量将没有对象的所有权,并且当对象被释放之后,对象将被自动赋值nil,记住IBOutlet应该使用weak
• unsafe_unretained : 等效于__unsafe_unretaind关键字声明的变量,等同于之前的"assign",iOS 5之前的系统用该属性代替 weak 来使用
• copy : 和之前的copy一样,复制一个对象并创建strong关联
• assign : 对象不能使用assign,但原始类型(BOOL、int、float)仍然可以使用
ARC之strong,weak 解释
先一句话总结:strong类保持他们拥有对象的活着,weak类他们拥有的对象被人家一牵就牵走,被人家一干就干死。(strong是一个好大哥所以strong,呵呵,weak是一个虚大哥所以weak,呵呵)
比如有一个对象是string类,实例是@“hello”
现有两个strong的string指针大哥a和b都指向了hello,现在b大哥把改成了指向@“hi”。那么这时候a大哥指向的值是什么呢,答案还是“hello”。然后,a大哥看hi不错,也指向了hi,那么现在hello就被都抛弃了,也就从内存中删除了。因为a大哥是strong的,既是retain或者copy的,这两个东西是可以使对象保存在计算机内存里的,所以如果即使b大哥抛弃hello,a大哥是有资本使@“hello”继续活下去。
而现在又有两个对象strong的c大哥和weak的d大哥,都指向hello,现在c大哥另有所爱,指向了之前的b大哥,同时a大哥也指向了b大哥,既现在没有strong大哥指向hello。那么现在这个weak的d大哥指向的对象就是一个屁啦,既nil。
标签:
objective-cit |
写这篇文章的缘由,是因为我泡在stackoverflow上翻帖子,看到一个名为Should IBOutlets be strong or weak under ARC? 的帖子很热,而我对被采纳为标准答案的回答也有一些话要补充,我想对于每一个初识ARC模式的人来说,都会有这个疑问,所以不妨我也来和大家探讨一下。
有人问,在ARC下,IBOutlets到底应该定义成strong 还是 weak ?支持这个答案的人最多,答案仅是摘自官方文档的一个片段:
From a practical perspective, in iOS and OS X outlets should be defined as declared properties. Outlets should generally be weak, except for those from File’s Owner to top-level objects in a nib file (or, in iOS, a storyboard scene) which should be strong. Outlets that you create will therefore typically be weak by default, because:
-
Outlets that you create to, for example, subviews of a view controller’s view or a window controller’s window, are arbitrary references between objects that do not imply ownership.
-
The strong outlets are frequently specified by framework classes (for example, UIViewController’s view outlet, or NSWindowController’s window outlet).
@property (weak) IBOutlet MyView *viewContainerSubview; @property (strong) IBOutlet MyOtherClass *topLevelObject;
大意是说,在 ARC 中,一般outlet属性都推荐使用 weak,应该使用 strong 的 outlet 是 File's Owner连接到 nib 的顶层对象。
什么是 File's Owner连接到 nib 的顶层对象呢?说白话一点,就是自定义的view,不是直接作为main view里面一个sub view直接显示出来,而是需要通过实例化创建出来的。你自己实例化,当然需要strong了,不然谁还替你保留对象所有权呢?
以上的分析都没有错,但是总觉得少了点什么。对于到底是weak 还是 strong,归根结底,还是要刨到对对象所有权的问题上,但是不便于总结出浅显易懂的规律性使用法则。于是,就会有一个又一个的特例打破文档所总结的常规,不明白规则的根是什么,还是会碰到麻烦的。
我来举一个简单的例子,创建一个程序入口指向navigation controller的工程,导航栏上拖2个按钮:
右侧按钮用于控制相机按钮的显示与否,按照文档的指示,我们在程序中定义这两个按钮应为weak属性
- #import
- @interface TestViewController : UIViewController
- {
- BOOL isShowing;
- }
- @property (nonatomic,weak)IBOutlet UIBarButtonItem *controlBtn;
- @property (nonatomic,weak)IBOutlet UIBarButtonItem *cameraBtn;
- -(IBAction)controlAction:(id)sender;
- @end
用右侧按钮,控制相机按钮的隐藏和显示:
- #import "TestViewController.h"
- @interface TestViewController ()
- @end
- @implementation TestViewController
- @synthesize cameraBtn,controlBtn;
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- // Do any additional setup after loading the view, typically from a nib.
- isShowing = YES;
- }
- - (void)viewDidUnload
- {
- [super viewDidUnload];
- // Release any retained subviews of the main view.
- }
- - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
- {
- return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
- }
- -(IBAction)controlAction:(id)sender
- {
- if (isShowing) {
- self.controlBtn.title = @"显示相机";
- self.navigationItem.leftBarButtonItem = nil;
- isShowing = NO;
- }else {
- self.controlBtn.title = @"隐藏相机";
- self.navigationItem.leftBarButtonItem = cameraBtn;
- isShowing = YES;
- }
- }
- @end
实验结果是,第一次隐藏了相机按钮后,就再也显示不出来了。原因很简单,cameraBtn指向了空,我们丢失了cameraBtn的对象所有权。
解决问题的办法有两个:
1.不在xib或者storyboard上拖相机按钮,而是用代码创建,自己控制对象所有权
2.将 cameraBtn 定义为strong
我想强调的当然是第二种方法,当然了,改成strong后,相应的也需要配合ARC做下工作:
- - (void)viewDidUnload
- {
- [super viewDidUnload];
- // Release any retained subviews of the main view.
- self.cameraBtn = nil;
- }
顺便提一下ARC其他属性的规则:
-
strong:等同于"retain",属性成为对象的拥有者
-
weak:属性是 weak pointer,当对象释放时会自动设置为 nil
-
unsafe_unretained:等同于之前的"assign",只有 iOS 4 才应该使用
-
copy:和之前的 copy 一样,复制一个对象并创建 strong 关联
-
assign:对象不能使用 assign,但原始类型(BOOL、int、float)仍然可以使用
最后一句,记忆规则,理解规则,善用规则。