这2年其实有很多想记录和共享的东西,但是直到今天才有勇气动笔。
过去的几年里,逐渐实现了从技术者往管理者的转型。但也深刻地体会到技术的敏感度对于管理者的重要性 ---- 没有体系的专业技能知识,没有从技术角度发现问题,解决问题的能力,没有对团队成员技能特点的掌握,最终是成为不了一名合格的技术管理人员的。
另一个方向,对人关系的Soft Skills也是体现管理者领导力的一项重要衡量标准。从团队组建,人员配置,到组织部门间资源协调,信息沟通,冲突管理,这些都淋漓尽致地体现了管理者的对人关系能力。怎样激励下属,怎样让客户满意,怎样高效地解决问题,说起来好像很容易,但是实际实施起来有很多外在/内在的要因阻碍我们的前进。不夸张的说,比起IQ来,EQ的能力往往决定了我们项目的成败。
PMBOK里面有很多理论的东西指导我们成为一名专业的项目经理人。但是只是照搬理论是丝毫没有用处的(当然除过拿证书以外),更多的是对其思想的理解,渗透,并化为自己的东西。就像马云老师教导我们的:很难改变一个人行为,但可以改变一个人的思想。日本实业家稻盛和夫(京瓷和KDDI的创始人,现在是日航CEO),曾提出人生成功方程式: 创造力 = 能力 x 热情 x 思维方式。这个公式里面,「能力」和「热情」的取值范围是 0 ~100的话,「思维方式」就是 -100 ~100。换句话说,「思维方式」就是决定失败和成功的主要因素。改变思维方式,改变一个人的心智,人生和事业就会有180度大转弯;有能力,有热情,但是思维方式却犯了方向性错误,仅此一点就会得到相反的结果。
今后的文章中,我会探索地做一个转型,一个由专业技术者往技术管理者的转型。同时,我坚信对技术的追求是永无止境的,也尽量从身边的工作和学习中多总结一些经验分享给大家。
]]>敏捷开发实践(3) — 培养敏捷开发团队
敏捷开发的真谛是适应变化的情况,让开发持续,并改善其过程。它并不是单纯的用来缩短发布时间,提高工作效率,增强产品品质的手段,而是强调一种随机应变,持续改善的理念,一种过程思想。学习敏捷开发,首先考虑的是人。
软件是由人开发出来的,这是软件开发的大前提,不管你是用敏捷还是不用敏捷。
如果回到上世纪8、90年代,软件开发往往让人联想到个人英雄主义;那个时候,新兴的IT行业涌现出了类似求伯君,王江民,梁肇新等为大众所熟悉的高手。在他们身上凝聚了浓厚个人英雄色彩,并被许多那个时候成长起来的程序员所崇拜。但是,日趋庞大的软件项目和复杂需求需要更多人以标准化的软件工程方法去实现。这个过程科学而严谨,软件开发的每个步骤开始细分,程序员的工作定位开始清晰,随之而来,程序员的个人英雄色彩开始逐渐淡化,消失。
现在的一个大型软件项目已不再是依赖几个软件开发高手就能奏效的了。一个团队,一个协调一致的组织架构往往成了项目成功与否的关键因素。
敏捷开发正是一种注重“改造人”的开发手法,培养团队成员,构造成熟团队是敏捷的终极目标。而要培养优秀的敏捷团队, 组织过程架构是其重中之重。
每个敏捷团队都是不一样的,他们事前并没有统一的 组织过程架构。相反作为敏捷团队需要自己去发掘,整理,完善,最终建立适合自己的敏捷开发过程架构。这里从心态和实践两个方面来讨论。
敏捷开发过程就是一个持续和反复的过程,其中有一个出现频度很高的词 — 改善;这就是说敏捷过程就是一个逐渐变大,逐渐成长的过程。通过不停的反复,实践,改善,最终完善产品自身和团队本体。
所以说项目开始时并不需要考虑所有的状况,指定终极的目标,以及理想的架构,Just Do it!仅此而已。
过程中发生问题怎么办?没关系,在迭代结束,回顾改善中发现问题,并在下个迭代中改善它既可。也即是说,「问题发生」本身不是问题,只要有应对问题的过程环节就OK。
正如前面讲到的,敏捷开发是一种思想,他并没有给你划定条条框框,一切随实际情况而改变。
但是,我们往往不由自主的陷入体质和过程的怪圈 — 既然作为一个体系,一个过程管理架构,那么我们就要尽量维系它,遵循其起初制定,或其他地方吸收来的规则。而不管该架构形式是否适合自身的团队,一律照搬照抄。一旦出了问题,往往有「敏捷过程就是这样」,「架构没有问题」,「我们哪里用的不对而已」等等想法。不从组织过程架构中找问题是危险的。
组织过程架构是需要敏捷团队去建立和完善的,不管是从其他地方得到的经验,还是自己制定的规则,该体系都有可能在过程中出现不符合实际情况的问题。在迭代改善中发现它,在计划中完善它,在过程中改善它,是我们需要做的;而不要认为它是架构本身的问题而搁置它,不闻不问只会错上加错。
万事开头难,怎样才能减小开端的难度。从小的阶段目标做起,一边确认一边递进式的扩大作业范围。先是在小的规模下(个人或者是pair开发)体验敏捷开发的思想,然后逐渐扩大规模(团队或者组织内部)。随着规模的扩大,关系者的增加,影响范围也随之变大,需要考虑的事情变多,敏捷的难度也会伴随增加。这时,也是验证敏捷开发者应对变化能力的时候。
持续集成是指用自动化的工具验证成果物,并且每天至少一次。持续集成的目的是为了早起发现系统(除过软件成果物本身,整个敏捷架构过程也是该系统一部分)的问题,从而频繁的整合,检查。
站会顾名思义就是站立着开会,一般会议持续10~15分钟,站久了累了就没有意义了。开站会的目的就是确认工作的进度状况,需要周知的事项,问题点等。基本上每天早晨实施的实例比较多。
站会是一种高效的沟通方式,作为工作开始的合意,问题点的共享等方面往往有意想不到的效果。
如果想更加紧密,频繁地整合敏捷系统,和早会对应,在下班前用5到10分开一下站立的“晚会”往往有更好的效果。这里,我们回顾每日工作的情况,检验本日的工作成果。通过“晚会”,我们可以:
如果有加班的情况,在“晚会”上周知团队的其他成员。如果有早退的情况,可以在早会的时候给大家通知,有必要的情况下,可以调整“晚会”的开始时间点。
]]>敏捷开发实践(3) — 培养敏捷开发团队
敏捷开发的真谛是适应变化的情况,让开发持续,并改善其过程。它并不是单纯的用来缩短发布时间,提高工作效率,增强产品品质的手段,而是强调一种随机应变,持续改善的理念,一种过程思想。学习敏捷开发,首先考虑的是人。
软件是由人开发出来的,这是软件开发的大前提,不管你是用敏捷还是不用敏捷。
如果回到上世纪8、90年代,软件开发往往让人联想到个人英雄主义;那个时候,新兴的IT行业涌现出了类似求伯君,王江民,梁肇新等为大众所熟悉的高手。在他们身上凝聚了浓厚个人英雄色彩,并被许多那个时候成长起来的程序员所崇拜。但是,日趋庞大的软件项目和复杂需求需要更多人以标准化的软件工程方法去实现。这个过程科学而严谨,软件开发的每个步骤开始细分,程序员的工作定位开始清晰,随之而来,程序员的个人英雄色彩开始逐渐淡化,消失。
现在的一个大型软件项目已不再是依赖几个软件开发高手就能奏效的了。一个团队,一个协调一致的组织架构往往成了项目成功与否的关键因素。
敏捷开发正是一种注重“改造人”的开发手法,培养团队成员,构造成熟团队是敏捷的终极目标。而要培养优秀的敏捷团队, 组织过程架构是其重中之重。
每个敏捷团队都是不一样的,他们事前并没有统一的 组织过程架构。相反作为敏捷团队需要自己去发掘,整理,完善,最终建立适合自己的敏捷开发过程架构。这里从心态和实践两个方面来讨论。
敏捷开发过程就是一个持续和反复的过程,其中有一个出现频度很高的词 — 改善;这就是说敏捷过程就是一个逐渐变大,逐渐成长的过程。通过不停的反复,实践,改善,最终完善产品自身和团队本体。
所以说项目开始时并不需要考虑所有的状况,指定终极的目标,以及理想的架构,Just Do it!仅此而已。
过程中发生问题怎么办?没关系,在迭代结束,回顾改善中发现问题,并在下个迭代中改善它既可。也即是说,「问题发生」本身不是问题,只要有应对问题的过程环节就OK。
正如前面讲到的,敏捷开发是一种思想,他并没有给你划定条条框框,一切随实际情况而改变。
但是,我们往往不由自主的陷入体质和过程的怪圈 — 既然作为一个体系,一个过程管理架构,那么我们就要尽量维系它,遵循其起初制定,或其他地方吸收来的规则。而不管该架构形式是否适合自身的团队,一律照搬照抄。一旦出了问题,往往有「敏捷过程就是这样」,「架构没有问题」,「我们哪里用的不对而已」等等想法。不从组织过程架构中找问题是危险的。
组织过程架构是需要敏捷团队去建立和完善的,不管是从其他地方得到的经验,还是自己制定的规则,该体系都有可能在过程中出现不符合实际情况的问题。在迭代改善中发现它,在计划中完善它,在过程中改善它,是我们需要做的;而不要认为它是架构本身的问题而搁置它,不闻不问只会错上加错。
万事开头难,怎样才能减小开端的难度。从小的阶段目标做起,一边确认一边递进式的扩大作业范围。先是在小的规模下(个人或者是pair开发)体验敏捷开发的思想,然后逐渐扩大规模(团队或者组织内部)。随着规模的扩大,关系者的增加,影响范围也随之变大,需要考虑的事情变多,敏捷的难度也会伴随增加。这时,也是验证敏捷开发者应对变化能力的时候。
持续集成是指用自动化的工具验证成果物,并且每天至少一次。持续集成的目的是为了早起发现系统(除过软件成果物本身,整个敏捷架构过程也是该系统一部分)的问题,从而频繁的整合,检查。
站会顾名思义就是站立着开会,一般会议持续10~15分钟,站久了累了就没有意义了。开站会的目的就是确认工作的进度状况,需要周知的事项,问题点等。基本上每天早晨实施的实例比较多。
站会是一种高效的沟通方式,作为工作开始的合意,问题点的共享等方面往往有意想不到的效果。
如果想更加紧密,频繁地整合敏捷系统,和早会对应,在下班前用5到10分开一下站立的“晚会”往往有更好的效果。这里,我们回顾每日工作的情况,检验本日的工作成果。通过“晚会”,我们可以:
如果有加班的情况,在“晚会”上周知团队的其他成员。如果有早退的情况,可以在早会的时候给大家通知,有必要的情况下,可以调整“晚会”的开始时间点。
]]>iPhone开发之深入浅出 (7) — ARC总结
通过前面几篇文章的介绍,我想大家应该对ARC有了一个比较完整的理解。最后,我们来对ARC做一个总结,并把一些未涉及到的细节部分再深入讨论一下。
不管ARC有没有效,该原则始终存在。
从代码上看,有ARC的代码和没有ARC的代码区别就在下面的几个关键字。
类似 NSObject* 的对象类型,或者 id 类型1,当ARC有效的时候,根据具体情况,这些关键字必须要使用2。
__strong是默认的修饰符。
__weak修饰了一个自动nil的weak引用。
__unsafe_unretained声明了一个不会自动nil的weak引用。当变量被释放,那么它就变成了一个野指针了。
__autoreleasing 用来修饰一个声明为 (id *) 的函数的参数,当函数返回值时被释放。
接下来,我们结合下面ARC的使用准则,来看看一些使用ARC后的技术细节。
为了比秒程序秒退的尴尬,ARC有效时,我们的代码必须遵循下面的准则。
建议使用Objective-C的class来管理数据格式,来代替C语言的struct。不能隐式转换 id 和 void *。
内存管理完全交给编译器去做,所以之前内存相关的函数(retain/release/retainCount/autorelease)不能出现在程序中。Apple的ARC文档中也有下面的说明。
ARC 有效后,不需要再次使用retain 和 release
如果我们在程序中使用这些函数,经得到类似下面的编译错误信息。
error: ARC forbids explicit message send of ’release’ [o release]; ^ ~~~~~~~
生成并持有一个Objective-C对象的时候,往往像下面一样使用NSObject的alloc接口函数。
id obj = [NSObject alloc];
实际上,如果我们看了GNUstep 中关于 alloc 的代码就会明白,实际他是使用 NSAllocateObject 来生成并持有对象实例的。换言之,ARC有效的时候,NSAllocateObject函数的调用也是禁止的。如果使用,也会遇到下面的编译错误。
error: ’NSAllocateObject’ is unavailable: not available in automatic reference counting mode
同样,对象释放时使用的 NSDeallocateObject 函数也不能使用。
NSZone 是什么?NSZone 是为了防止内存碎片而导入的一项措施。Zone 是内存管理的基本单元,系统中管理复数的Zone。系统根据对象的使用目的,尺寸,分配其所属的Zone区域。以提高对象的访问效率,避免不必要的内存碎片。但是,现在的运行时系统(用编译开关 __OBJC2__ 指定的情况下)是不支持Zone概念的。所以,不管ARC是否有效,都不能使用 NSZone。
不管是否使用ARC,当对象被释放的时候,对象的dealloc函数被调用(就像是C++中对象的析构函数)。在该函数中,需要做一些内存释放的动作。比如,当对象中使用了malloc分配的C语言内存空间,那么dealloc中就需要像下面一样处理内存的释放。
1 2 3 4 |
- (void) dealloc { free(buffer_); } |
又或者是注册的delegate对象,观察者对象需要被删除的时候,也是在dealloc函数中动作。
1 2 3 4 |
- (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } |
如果在ARC无效的时候,我们还要像下面一样,调用父类对象的dealloc函数。
1 2 3 4 |
- (void) dealloc { [super dealloc]; } |
但是当ARC有效的时候,[super dealloc];的调用已经被编译器自动执行,已经不需要我们明示调用了。如果你在代码中还这样写,难免遇到下面的错误。
error: ARC forbids explicit message send of ’dealloc’ [super dealloc]; ^ ~~~~~~~
在iPhone开发之深入浅出 (3) — ARC之前世今生中,我们知道如果是 alloc/new/copy/mutableCopy/init 开头的函数,需要将对象所有权返回给调用端。这条规则不管ARC是否有效都应该被遵守。只是 init 开头的函数比较特殊,他只在ARC下有要求,而且异常苛刻。
init 开始的函数只能返回id型,或者是该函数所属的类/父类的对象类型。基本上来说,init函数是针对alloc函数的返回值,做一些初始化处理,然后再将该对象返回。比如:
id obj = [[NSObject alloc] init];
再比如下面定义的函数就是不对的:
- (void) initThisObject;
需要是下面这样:
- (id) initWithObject:(id)obj;
另外,下面名为 initialize 的函数比较特殊,编译器将把它过滤掉,不按上面的规则处理。
在ARC之下,已经不能在代码中使用 NSAutoreleasePool,我们之前写 main.m 文件的时候,往往像下面这样写。
1 2 3 4 5 6 |
int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, nil); [pool release]; return retVal; } |
而当ARC有效后,我们需要用@autoreleasepool代替NSAutoreleasePool。
1 2 3 4 5 6 |
int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } |
当编译器看到 @autoreleasepool 定义的块后会自动生成 NSAutoreleasePool 对象,并将需要的对象放入 AutoReleasePool 中,当出方块的定义范围时,pool 中的对象将被释放。
当我们设置ARC有效,并在C语言的结构体中定义Objective-C的对象时,将出现类似下面的编译错误。
1 2 3 |
struct Data { NSMutableArray *array; }; |
error: ARC forbids Objective-C objs in structs or unions NSMutableArray *array; ^
由于 ARC 是将内存管理的细节委托给编译器来做,所以说编译器必须要管理对象的生命周期。而LLVM 3.0中不存在对单纯C语言构造体成员的内存管理方法。如果单纯是栈对象,利用进出栈原理,可以简单地维护对象的生命周期;而结构体是不行的,简单地理解,结构体没有析构函数,编译器自身不能自动释放其内部的 Objective-C 对象。
当我们必须在C语言的结构体中放入 Objective-C 对象的时候,可以使用 void* 转型,或者使用 __unsafe_unretained 关键字。比如下面:
1 2 3 |
struct Data { NSMutableArray __unsafe_unretained *array; }; |
这样一来,该内存信息不在编译器内存管理对象内,仅仅是使用而已,没有对象的持有权。当然,对象所有权的持有者需要明确的管理他与该结构体的交互,不要引起不必要的错误3。
ARC 有效的时候,由于编译器帮我们做了内存管理的工作,所以我们不需要太担心。但是当与 ARC 管理以外的对象类型交互的时候,就需要特殊的转型关键字,来决定所有权的归属问题。
主要的转型关键字是:
关键字 | 解释 |
---|---|
__bridge | 单纯的类型转换,没有进行所有权的转移 |
__bridge_retained | 类型转换是伴随所有权传递,转换前后变量都持有对象的所有权 |
__bridge_transfer | 类型转换伴随所有权转移,被转换变量将失去对象的所有权 |
当我们在 Core Foundation 对象类型与 Objective-C 对象类型之间切换的时候,需要把握下面的因素:
最近随着 iOS 5.1 的推出,Xcode也推出了4.3版本。在该版本下,ARC 有效时的属性(@property) 定义的时候,如果不明确指定所有权关键字,那么缺省的就是 strong。而在 Xcode4.2 中,即使 strong 也要显示指定。
在 Xcode4.2 的时候,针对下面的代码,
1 2 3 4 5 6 7 |
// ARC 无效 @property (nonatomic, retain) NSString *string; // ---> // ARC 有效 @property (nonatomic, strong) NSString *string; |
而在 Xcode 4.3 中,我们可以这么做,
1 2 3 4 5 6 7 |
// ARC 无效 @property (nonatomic, retain) NSString *string; // ---> // ARC 有效 @property (nonatomic) NSString *string; |
另外,Xcode 4.2开始,增加了旧代码向 ARC 代码自动转换的功能。有兴趣的朋友可以试试。位置是:
Edit->Refactor->Convert to Objective-C ARC…
我们已经知道ARC并不是GC(垃圾回收)了,那么,为什么iOS中不支持该机能呢?还特意搞出个ARC来。以下是我的分析:
1. 关于Objective-C对象的解释,可以参考iPhone开发入门(7)— 从C/C++语言到Objective-C语言。
2. 当然,如果你不写,编译器会用缺省的值代替。具体见iPhone开发之深入浅出 (3) — ARC之前世今生中的描述。
3. 关于这一点,可以参考iPhone开发之深入浅出 (1) — ARC是什么 一文,明白为什么 __unsafe_unretained 是危险的。
]]>iPhone开发之深入浅出 (7) — ARC总结
通过前面几篇文章的介绍,我想大家应该对ARC有了一个比较完整的理解。最后,我们来对ARC做一个总结,并把一些未涉及到的细节部分再深入讨论一下。
不管ARC有没有效,该原则始终存在。
从代码上看,有ARC的代码和没有ARC的代码区别就在下面的几个关键字。
类似 NSObject* 的对象类型,或者 id 类型1,当ARC有效的时候,根据具体情况,这些关键字必须要使用2。
__strong是默认的修饰符。
__weak修饰了一个自动nil的weak引用。
__unsafe_unretained声明了一个不会自动nil的weak引用。当变量被释放,那么它就变成了一个野指针了。
__autoreleasing 用来修饰一个声明为 (id *) 的函数的参数,当函数返回值时被释放。
接下来,我们结合下面ARC的使用准则,来看看一些使用ARC后的技术细节。
为了比秒程序秒退的尴尬,ARC有效时,我们的代码必须遵循下面的准则。
建议使用Objective-C的class来管理数据格式,来代替C语言的struct。不能隐式转换 id 和 void *。
内存管理完全交给编译器去做,所以之前内存相关的函数(retain/release/retainCount/autorelease)不能出现在程序中。Apple的ARC文档中也有下面的说明。
ARC 有效后,不需要再次使用retain 和 release
如果我们在程序中使用这些函数,经得到类似下面的编译错误信息。
error: ARC forbids explicit message send of ’release’ [o release]; ^ ~~~~~~~
生成并持有一个Objective-C对象的时候,往往像下面一样使用NSObject的alloc接口函数。
id obj = [NSObject alloc];
实际上,如果我们看了GNUstep 中关于 alloc 的代码就会明白,实际他是使用 NSAllocateObject 来生成并持有对象实例的。换言之,ARC有效的时候,NSAllocateObject函数的调用也是禁止的。如果使用,也会遇到下面的编译错误。
error: ’NSAllocateObject’ is unavailable: not available in automatic reference counting mode
同样,对象释放时使用的 NSDeallocateObject 函数也不能使用。
NSZone 是什么?NSZone 是为了防止内存碎片而导入的一项措施。Zone 是内存管理的基本单元,系统中管理复数的Zone。系统根据对象的使用目的,尺寸,分配其所属的Zone区域。以提高对象的访问效率,避免不必要的内存碎片。但是,现在的运行时系统(用编译开关 __OBJC2__ 指定的情况下)是不支持Zone概念的。所以,不管ARC是否有效,都不能使用 NSZone。
不管是否使用ARC,当对象被释放的时候,对象的dealloc函数被调用(就像是C++中对象的析构函数)。在该函数中,需要做一些内存释放的动作。比如,当对象中使用了malloc分配的C语言内存空间,那么dealloc中就需要像下面一样处理内存的释放。
1 2 3 4 |
- (void) dealloc { free(buffer_); } |
又或者是注册的delegate对象,观察者对象需要被删除的时候,也是在dealloc函数中动作。
1 2 3 4 |
- (void) dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } |
如果在ARC无效的时候,我们还要像下面一样,调用父类对象的dealloc函数。
1 2 3 4 |
- (void) dealloc { [super dealloc]; } |
但是当ARC有效的时候,[super dealloc];的调用已经被编译器自动执行,已经不需要我们明示调用了。如果你在代码中还这样写,难免遇到下面的错误。
error: ARC forbids explicit message send of ’dealloc’ [super dealloc]; ^ ~~~~~~~
在iPhone开发之深入浅出 (3) — ARC之前世今生中,我们知道如果是 alloc/new/copy/mutableCopy/init 开头的函数,需要将对象所有权返回给调用端。这条规则不管ARC是否有效都应该被遵守。只是 init 开头的函数比较特殊,他只在ARC下有要求,而且异常苛刻。
init 开始的函数只能返回id型,或者是该函数所属的类/父类的对象类型。基本上来说,init函数是针对alloc函数的返回值,做一些初始化处理,然后再将该对象返回。比如:
id obj = [[NSObject alloc] init];
再比如下面定义的函数就是不对的:
- (void) initThisObject;
需要是下面这样:
- (id) initWithObject:(id)obj;
另外,下面名为 initialize 的函数比较特殊,编译器将把它过滤掉,不按上面的规则处理。
在ARC之下,已经不能在代码中使用 NSAutoreleasePool,我们之前写 main.m 文件的时候,往往像下面这样写。
1 2 3 4 5 6 |
int main(int argc, char *argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, nil); [pool release]; return retVal; } |
而当ARC有效后,我们需要用@autoreleasepool代替NSAutoreleasePool。
1 2 3 4 5 6 |
int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } |
当编译器看到 @autoreleasepool 定义的块后会自动生成 NSAutoreleasePool 对象,并将需要的对象放入 AutoReleasePool 中,当出方块的定义范围时,pool 中的对象将被释放。
当我们设置ARC有效,并在C语言的结构体中定义Objective-C的对象时,将出现类似下面的编译错误。
1 2 3 |
struct Data { NSMutableArray *array; }; |
error: ARC forbids Objective-C objs in structs or unions NSMutableArray *array; ^
由于 ARC 是将内存管理的细节委托给编译器来做,所以说编译器必须要管理对象的生命周期。而LLVM 3.0中不存在对单纯C语言构造体成员的内存管理方法。如果单纯是栈对象,利用进出栈原理,可以简单地维护对象的生命周期;而结构体是不行的,简单地理解,结构体没有析构函数,编译器自身不能自动释放其内部的 Objective-C 对象。
当我们必须在C语言的结构体中放入 Objective-C 对象的时候,可以使用 void* 转型,或者使用 __unsafe_unretained 关键字。比如下面:
1 2 3 |
struct Data { NSMutableArray __unsafe_unretained *array; }; |
这样一来,该内存信息不在编译器内存管理对象内,仅仅是使用而已,没有对象的持有权。当然,对象所有权的持有者需要明确的管理他与该结构体的交互,不要引起不必要的错误3。
ARC 有效的时候,由于编译器帮我们做了内存管理的工作,所以我们不需要太担心。但是当与 ARC 管理以外的对象类型交互的时候,就需要特殊的转型关键字,来决定所有权的归属问题。
主要的转型关键字是:
关键字 | 解释 |
---|---|
__bridge | 单纯的类型转换,没有进行所有权的转移 |
__bridge_retained | 类型转换是伴随所有权传递,转换前后变量都持有对象的所有权 |
__bridge_transfer | 类型转换伴随所有权转移,被转换变量将失去对象的所有权 |
当我们在 Core Foundation 对象类型与 Objective-C 对象类型之间切换的时候,需要把握下面的因素:
最近随着 iOS 5.1 的推出,Xcode也推出了4.3版本。在该版本下,ARC 有效时的属性(@property) 定义的时候,如果不明确指定所有权关键字,那么缺省的就是 strong。而在 Xcode4.2 中,即使 strong 也要显示指定。
在 Xcode4.2 的时候,针对下面的代码,
1 2 3 4 5 6 7 |
// ARC 无效 @property (nonatomic, retain) NSString *string; // ---> // ARC 有效 @property (nonatomic, strong) NSString *string; |
而在 Xcode 4.3 中,我们可以这么做,
1 2 3 4 5 6 7 |
// ARC 无效 @property (nonatomic, retain) NSString *string; // ---> // ARC 有效 @property (nonatomic) NSString *string; |
另外,Xcode 4.2开始,增加了旧代码向 ARC 代码自动转换的功能。有兴趣的朋友可以试试。位置是:
Edit->Refactor->Convert to Objective-C ARC…
我们已经知道ARC并不是GC(垃圾回收)了,那么,为什么iOS中不支持该机能呢?还特意搞出个ARC来。以下是我的分析:
1. 关于Objective-C对象的解释,可以参考iPhone开发入门(7)— 从C/C++语言到Objective-C语言。
2. 当然,如果你不写,编译器会用缺省的值代替。具体见iPhone开发之深入浅出 (3) — ARC之前世今生中的描述。
3. 关于这一点,可以参考iPhone开发之深入浅出 (1) — ARC是什么 一文,明白为什么 __unsafe_unretained 是危险的。
]]>iPhone开发之深入浅出 (6) — ARC之对象转型
自 Xcode4.2 开始导入ARC机制后,为了支持对象间的转型,Apple又增加了许多转型用的关键字。这一讲我们就来了解其用法,以及产生的理由。
我们先来看一下ARC无效的时候,我们写id类型转void*类型的写法:
1 2 |
id obj = [[NSObject alloc] init]; void *p = obj; |
反过来,当把void*对象变回id类型时,只是简单地如下来写,
1 2 |
id obj = p; [obj release]; |
但是上面的代码在ARC有效时,就有了下面的错误:
error: implicit conversion of an Objective-C pointer to ’void *’ is disallowed with ARC void *p = obj; ^ error: implicit conversion of a non-Objective-C pointer type ’void *’ to ’id’ is disallowed with ARC id o = p; ^
为了解决这一问题,我们使用 __bridge 关键字来实现id类型与void*类型的相互转换。看下面的例子。
1 2 3 4 5 |
id obj = [[NSObject alloc] init]; void *p = (__bridge void *)obj; id o = (__bridge id)p; |
将Objective-C的对象类型用 __bridge 转换为 void* 类型和使用 __unsafe_unretained 关键字修饰的变量是一样的。被代入对象的所有者需要明确对象生命周期的管理,不要出现异常访问的问题。
除过 __bridge 以外,还有两个 __bridge 相关的类型转换关键字:
接下来,我们将看看这两个关键字的区别。
先来看使用 __bridge_retained 关键字的例子程序:
1 2 3 |
id obj = [[NSObject alloc] init]; void *p = (__bridge_retained void *)obj; |
从名字上我们应该能理解其意义:类型被转换时,其对象的所有权也将被变换后变量所持有。如果不是ARC代码,类似下面的实现:
1 2 3 4 |
id obj = [[NSObject alloc] init]; void *p = obj; [(id)p retain]; |
可以用一个实际的例子验证,对象所有权是否被持有。
1 2 3 4 5 6 7 8 |
void *p = 0; { id obj = [[NSObject alloc] init]; p = (__bridge_retained void *)obj; } NSLog(@"class=%@", [(__bridge id)p class]); |
出了大括号的范围后,p 仍然指向一个有效的实体。说明他拥有该对象的所有权,该对象没有因为出其定义范围而被销毁。
相反,当想把本来拥有对象所有权的变量,在类型转换后,让其释放原先所有权的时候,需要使用 __bridge_transfer 关键字。文字有点绕口,我们还是来看一段代码吧。
如果ARC无效的时候,我们可能需要写下面的代码。
1 2 3 4 |
// p 变量原先持有对象的所有权 id obj = (id)p; [obj retain]; [(id)p release]; |
那么ARC有效后,我们可以用下面的代码来替换:
1 2 |
// p 变量原先持有对象的所有权 id obj = (__bridge_transfer id)p; |
可以看出来,__bridge_retained 是编译器替我们做了 retain 操作,而 __bridge_transfer 是替我们做了 release1。
在iOS世界,主要有两种对象:Objective-C 对象和 Core Foundation 对象0。Core Foundation 对象主要是有C语言实现的 Core Foundation Framework 的对象,其中也有对象引用计数的概念,只是不是 Cocoa Framework::Foundation Framework 的 retain/release,而是自身的 CFRetain/CFRelease 接口。
这两种对象间可以互相转换和操作,不使用ARC的时候,单纯的用C原因的类型转换,不需要消耗CPU的资源,所以叫做 Toll-Free bridged。比如 NSArray和CFArrayRef, NSString和CFStringRef,他们虽然属于不同的 Framework,但是具有相同的对象结构,所以可以用标准C的类型转换。
比如不使用ARC时,我们用下面的代码:
1 2 |
NSString *string = [NSString stringWithFormat:...]; CFStringRef cfString = (CFStringRef)string; |
同样,Core Foundation类型向Objective-C类型转换时,也是简单地用标准C的类型转换即可。
但是在ARC有效的情况下,将出现类似下面的编译错误:
Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef’ (aka ‘const struct __CFString *’) requires a bridged cast Use __bridge to convert directly (no change in ownership) Use __bridge_retained to make an ARC object available as a +1 ‘CFStringRef’ (aka ‘const struct __CFString *’)
错误中已经提示了我们需要怎样做:用 __bridge 或者 __bridge_retained 来转型,其差别就是变更对象的所有权。
正因为Objective-C是ARC管理的对象,而Core Foundation不是ARC管理的对象,所以才要特意这样转换,这与id类型向void*转换是一个概念。也就是说,当这两种类型(有ARC管理,没有ARC管理)在转换时,需要告诉编译器怎样处理对象的所有权。
上面的例子,使用 __bridge/__bridge_retained 后的代码如下:
1 2 |
NSString *string = [NSString stringWithFormat:...]; CFStringRef cfString = (__bridge CFStringRef)string; |
只是单纯地执行了类型转换,没有进行所有权的转移,也就是说,当string对象被释放的时候,cfString也不能被使用了。
1 2 3 4 |
NSString *string = [NSString stringWithFormat:...]; CFStringRef cfString = (__bridge_retained CFStringRef)string; ... CFRelease(cfString); // 由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release |
使用 __bridge_retained 可以通过转换目标处(cfString)的 retain 处理,来使所有权转移。即使 string 变量被释放,cfString 还是可以使用具体的对象。只是有一点,由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release。
实际上,Core Foundation 内部,为了实现Core Foundation对象类型与Objective-C对象类型的相互转换,提供了下面的函数。
1 2 3 4 5 6 7 |
CFTypeRef CFBridgingRetain(id X) { return (__bridge_retained CFTypeRef)X; } id CFBridgingRelease(CFTypeRef X) { return (__bridge_transfer id)X; } |
所以,可以用 CFBridgingRetain 替代 __bridge_retained 关键字:
1 2 3 4 |
NSString *string = [NSString stringWithFormat:...]; CFStringRef cfString = CFBridgingRetain(string); ... CFRelease(cfString); // 由于Core Foundation不在ARC管理范围内,所以需要主动release。 |
所有权被转移的同时,被转换变量将失去对象的所有权。当Core Foundation对象类型向Objective-C对象类型转换的时候,会经常用到 __bridge_transfer 关键字。
1 2 3 4 |
CFStringRef cfString = CFStringCreate...(); NSString *string = (__bridge_transfer NSString *)cfString; // CFRelease(cfString); 因为已经用 __bridge_transfer 转移了对象的所有权,所以不需要调用 release |
同样,我们可以使用 CFBridgingRelease() 来代替 __bridge_transfer 关键字。
1 2 |
CFStringRef cfString = CFStringCreate...(); NSString *string = CFBridgingRelease(cfString); |
由上面的学习我们了解到 ARC 中类型转换的用法,那么我们实际使用中按照怎样的原则或者方法来区分使用呢,下面我总结了几点关键要素。
1. 声明 id obj 的时候,其实是缺省的申明了一个 __strong 修饰的变量,所以编译器自动地加入了 retain 的处理,所以说 __bridge_transfer 关键字只为我们做了 release 处理。
]]>iPhone开发之深入浅出 (6) — ARC之对象转型
自 Xcode4.2 开始导入ARC机制后,为了支持对象间的转型,Apple又增加了许多转型用的关键字。这一讲我们就来了解其用法,以及产生的理由。
我们先来看一下ARC无效的时候,我们写id类型转void*类型的写法:
1 2 |
id obj = [[NSObject alloc] init]; void *p = obj; |
反过来,当把void*对象变回id类型时,只是简单地如下来写,
1 2 |
id obj = p; [obj release]; |
但是上面的代码在ARC有效时,就有了下面的错误:
error: implicit conversion of an Objective-C pointer to ’void *’ is disallowed with ARC void *p = obj; ^ error: implicit conversion of a non-Objective-C pointer type ’void *’ to ’id’ is disallowed with ARC id o = p; ^
为了解决这一问题,我们使用 __bridge 关键字来实现id类型与void*类型的相互转换。看下面的例子。
1 2 3 4 5 |
id obj = [[NSObject alloc] init]; void *p = (__bridge void *)obj; id o = (__bridge id)p; |
将Objective-C的对象类型用 __bridge 转换为 void* 类型和使用 __unsafe_unretained 关键字修饰的变量是一样的。被代入对象的所有者需要明确对象生命周期的管理,不要出现异常访问的问题。
除过 __bridge 以外,还有两个 __bridge 相关的类型转换关键字:
接下来,我们将看看这两个关键字的区别。
先来看使用 __bridge_retained 关键字的例子程序:
1 2 3 |
id obj = [[NSObject alloc] init]; void *p = (__bridge_retained void *)obj; |
从名字上我们应该能理解其意义:类型被转换时,其对象的所有权也将被变换后变量所持有。如果不是ARC代码,类似下面的实现:
1 2 3 4 |
id obj = [[NSObject alloc] init]; void *p = obj; [(id)p retain]; |
可以用一个实际的例子验证,对象所有权是否被持有。
1 2 3 4 5 6 7 8 |
void *p = 0; { id obj = [[NSObject alloc] init]; p = (__bridge_retained void *)obj; } NSLog(@"class=%@", [(__bridge id)p class]); |
出了大括号的范围后,p 仍然指向一个有效的实体。说明他拥有该对象的所有权,该对象没有因为出其定义范围而被销毁。
相反,当想把本来拥有对象所有权的变量,在类型转换后,让其释放原先所有权的时候,需要使用 __bridge_transfer 关键字。文字有点绕口,我们还是来看一段代码吧。
如果ARC无效的时候,我们可能需要写下面的代码。
1 2 3 4 |
// p 变量原先持有对象的所有权 id obj = (id)p; [obj retain]; [(id)p release]; |
那么ARC有效后,我们可以用下面的代码来替换:
1 2 |
// p 变量原先持有对象的所有权 id obj = (__bridge_transfer id)p; |
可以看出来,__bridge_retained 是编译器替我们做了 retain 操作,而 __bridge_transfer 是替我们做了 release1。
在iOS世界,主要有两种对象:Objective-C 对象和 Core Foundation 对象0。Core Foundation 对象主要是有C语言实现的 Core Foundation Framework 的对象,其中也有对象引用计数的概念,只是不是 Cocoa Framework::Foundation Framework 的 retain/release,而是自身的 CFRetain/CFRelease 接口。
这两种对象间可以互相转换和操作,不使用ARC的时候,单纯的用C原因的类型转换,不需要消耗CPU的资源,所以叫做 Toll-Free bridged。比如 NSArray和CFArrayRef, NSString和CFStringRef,他们虽然属于不同的 Framework,但是具有相同的对象结构,所以可以用标准C的类型转换。
比如不使用ARC时,我们用下面的代码:
1 2 |
NSString *string = [NSString stringWithFormat:...]; CFStringRef cfString = (CFStringRef)string; |
同样,Core Foundation类型向Objective-C类型转换时,也是简单地用标准C的类型转换即可。
但是在ARC有效的情况下,将出现类似下面的编译错误:
Cast of Objective-C pointer type ‘NSString *’ to C pointer type ‘CFStringRef’ (aka ‘const struct __CFString *’) requires a bridged cast Use __bridge to convert directly (no change in ownership) Use __bridge_retained to make an ARC object available as a +1 ‘CFStringRef’ (aka ‘const struct __CFString *’)
错误中已经提示了我们需要怎样做:用 __bridge 或者 __bridge_retained 来转型,其差别就是变更对象的所有权。
正因为Objective-C是ARC管理的对象,而Core Foundation不是ARC管理的对象,所以才要特意这样转换,这与id类型向void*转换是一个概念。也就是说,当这两种类型(有ARC管理,没有ARC管理)在转换时,需要告诉编译器怎样处理对象的所有权。
上面的例子,使用 __bridge/__bridge_retained 后的代码如下:
1 2 |
NSString *string = [NSString stringWithFormat:...]; CFStringRef cfString = (__bridge CFStringRef)string; |
只是单纯地执行了类型转换,没有进行所有权的转移,也就是说,当string对象被释放的时候,cfString也不能被使用了。
1 2 3 4 |
NSString *string = [NSString stringWithFormat:...]; CFStringRef cfString = (__bridge_retained CFStringRef)string; ... CFRelease(cfString); // 由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release |
使用 __bridge_retained 可以通过转换目标处(cfString)的 retain 处理,来使所有权转移。即使 string 变量被释放,cfString 还是可以使用具体的对象。只是有一点,由于Core Foundation的对象不属于ARC的管理范畴,所以需要自己release。
实际上,Core Foundation 内部,为了实现Core Foundation对象类型与Objective-C对象类型的相互转换,提供了下面的函数。
1 2 3 4 5 6 7 |
CFTypeRef CFBridgingRetain(id X) { return (__bridge_retained CFTypeRef)X; } id CFBridgingRelease(CFTypeRef X) { return (__bridge_transfer id)X; } |
所以,可以用 CFBridgingRetain 替代 __bridge_retained 关键字:
1 2 3 4 |
NSString *string = [NSString stringWithFormat:...]; CFStringRef cfString = CFBridgingRetain(string); ... CFRelease(cfString); // 由于Core Foundation不在ARC管理范围内,所以需要主动release。 |
所有权被转移的同时,被转换变量将失去对象的所有权。当Core Foundation对象类型向Objective-C对象类型转换的时候,会经常用到 __bridge_transfer 关键字。
1 2 3 4 |
CFStringRef cfString = CFStringCreate...(); NSString *string = (__bridge_transfer NSString *)cfString; // CFRelease(cfString); 因为已经用 __bridge_transfer 转移了对象的所有权,所以不需要调用 release |
同样,我们可以使用 CFBridgingRelease() 来代替 __bridge_transfer 关键字。
1 2 |
CFStringRef cfString = CFStringCreate...(); NSString *string = CFBridgingRelease(cfString); |
由上面的学习我们了解到 ARC 中类型转换的用法,那么我们实际使用中按照怎样的原则或者方法来区分使用呢,下面我总结了几点关键要素。
1. 声明 id obj 的时候,其实是缺省的申明了一个 __strong 修饰的变量,所以编译器自动地加入了 retain 的处理,所以说 __bridge_transfer 关键字只为我们做了 release 处理。
]]>iPhone开发之深入浅出 (5) — ARC之Outlet与弱引用
当我们使用 Interface Builder 生成Outlet对象的时候,一般都是作为 subview 来使用的。比如 UIViewController 的view。所以说Outlet的持有者就是superview对象,即有“父子”关系。由上一回 iPhone开发之深入浅出 (4) — ARC之循环参照 我们知道,当对象间有“父子”关系时,需要使用弱参照,以避免“循环参照”。
ViewController 本身是不会作为Outlet的所有者的,所以使用weak property声明。
Outlet都使用weak property声明的时候,还有一个好处,就是简化viewDidUnload的处理。
iOS在系统内存不足的时候,UIViewController会将没有表示的所有view做unload处理,即调用viewDidUnload接口。
所以,如果是强参照的情况下,需要释放所有权,
1 |
@property (nonatomic, strong) IBOutlet UILabel *label; |
1 2 3 4 |
(void)viewDidUnload { self.label = nil; // 取消强参照,释放所有权 [super viewDidUnload]; } |
如果没有 self.label = nil 的处理,那么 UIViewController 将不会释放 label 的所有权;结果,系统是调用了unload,但是subview对象始终留在内存中。随着界面上控件的增多,内存泄露会越来越大。
如果使用的是weak property声明的话,会是怎样的呢?
1 |
@property (nonatomic, weak) IBOutlet UILabel *label; |
这时,系统在unload时,由于label没有被强参照,更加ARC的规则,这时,label的对象即被释放。并在释放的同时,变量自动指向nil。
1 2 3 4 |
- (void)viewDidUnload { // 这里什么也不用管 [super viewDidUnload]; } |
其实,如果我们的viewDidUnload只是用来释放Outlet用的话,那么该函数也可以不被重载的。
由上我们也可以看到,并不是所有的Outlet都用弱参照来声明都是正确的;当使用Interface Builder生成的第一层的view或 者windows被作为Outlet来使用的话,那么不是不能声明为弱参照property的。(比如,Storyboard的各个scene)
理由很简单,没有被任何人强参照的对象,生成之后就会立刻被释放。
综上,当我们使用Outlet的时候,注意不同的情况来使用strong或者是weak。
]]>iPhone开发之深入浅出 (5) — ARC之Outlet与弱引用
当我们使用 Interface Builder 生成Outlet对象的时候,一般都是作为 subview 来使用的。比如 UIViewController 的view。所以说Outlet的持有者就是superview对象,即有“父子”关系。由上一回 iPhone开发之深入浅出 (4) — ARC之循环参照 我们知道,当对象间有“父子”关系时,需要使用弱参照,以避免“循环参照”。
ViewController 本身是不会作为Outlet的所有者的,所以使用weak property声明。
Outlet都使用weak property声明的时候,还有一个好处,就是简化viewDidUnload的处理。
iOS在系统内存不足的时候,UIViewController会将没有表示的所有view做unload处理,即调用viewDidUnload接口。
所以,如果是强参照的情况下,需要释放所有权,
1 |
@property (nonatomic, strong) IBOutlet UILabel *label; |
1 2 3 4 |
(void)viewDidUnload { self.label = nil; // 取消强参照,释放所有权 [super viewDidUnload]; } |
如果没有 self.label = nil 的处理,那么 UIViewController 将不会释放 label 的所有权;结果,系统是调用了unload,但是subview对象始终留在内存中。随着界面上控件的增多,内存泄露会越来越大。
如果使用的是weak property声明的话,会是怎样的呢?
1 |
@property (nonatomic, weak) IBOutlet UILabel *label; |
这时,系统在unload时,由于label没有被强参照,更加ARC的规则,这时,label的对象即被释放。并在释放的同时,变量自动指向nil。
1 2 3 4 |
- (void)viewDidUnload { // 这里什么也不用管 [super viewDidUnload]; } |
其实,如果我们的viewDidUnload只是用来释放Outlet用的话,那么该函数也可以不被重载的。
由上我们也可以看到,并不是所有的Outlet都用弱参照来声明都是正确的;当使用Interface Builder生成的第一层的view或
者windows被作为Outlet来使用的话,那么不是不能声明为弱参照property的。(比如,Storyboard的各个scene)
理由很简单,没有被任何人强参照的对象,生成之后就会立刻被释放。
综上,当我们使用Outlet的时候,注意不同的情况来使用strong或者是weak。
]]>iPhone开发之深入浅出 (4) — ARC之循环参照
当我们使用强参照(Strong reference)时,往往需要留意 循环参照 的问题。循环参照指的是两个对象被互相强参照,以至于任一对象都不能释放。
一般情况下,当对象之间有“父子关系”时,强参照的情况发生的比较多。比如通讯薄对象AddrBook和每个通讯录Entry的关系如下。
这种情况下,由于Entry对象被AddrBook强参照,所以不能释放。另一方面,如果Entry被释放了,AddrBook对象的强参照也就没有了,其对象也应被释放。
像上面的例子,当多个对象间有“父子关系”时,需要在一侧用“弱参照”来解决循环参照问题。一般情况下,“父亲”作为“孩子”的拥有者,对“孩子”是强参照,而“孩子”对父亲是弱参照。
如图所示,当强参照AddrBook对象的变量被释放的时候,AddrBook对象将被自动释放,同时将失去Entry成员对象的强参照。另外,当AddrBook对象被释放的时候,Entry对象中的AddrBook变量也将由Zeroing机制,自动带入nil。我们不需要担心释放对象的再访问问题。
下面,我们将看看有几种情况下,需要注意循环参照问题。
iOS程序中经常用到delegate模式,比如ViewController中,用ModalView打开/关闭DetailViewController时,需要delegate的设定。
这里,ViewController对象中强参照detailViewController,如果DetailViewController的delegate不是弱参照ViewController话,将引起循环参照。
另外,当类中使用weak @property声明的delegate变量时,如果参照对象被释放,该变量将被自动设为nil,不需要程序代码设置。
Blocks是iOS 4开始导入的,可以理解为python或者lisp中的Lambda,C++11也已导入了该概念;类似概念ruby/smalltalk/JSP语言中也有定义。具体讲解见以后的文章,本节我们主要看看在Block中的循环参照问题。
比如,block对象用copy的属性定义时候,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
typedef void(^MyBlock)(void); @interface MyObject : NSObject @property (nonatomic, copy) MyBlock block; @property (nonatomic, strong) NSString *str; - (void)performBlock; @end @implementation MyObject @synthesize block, str; - (void)performBlock { if (self.block) { self.block(); } } @end |
调用端如下:
1 2 3 4 5 6 7 |
MyObject *object = [[MyObject alloc] init]; object.str = @"hoge"; object.block = ^{ NSLog(@"block: str=%@", object.str); }; [object performBlock]; |
我们看到,Block的构文中参照了object,同样object也强参照block。
为了解决该问题,我们可以有下面两种选择。
使用__block关键字,让对象有读写权限,如果Block内的处理完毕就释放object。
1 2 3 4 5 6 7 8 |
__block MyObject *object = [[MyObject alloc] init]; object.str = @"hoge"; object.block = ^{ NSLog(@"block: str=%@", object.str); object = nil; }; [object performBlock]; |
该关键字的意思就是让block取消对object的强参照,以避免循环参照。但是,有一个问题就是,object的释放动作是在Block内部执行,如果Block没有被执行的话,循环参照一直存在。比如上面的代码,如果第8行 [object performBlock]; 没有执行的话,那么一直还是循环参照状态。
另一种方案就是让Block的参照变为弱参照。
1 2 3 4 5 6 7 8 |
MyObject *object = [[MyObject alloc] init]; object.str = @"hoge"; __weak MyObject *weakObject = object; object.block = ^{ NSLog(@"block: str=%@", weakObject.str); }; [object performBlock]; |
考虑到异步通信时Blocks的使用情况,weak变量weakObject有可能随时变为nil,所以类似于下面先变为strong变量,并检查是否为nil的处理方式应该更安全。
1 2 3 4 5 6 7 8 9 10 11 |
MyObject *object = [[MyObject alloc] init]; object.str = @"hoge"; __weak MyObject *weakObject = object; object.block = ^{ MyObject strongObject = weakObject; if (strongObject) { NSLog(@"block: str=%@", strongObject.str); } }; [object performBlock]; |
总上,当我们使用Blocks时,也需要考虑Block中变量和实例的关系,不要引起不必要的循环参照问题。
]]>iPhone开发之深入浅出 (4) — ARC之循环参照
当我们使用强参照(Strong reference)时,往往需要留意 循环参照 的问题。循环参照指的是两个对象被互相强参照,以至于任一对象都不能释放。
一般情况下,当对象之间有“父子关系”时,强参照的情况发生的比较多。比如通讯薄对象AddrBook和每个通讯录Entry的关系如下。
这种情况下,由于Entry对象被AddrBook强参照,所以不能释放。另一方面,如果Entry被释放了,AddrBook对象的强参照也就没有了,其对象也应被释放。
像上面的例子,当多个对象间有“父子关系”时,需要在一侧用“弱参照”来解决循环参照问题。一般情况下,“父亲”作为“孩子”的拥有者,对“孩子”是强参照,而“孩子”对父亲是弱参照。
如图所示,当强参照AddrBook对象的变量被释放的时候,AddrBook对象将被自动释放,同时将失去Entry成员对象的强参照。另外,当AddrBook对象被释放的时候,Entry对象中的AddrBook变量也将由Zeroing机制,自动带入nil。我们不需要担心释放对象的再访问问题。
下面,我们将看看有几种情况下,需要注意循环参照问题。
iOS程序中经常用到delegate模式,比如ViewController中,用ModalView打开/关闭DetailViewController时,需要delegate的设定。
这里,ViewController对象中强参照detailViewController,如果DetailViewController的delegate不是弱参照ViewController话,将引起循环参照。
另外,当类中使用weak @property声明的delegate变量时,如果参照对象被释放,该变量将被自动设为nil,不需要程序代码设置。
Blocks是iOS 4开始导入的,可以理解为python或者lisp中的Lambda,C++11也已导入了该概念;类似概念ruby/smalltalk/JSP语言中也有定义。具体讲解见以后的文章,本节我们主要看看在Block中的循环参照问题。
比如,block对象用copy的属性定义时候,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
typedef void(^MyBlock)(void); @interface MyObject : NSObject @property (nonatomic, copy) MyBlock block; @property (nonatomic, strong) NSString *str; - (void)performBlock; @end @implementation MyObject @synthesize block, str; - (void)performBlock { if (self.block) { self.block(); } } @end |
调用端如下:
1 2 3 4 5 6 7 |
MyObject *object = [[MyObject alloc] init]; object.str = @"hoge"; object.block = ^{ NSLog(@"block: str=%@", object.str); }; [object performBlock]; |
我们看到,Block的构文中参照了object,同样object也强参照block。
为了解决该问题,我们可以有下面两种选择。
使用__block关键字,让对象有读写权限,如果Block内的处理完毕就释放object。
1 2 3 4 5 6 7 8 |
__block MyObject *object = [[MyObject alloc] init]; object.str = @"hoge"; object.block = ^{ NSLog(@"block: str=%@", object.str); object = nil; }; [object performBlock]; |
该关键字的意思就是让block取消对object的强参照,以避免循环参照。但是,有一个问题就是,object的释放动作是在Block内部执行,如果Block没有被执行的话,循环参照一直存在。比如上面的代码,如果第8行 [object performBlock]; 没有执行的话,那么一直还是循环参照状态。
另一种方案就是让Block的参照变为弱参照。
1 2 3 4 5 6 7 8 |
MyObject *object = [[MyObject alloc] init]; object.str = @"hoge"; __weak MyObject *weakObject = object; object.block = ^{ NSLog(@"block: str=%@", weakObject.str); }; [object performBlock]; |
考虑到异步通信时Blocks的使用情况,weak变量weakObject有可能随时变为nil,所以类似于下面先变为strong变量,并检查是否为nil的处理方式应该更安全。
1 2 3 4 5 6 7 8 9 10 11 |
MyObject *object = [[MyObject alloc] init]; object.str = @"hoge"; __weak MyObject *weakObject = object; object.block = ^{ MyObject strongObject = weakObject; if (strongObject) { NSLog(@"block: str=%@", strongObject.str); } }; [object performBlock]; |
总上,当我们使用Blocks时,也需要考虑Block中变量和实例的关系,不要引起不必要的循环参照问题。
]]>iPhone开发之深入浅出 (3) — ARC之前世今生
前两节我们对 ARC(Automatic Reference Counting) 有了一个基本的理解,但是 ARC 是怎么产生的,为什么苹果要在其最新的 iOS/Mac OS X 上导入该框架? 如果不理解其背后的基本原理,只是死记硬背那些规则/方法,是毫无意义的。就像我们从小接受的填鸭式教育,基本上到后来都还给老师了。
本节,我们先来看看 ARC 产生之前的 Objective-C 内存管理世界,然后再来看看导入 ARC 后,新的 LLVM 编译器在背后为我们做了什么。
和许多面向对象语言一样,Objective-C 中内存管理的方式其实就是指 引用计数 (Reference Counting)的使用准则。如下图所示,对象生成的时候必定被某个持有者拿着,如果有多个持有者的话,其引用计数就会递增;相反失去一个持有者那么引用计数即会递减,直到失去所有的持有者,才真正地从内测中释放自己。
结合 Objective-C 语言中的方法,我们来看看基本的内存管理。
方法 | 动作 |
---|---|
alloc/new/copy/mutableCopy | 生成对象并拥有所有权 |
retain | 拥有对象所有权 |
release | 释放对象所有权 |
dealloc | 释放对象资源 |
实际上这些函数并不能说是 Objective-C 语言所特有的,而是 OS X / iOS 系统库中包含的基类函数;具体说就是 Cocoa Framework::Foundation::NSObject 基类的成员函数。
Objective-C 语言内部严格遵守上面表格中的定义;首先是 alloc/new/copy/mutableCopy 这几个函数,并且是alloc/new/copy/mutableCopy 开头的函数,比如:allpcMyObject/newTheObject/copyThis/mutableCopyTheObject 等都必须遵循这个原则。
反而言之,如果不是 alloc/new/copy/mutableCopy 开头的函数,而且要返回对象的话,那么调用端只是生成对象,而不是其持有者。
1 2 3 4 5 6 7 8 9 10 11 |
-(id)allocObject { /* * 生成对象并拥有所有权 */ id obj = [[NSObject alloc] init]; /* * 自己一直是持有对象状态 */ return obj; } |
如上面的例子,alloc 生成的对象,其所有权会传递给函数的调用端;即满足了 alloc 开头函数的命名规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-(id)object { id obj = [[NSObject alloc] init]; /* * 自己一直是持有对象状态 */ [obj autorelease]; /* * 对象还存在,只是并不持有它的所有权 */ return obj; } |
这里我们用到了 autorelease 函数。它的作用既是将对象放入 NSAutoreleasePool 中,由其来维护其生命周期。换句话说对象的持有者是 NSAutoreleasePool;上面的例子中,object 返回后,调用者将不持有其所有权。(除非再调用 retain。)
用 autorelease 的一个理由既是让程序员来控制对象的存活周期,而不像 C/C++ 等语言中,出栈后,栈中数据都被自动废弃,或者用 { } 框住的自动变量,当出了范围就看不到了。在 Objective-C 中,只有当 [pool drain] 被调用的时候,才清空 pool 中所有登录的对象实体,在这之前,你可以像往常一样正常使用对象。
当然可以想象得到的,如果一个程序只有一个 NSAutoreleasePool,并在 main 中声明,程序结束时才 [pool drain]/[pool release] 的话,那么所有 autorelease 的对象都将塞满这个 pool,会耗掉系统大部分内存。所以,使用 NSAutoreleasePool 的时候也尽量建议局部使用,比如下面的循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
for (i=0; i < 100; i++) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; // 下面的函数由于不属于 alloc/new/copy/mutableCopy 范畴的函数,所以都使用了 autorelease NSMutableArray* array = [NSMutableArray array]; NSString *str = [NSString stringWithFormat:@"TestCode"]; /* * 其他使用autorelease定义的对象 */ Test *test = [[[Test alloc] init] autorelease]; // 通过下面的函数,可以随时监控pool中的对象 // iOS以外的运行库的情况下,也可以使用 _objc_autoreleasePoolPrint() 私有函数,只是需要下面的声明 // extern void _objc_autoreleasePoolPrint(); [NSAutoreleasePool showPools]; // 这里把所有pool中的对象都释放掉 [pool release]; } |
当然 NSAutoreleasePool 也可以嵌套,基本上都依存大括号规则。
基于以上原则,在 ARC 诞生之前,我们往往用下面准则来写代码。
一般情况下,我们这样生成对象并使用
1 2 3 |
MyController* controller = [[MyController alloc] init]; // ...... [controller release]; |
如果在 [controller release] 之前函数return了怎么样,内存泄露了不是;为了防患于未然,一般像下面一样 生成对象时,使用autorelease。这样一来,该对象就被自动加入到最近的那个 pool 中。
1 |
MyController* controller = [[[MyController alloc] init] autorelease]; |
对象代入的时候,如果之前不将变量所持有的对象释放,那么很可能引起内存泄露。比如下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 |
{ _member = [[TempValue alloc] init]; } - (void)setValue:(TempValue *)value { _member = value; // 这时,之前持有的对象因为没有 release 而引起内存泄露 // 当然,先 [_member release] 后再代入也是可以的, // 但是当与「对象在函数中返回时」的问题一同考虑时, // 如果没有 return [[object retain] autorelease] 的保证,这里即使 [_member release]也是百搭 // 详细的解释见下 } |
鉴于以上原因,我们将原先的对象先autorelease后再将新对象retain代入。
1 2 3 4 5 6 7 8 9 10 |
{ _member = [[TempValue alloc] init]; // 这里,即使使用【生成对象时,使用autorelease】的准则,也没有关系 // 使用autorelease一次就将制定对象放入pool中,放几次[pool drain]的时候就释放几次 } - (void)setValue:(TempValue *)value { [_member autorelease]; _member = [value retain]; } |
该原则遵循 Failed Self 的原则,虽然从性能上看有所损耗但是保证了代码质量。
严格地说,是除 alloc/new/copy/mutableCopy 开头函数以外的函数中,有对象放回时,使用return [[object retain] autorelease]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
@implementation FooClass - (void)setObject:(MyObject *)object; { // 这里故意没有使用 autorelease,以便说明问题 [_object release]; _object = [object retain]; } - (id)object; { return _object; } - (void)dealloc; { [_object release]; [super dealloc]; } @end @implementation BarClass - (void)doStuff; { FooClass * foo = [[FooClass alloc] init]; // 创建第一个对象,引用计数 = 1 MyObject * firstObject = [[MyObject alloc] init]; // setObject中由于 [object retain] ,引用计数 = 2 [foo setObject:firstObject]; // 释放一次,引用计数 = 1;这之后对象有正确的所有权属性 [firstObject release]; // 通过非 alloc/new/copy/mutableCopy 开头函数得到对象 // anObject 指向第一个对象,但是并没有其所有权,对象引用计数 = 1 MyObject * anObject = [foo object]; [anObject testMethod]; // 创建第二个对象 MyObject * secondObject = [[MyObject alloc] init]; // setObject中由于 [_object release]; 第一个对象引用计数 = 0,内存被释放 [foo setObject:secondObject]; [secondObject release]; // 程序在这里崩溃了,因为 anObject 指向了一个空地址 [anObject testMethod]; } @end |
从结论我们来看看该问题的几种可行的解决方案;各种方案中没有列出的代码与原先代码一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@implementation BarClass - (void)doStuff; { FooClass * foo = [[FooClass alloc] init]; MyObject * firstObject = [[[MyObject alloc] init] autorelease]; [foo setObject:firstObject]; MyObject * anObject = [foo object]; [anObject testMethod]; MyObject * secondObject = [[[MyObject alloc] init] autorelease]; [foo setObject:secondObject]; [anObject testMethod]; } @end |
对象生成时,即被放入最近的 pool 中,不需要人为特殊的维护,对象的生命周期将被延续,出 {} 范围之时即对象释放之际。
1 2 3 4 5 6 7 8 9 10 11 |
- (void)setObject:(MyObject *)object; { [_object autorelease]; _object = [object retain]; } - (id)object; { // 遵循非 alloc/new/copy/mutableCopy 开头的函数,不赐予所有权原则 return _object; } |
同样的,对象被放入最近的 pool 中,第二次 setObject 后对象引用计数仍为1, pool 清空时才执行最后一次对象release,从而保证了代码的正确性。
1 2 3 4 5 6 7 8 9 10 11 |
- (void)setObject:(MyObject *)object; { [_object release]; _object = [object retain]; } - (id)object; { // 遵循非 alloc/new/copy/mutableCopy 开头的函数,不赐予所有权原则 return [[_object retain] autorelease]; } |
好不容易回到了本小节要说明的方法;可以看到这是从另一个角度解决了该问题:[foo object] 的时候保证引用计数是2,并将对象放入pool中维护。
总结上面上面3中方法,虽说是从不同角度入手解决了这个问题,但是基本原则不变,利用 NSAutoreleasePool 机制帮程序员维护代码,管理内存。
如果你觉得3种编码原则怎么搭配使用,在什么样的场合下选择比较麻烦,不要紧,都用就得了。我们牺牲的只是 NSAutoreleasePool 中的一些内存,一小许性能损失罢了,这总比我们的程序崩溃了强。
ARC 是什么我不需要再解释,若有不明白,可以看iPhone开发之深入浅出 (1) — ARC是什么。
并从编译器角度维护了该原则,比如如果不是 alloc/new/copy/mutableCopy 开头的函数,编译器会将生成的对象自动放入 autoReleasePool 中。如果是 __strong 修饰的变量,编译器会自动给其加上所有权。等等,详细,我们根据不同的关键字来看看编译器为我们具体做了什么。并从中总结出 ARC 的使用规则。
我们先来看看用 __strong 修饰的变量,以及缺省隐藏的 __strong 情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ /* * 生成对象并拥有所有权 */ id __strong obj = [[NSObject alloc] init]; /* * 自己一直是持有对象状态 */ } /* * 变量出生命周期时,失去全部所有者,对象内存空间被释放 */ |
这种情况毫无悬念,缺省使用 alloc/new/copy/mutableCopy 开头的函数也是这样的结果。并且在这里,编译器帮我们自动的调用了对象的 release 函数,不需要手工维护。再看看下面的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ /* * 生成对象但是并没有其所有权 */ id __strong obj = [NSMutableArray array]; /* * 由于变量声明是强引用,自己一直是持有对象状态 * 编译器根据函数名,再将该对象放入 autoreleasepool 中 */ } /* * 变量出生命周期时,失去全部所有者,对象内存空间被释放 */ |
由上,虽然不是用 alloc/new/copy/mutableCopy 开头的函数得到的对象,由于是强参照,我们任然成为对象的持有者。而这,正是编译器帮我们做到的。
具体做的是什么呢?其实就是【对象在函数中返回时,使用return [[object retain] autorelease]】所描述的;如果你反汇编一下ARC生成的代码,可以看到这时会自动调用名为 objc_retainAutoreleaseReturnValue 的函数,而其作用和 [[object retain] autorelease] 一致。编译器通过函数名分析,如果不是 alloc/new/copy/mutableCopy 开头的函数,自动加入了这段代码。
另外,缺省 __strong 修饰的变量,对象代入的时候也正确地保证对象所有者规则;代入新对象时,自动释放旧对象的参照,代入nil的时候,表示释放当前对象的强参照。
虽然大部分场合,大部分问题使用 __strong 来编码就足够了;但是为了解决循环参照的问题 __weak 关键字修饰【弱参照】变量就发挥了左右。关于循环参照的问题,准备在以后的博文中介绍;今天,主要看看编译器在背后怎么处理 __weak 变量的。
__weak 声明的变量其实是被放入一个weak表中,该表和引用计数的表格类似,是一个Hash表,都是以对象的内存地址做key,同时,针对一个对象地址的key,可以同时对应多个变量的地址。
另外,当使用 __weak 修饰的变量的时候,变量将放入 autoreleasepool 中,并且用几次放几次。比如下面的简单例子。
1 2 3 4 5 6 7 8 |
{ id __weak o = obj; NSLog(@"1 %@", o); NSLog(@"2 %@", o); NSLog(@"3 %@", o); NSLog(@"4 %@", o); NSLog(@"5 %@", o); } |
这里我们用了5次,那么pool中就被登录了5次;从效率上考虑这样当然不是很好,可以通过代入 __strong 修饰的强参照变量来避开这个问题。
1 2 3 4 5 6 7 8 9 |
{ id __weak o = obj; id temp = o; NSLog(@"1 %@", temp); NSLog(@"2 %@", temp); NSLog(@"3 %@", temp); NSLog(@"4 %@", temp); NSLog(@"5 %@", temp); } |
另外,还有通过重载 allowsWeakReference/retainWeakReference 函数来限制 __weak 声明变量使用回数的方法,毕竟不在本次讨论范畴之内,就此省略。
话说回来,为什么使用弱参照变量的时候,要将其放入 autoreleasepool 中呢?想想弱参照的定义就应该明白了 —- 如果在访问弱参照对象时,该对象被释放了怎么办,程序不就崩溃了嘛;所以为了解决该问题,又再一次用到了 pool。
虽然上面还没有降到该关键字,但是编译器在很多时候已经用到了 autoreleasepool。比如非 alloc/new/copy/mutableCopy 开头的函数返回一个对象的时候,又比如使用一个 __weak 声明的变量的时候。
实际上,写ARC代码的时候,明示 __autoreleasing 声明变量和明示 __strong 声明变量一样基本上没有,因为编译器已经为我们做了很多,很智能了(前提是我们要按ARC的规则写代码)。
还有一种编译器缺省使用 __autoreleasing 关键字声明变量的时候:对象指针类型。比如下面的对应关系。
1 2 |
id *obj == id __autoreleasing *obj NSObject **obj == NSObject * __autoreleasing *obj |
所以,下面两个函数的是等价的。
1 2 3 |
-(BOOL)performOperationWithError:(NSError **)error; -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error; |
像下面的函数调用,为什么是可行的呢?
1 2 |
NSError __strong *error = nil; BOOL result = [obj performOperationWithError:&error]; |
其实,编译器是这样解释这段代码的。
1 2 3 4 |
NSError __strong *error = nil; NSError __autoreleasing *tmp = error; BOOL result = [obj performOperationWithError:&tmp]; error = tmp; |
那么我们这样声明函数不就可以了吗?
1 |
-(BOOL)performOperationWithError:(NSError * __strong *)error; |
答案是肯定的,你可以这样做,编译是可以通过,但你违反了非 alloc/new/copy/mutableCopy 开头的函数,不返回对象持有权的原则。这里是没有问题了,但也许影响到其他地方NG。
(这里只列出本讲中涉及的内容,其他的内容以后总结)
关于函数命名,伴随ARC的导入,还有一系列函数的定义也被严格定义了,那就是以 init 开头的函数。init 函数作为alloc生成对象的初期化函数,需要按原样直接传递对象给调用段,所以下面的声明是OK的。
1 |
-(id)initWithObject:(id)obj; |
而下面的是NG的。
1 |
-(void)initWithObject; |
不过声明为 -(void) initialize; 是没有问题的。
]]>iPhone开发之深入浅出 (3) — ARC之前世今生
前两节我们对 ARC(Automatic Reference Counting) 有了一个基本的理解,但是 ARC 是怎么产生的,为什么苹果要在其最新的 iOS/Mac OS X 上导入该框架? 如果不理解其背后的基本原理,只是死记硬背那些规则/方法,是毫无意义的。就像我们从小接受的填鸭式教育,基本上到后来都还给老师了。
本节,我们先来看看 ARC 产生之前的 Objective-C 内存管理世界,然后再来看看导入 ARC 后,新的 LLVM 编译器在背后为我们做了什么。
和许多面向对象语言一样,Objective-C 中内存管理的方式其实就是指 引用计数 (Reference Counting)的使用准则。如下图所示,对象生成的时候必定被某个持有者拿着,如果有多个持有者的话,其引用计数就会递增;相反失去一个持有者那么引用计数即会递减,直到失去所有的持有者,才真正地从内测中释放自己。
结合 Objective-C 语言中的方法,我们来看看基本的内存管理。
方法 | 动作 |
---|---|
alloc/new/copy/mutableCopy | 生成对象并拥有所有权 |
retain | 拥有对象所有权 |
release | 释放对象所有权 |
dealloc | 释放对象资源 |
实际上这些函数并不能说是 Objective-C 语言所特有的,而是 OS X / iOS 系统库中包含的基类函数;具体说就是 Cocoa Framework::Foundation::NSObject 基类的成员函数。
Objective-C 语言内部严格遵守上面表格中的定义;首先是 alloc/new/copy/mutableCopy 这几个函数,并且是alloc/new/copy/mutableCopy 开头的函数,比如:allpcMyObject/newTheObject/copyThis/mutableCopyTheObject 等都必须遵循这个原则。
反而言之,如果不是 alloc/new/copy/mutableCopy 开头的函数,而且要返回对象的话,那么调用端只是生成对象,而不是其持有者。
1 2 3 4 5 6 7 8 9 10 11 |
-(id)allocObject { /* * 生成对象并拥有所有权 */ id obj = [[NSObject alloc] init]; /* * 自己一直是持有对象状态 */ return obj; } |
如上面的例子,alloc 生成的对象,其所有权会传递给函数的调用端;即满足了 alloc 开头函数的命名规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-(id)object { id obj = [[NSObject alloc] init]; /* * 自己一直是持有对象状态 */ [obj autorelease]; /* * 对象还存在,只是并不持有它的所有权 */ return obj; } |
这里我们用到了 autorelease 函数。它的作用既是将对象放入 NSAutoreleasePool 中,由其来维护其生命周期。换句话说对象的持有者是 NSAutoreleasePool;上面的例子中,object 返回后,调用者将不持有其所有权。(除非再调用 retain。)
用 autorelease 的一个理由既是让程序员来控制对象的存活周期,而不像 C/C++ 等语言中,出栈后,栈中数据都被自动废弃,或者用 { } 框住的自动变量,当出了范围就看不到了。在 Objective-C 中,只有当 [pool drain] 被调用的时候,才清空 pool 中所有登录的对象实体,在这之前,你可以像往常一样正常使用对象。
当然可以想象得到的,如果一个程序只有一个 NSAutoreleasePool,并在 main 中声明,程序结束时才 [pool drain]/[pool release] 的话,那么所有 autorelease 的对象都将塞满这个 pool,会耗掉系统大部分内存。所以,使用 NSAutoreleasePool 的时候也尽量建议局部使用,比如下面的循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
for (i=0; i < 100; i++) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; // 下面的函数由于不属于 alloc/new/copy/mutableCopy 范畴的函数,所以都使用了 autorelease NSMutableArray* array = [NSMutableArray array]; NSString *str = [NSString stringWithFormat:@"TestCode"]; /* * 其他使用autorelease定义的对象 */ Test *test = [[[Test alloc] init] autorelease]; // 通过下面的函数,可以随时监控pool中的对象 // iOS以外的运行库的情况下,也可以使用 _objc_autoreleasePoolPrint() 私有函数,只是需要下面的声明 // extern void _objc_autoreleasePoolPrint(); [NSAutoreleasePool showPools]; // 这里把所有pool中的对象都释放掉 [pool release]; } |
当然 NSAutoreleasePool 也可以嵌套,基本上都依存大括号规则。
基于以上原则,在 ARC 诞生之前,我们往往用下面准则来写代码。
一般情况下,我们这样生成对象并使用
1 2 3 |
MyController* controller = [[MyController alloc] init]; // ...... [controller release]; |
如果在 [controller release] 之前函数return了怎么样,内存泄露了不是;为了防患于未然,一般像下面一样 生成对象时,使用autorelease。这样一来,该对象就被自动加入到最近的那个 pool 中。
1 |
MyController* controller = [[[MyController alloc] init] autorelease]; |
对象代入的时候,如果之前不将变量所持有的对象释放,那么很可能引起内存泄露。比如下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 |
{ _member = [[TempValue alloc] init]; } - (void)setValue:(TempValue *)value { _member = value; // 这时,之前持有的对象因为没有 release 而引起内存泄露 // 当然,先 [_member release] 后再代入也是可以的, // 但是当与「对象在函数中返回时」的问题一同考虑时, // 如果没有 return [[object retain] autorelease] 的保证,这里即使 [_member release]也是百搭 // 详细的解释见下 } |
鉴于以上原因,我们将原先的对象先autorelease后再将新对象retain代入。
1 2 3 4 5 6 7 8 9 10 |
{ _member = [[TempValue alloc] init]; // 这里,即使使用【生成对象时,使用autorelease】的准则,也没有关系 // 使用autorelease一次就将制定对象放入pool中,放几次[pool drain]的时候就释放几次 } - (void)setValue:(TempValue *)value { [_member autorelease]; _member = [value retain]; } |
该原则遵循 Failed Self 的原则,虽然从性能上看有所损耗但是保证了代码质量。
严格地说,是除 alloc/new/copy/mutableCopy 开头函数以外的函数中,有对象放回时,使用return [[object retain] autorelease]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
@implementation FooClass - (void)setObject:(MyObject *)object; { // 这里故意没有使用 autorelease,以便说明问题 [_object release]; _object = [object retain]; } - (id)object; { return _object; } - (void)dealloc; { [_object release]; [super dealloc]; } @end @implementation BarClass - (void)doStuff; { FooClass * foo = [[FooClass alloc] init]; // 创建第一个对象,引用计数 = 1 MyObject * firstObject = [[MyObject alloc] init]; // setObject中由于 [object retain] ,引用计数 = 2 [foo setObject:firstObject]; // 释放一次,引用计数 = 1;这之后对象有正确的所有权属性 [firstObject release]; // 通过非 alloc/new/copy/mutableCopy 开头函数得到对象 // anObject 指向第一个对象,但是并没有其所有权,对象引用计数 = 1 MyObject * anObject = [foo object]; [anObject testMethod]; // 创建第二个对象 MyObject * secondObject = [[MyObject alloc] init]; // setObject中由于 [_object release]; 第一个对象引用计数 = 0,内存被释放 [foo setObject:secondObject]; [secondObject release]; // 程序在这里崩溃了,因为 anObject 指向了一个空地址 [anObject testMethod]; } @end |
从结论我们来看看该问题的几种可行的解决方案;各种方案中没有列出的代码与原先代码一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@implementation BarClass - (void)doStuff; { FooClass * foo = [[FooClass alloc] init]; MyObject * firstObject = [[[MyObject alloc] init] autorelease]; [foo setObject:firstObject]; MyObject * anObject = [foo object]; [anObject testMethod]; MyObject * secondObject = [[[MyObject alloc] init] autorelease]; [foo setObject:secondObject]; [anObject testMethod]; } @end |
对象生成时,即被放入最近的 pool 中,不需要人为特殊的维护,对象的生命周期将被延续,出 {} 范围之时即对象释放之际。
1 2 3 4 5 6 7 8 9 10 11 |
- (void)setObject:(MyObject *)object; { [_object autorelease]; _object = [object retain]; } - (id)object; { // 遵循非 alloc/new/copy/mutableCopy 开头的函数,不赐予所有权原则 return _object; } |
同样的,对象被放入最近的 pool 中,第二次 setObject 后对象引用计数仍为1, pool 清空时才执行最后一次对象release,从而保证了代码的正确性。
1 2 3 4 5 6 7 8 9 10 11 |
- (void)setObject:(MyObject *)object; { [_object release]; _object = [object retain]; } - (id)object; { // 遵循非 alloc/new/copy/mutableCopy 开头的函数,不赐予所有权原则 return [[_object retain] autorelease]; } |
好不容易回到了本小节要说明的方法;可以看到这是从另一个角度解决了该问题:[foo object] 的时候保证引用计数是2,并将对象放入pool中维护。
总结上面上面3中方法,虽说是从不同角度入手解决了这个问题,但是基本原则不变,利用 NSAutoreleasePool 机制帮程序员维护代码,管理内存。
如果你觉得3种编码原则怎么搭配使用,在什么样的场合下选择比较麻烦,不要紧,都用就得了。我们牺牲的只是 NSAutoreleasePool 中的一些内存,一小许性能损失罢了,这总比我们的程序崩溃了强。
ARC 是什么我不需要再解释,若有不明白,可以看iPhone开发之深入浅出 (1) — ARC是什么。
并从编译器角度维护了该原则,比如如果不是 alloc/new/copy/mutableCopy 开头的函数,编译器会将生成的对象自动放入 autoReleasePool 中。如果是 __strong 修饰的变量,编译器会自动给其加上所有权。等等,详细,我们根据不同的关键字来看看编译器为我们具体做了什么。并从中总结出 ARC 的使用规则。
我们先来看看用 __strong 修饰的变量,以及缺省隐藏的 __strong 情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ /* * 生成对象并拥有所有权 */ id __strong obj = [[NSObject alloc] init]; /* * 自己一直是持有对象状态 */ } /* * 变量出生命周期时,失去全部所有者,对象内存空间被释放 */ |
这种情况毫无悬念,缺省使用 alloc/new/copy/mutableCopy 开头的函数也是这样的结果。并且在这里,编译器帮我们自动的调用了对象的 release 函数,不需要手工维护。再看看下面的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ /* * 生成对象但是并没有其所有权 */ id __strong obj = [NSMutableArray array]; /* * 由于变量声明是强引用,自己一直是持有对象状态 * 编译器根据函数名,再将该对象放入 autoreleasepool 中 */ } /* * 变量出生命周期时,失去全部所有者,对象内存空间被释放 */ |
由上,虽然不是用 alloc/new/copy/mutableCopy 开头的函数得到的对象,由于是强参照,我们任然成为对象的持有者。而这,正是编译器帮我们做到的。
具体做的是什么呢?其实就是【对象在函数中返回时,使用return [[object retain] autorelease]】所描述的;如果你反汇编一下ARC生成的代码,可以看到这时会自动调用名为 objc_retainAutoreleaseReturnValue 的函数,而其作用和 [[object retain] autorelease] 一致。编译器通过函数名分析,如果不是 alloc/new/copy/mutableCopy 开头的函数,自动加入了这段代码。
另外,缺省 __strong 修饰的变量,对象代入的时候也正确地保证对象所有者规则;代入新对象时,自动释放旧对象的参照,代入nil的时候,表示释放当前对象的强参照。
虽然大部分场合,大部分问题使用 __strong 来编码就足够了;但是为了解决循环参照的问题 __weak 关键字修饰【弱参照】变量就发挥了左右。关于循环参照的问题,准备在以后的博文中介绍;今天,主要看看编译器在背后怎么处理 __weak 变量的。
__weak 声明的变量其实是被放入一个weak表中,该表和引用计数的表格类似,是一个Hash表,都是以对象的内存地址做key,同时,针对一个对象地址的key,可以同时对应多个变量的地址。
另外,当使用 __weak 修饰的变量的时候,变量将放入 autoreleasepool 中,并且用几次放几次。比如下面的简单例子。
1 2 3 4 5 6 7 8 |
{ id __weak o = obj; NSLog(@"1 %@", o); NSLog(@"2 %@", o); NSLog(@"3 %@", o); NSLog(@"4 %@", o); NSLog(@"5 %@", o); } |
这里我们用了5次,那么pool中就被登录了5次;从效率上考虑这样当然不是很好,可以通过代入 __strong 修饰的强参照变量来避开这个问题。
1 2 3 4 5 6 7 8 9 |
{ id __weak o = obj; id temp = o; NSLog(@"1 %@", temp); NSLog(@"2 %@", temp); NSLog(@"3 %@", temp); NSLog(@"4 %@", temp); NSLog(@"5 %@", temp); } |
另外,还有通过重载 allowsWeakReference/retainWeakReference 函数来限制 __weak 声明变量使用回数的方法,毕竟不在本次讨论范畴之内,就此省略。
话说回来,为什么使用弱参照变量的时候,要将其放入 autoreleasepool 中呢?想想弱参照的定义就应该明白了 —- 如果在访问弱参照对象时,该对象被释放了怎么办,程序不就崩溃了嘛;所以为了解决该问题,又再一次用到了 pool。
虽然上面还没有降到该关键字,但是编译器在很多时候已经用到了 autoreleasepool。比如非 alloc/new/copy/mutableCopy 开头的函数返回一个对象的时候,又比如使用一个 __weak 声明的变量的时候。
实际上,写ARC代码的时候,明示 __autoreleasing 声明变量和明示 __strong 声明变量一样基本上没有,因为编译器已经为我们做了很多,很智能了(前提是我们要按ARC的规则写代码)。
还有一种编译器缺省使用 __autoreleasing 关键字声明变量的时候:对象指针类型。比如下面的对应关系。
1 2 |
id *obj == id __autoreleasing *obj NSObject **obj == NSObject * __autoreleasing *obj |
所以,下面两个函数的是等价的。
1 2 3 |
-(BOOL)performOperationWithError:(NSError **)error; -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error; |
像下面的函数调用,为什么是可行的呢?
1 2 |
NSError __strong *error = nil; BOOL result = [obj performOperationWithError:&error]; |
其实,编译器是这样解释这段代码的。
1 2 3 4 |
NSError __strong *error = nil; NSError __autoreleasing *tmp = error; BOOL result = [obj performOperationWithError:&tmp]; error = tmp; |
那么我们这样声明函数不就可以了吗?
1 |
-(BOOL)performOperationWithError:(NSError * __strong *)error; |
答案是肯定的,你可以这样做,编译是可以通过,但你违反了非 alloc/new/copy/mutableCopy 开头的函数,不返回对象持有权的原则。这里是没有问题了,但也许影响到其他地方NG。
(这里只列出本讲中涉及的内容,其他的内容以后总结)
关于函数命名,伴随ARC的导入,还有一系列函数的定义也被严格定义了,那就是以 init 开头的函数。init 函数作为alloc生成对象的初期化函数,需要按原样直接传递对象给调用段,所以下面的声明是OK的。
1 |
-(id)initWithObject:(id)obj; |
而下面的是NG的。
1 |
-(void)initWithObject; |
不过声明为 -(void) initialize; 是没有问题的。
]]>iPhone开发技巧之发布篇(7)— 制作自己的Cydia发布源
针对越狱的iPhone,大家肯定都知道用Cydia来安装各种免费应用的经历。Cydia中是否能指定我们自己的发布源,让我们自己随心所欲地发布程序给别人呢?答案是肯定的,我们只要创建Cydia的发布repository即可。首先,我们需要有Linux Debian或者Ubuntu系统来制作Cydia的发布源。如果你没有Linux Debian系统的机器,不要紧,可以随时用VMware Player或VirtualBox虚拟机来安装一个。
安装的时候用i386的iso即可,如果是64bit的OS用amd64的image来安装。安装的时候,选择Graphical Install,其他缺省安装。下载下面两个脚本文件到本地,并加上可执行属性。
1 2 3 4 5 6 7 8 9 10 |
Cydia +- MyProgram +- DEBIAN | +- control +- var +- mobile +- MyProgram.app +- Info.plist +- MyProgram +- icon.png |
1 2 3 4 5 6 7 8 9 |
Package: net.yifeiyang.MyProgram Name: MyProgram Version: 1.0.4-1 Architecture: iphoneos-arm Description: test text. Homepage: http://www.yifeiyang.net Maintainer: YIFEIYANG <kane_yj@hotmail.com> Author: YIFEIYANG <kane_yj@hotmail.com> Section: Games |
名称 | 说明 |
---|---|
Package | 唯一标示Package的名称,一般用「域名.Package名」 |
Name | 程序的名称 |
Version | 程序版本,不能使用字母 |
Architecture | 固定为iphoneos-arm |
Description | 程序概要说明,将显示在Cydia的说明页内 |
Homepage | 程序网页 |
Maintainer | 维护者名称,邮箱 |
Author | 作者,邮箱 |
Section | 所属类型,设定了之后,程序名旁边将显示具体的icon |
1 2 |
cd ~/Cydia
dpkg-deb –b MyProgram
|
1 2 |
dpkg-scanpackages –m . /dev/null >Packages bzip2 Packages |
iPhone开发技巧之发布篇(7)— 制作自己的Cydia发布源
针对越狱的iPhone,大家肯定都知道用Cydia来安装各种免费应用的经历。Cydia中是否能指定我们自己的发布源,让我们自己随心所欲地发布程序给别人呢?答案是肯定的,我们只要创建Cydia的发布repository即可。
首先,我们需要有Linux Debian或者Ubuntu系统来制作Cydia的发布源。如果你没有Linux Debian系统的机器,不要紧,可以随时用VMware Player或VirtualBox虚拟机来安装一个。
安装的时候用i386的iso即可,如果是64bit的OS用amd64的image来安装。安装的时候,选择Graphical Install,其他缺省安装。
下载下面两个脚本文件到本地,并加上可执行属性。
1 2 3 4 5 6 7 8 9 10 |
Cydia +- MyProgram +- DEBIAN | +- control +- var +- mobile +- MyProgram.app +- Info.plist +- MyProgram +- icon.png |
可以看出来,我们需要把程序MyProgram安装到/var/mobile/中去.
DEBIAN目录下面有一个名为control的文件,我们来看看它的内容。
1 2 3 4 5 6 7 8 9 |
Package: net.yifeiyang.MyProgram Name: MyProgram Version: 1.0.4-1 Architecture: iphoneos-arm Description: test text. Homepage: http://www.yifeiyang.net Maintainer: YIFEIYANG <kane_yj@hotmail.com> Author: YIFEIYANG <kane_yj@hotmail.com> Section: Games |
名称 | 说明 |
---|---|
Package | 唯一标示Package的名称,一般用「域名.Package名」 |
Name | 程序的名称 |
Version | 程序版本,不能使用字母 |
Architecture | 固定为iphoneos-arm |
Description | 程序概要说明,将显示在Cydia的说明页内 |
Homepage | 程序网页 |
Maintainer | 维护者名称,邮箱 |
Author | 作者,邮箱 |
Section | 所属类型,设定了之后,程序名旁边将显示具体的icon |
1 2 |
cd ~/Cydia
dpkg-deb –b MyProgram
|
这之后,我们就可以试试该deb文件,用iFile将文件上传到iPhone上并安装,查看/var/mobile/下的情况。这里我们可以使用iPhone开发技巧之发布篇(6)— 不需Developper认证的真机调试方法中的方法制作我们的程序,从而不需要验证信息。
1 2 |
dpkg-scanpackages –m . /dev/null >Packages bzip2 Packages |
OK了,接下来我们就可以把Packages.bz2和MyProgram.deb这两个文件上传到你的web服务器中,然后把地址作为Source添加到Cydia中就搞定了。如果没有web服务器也没有关系,可以使用Dropbox等共享网盘,得到唯一的一个URL即可。
]]>iPhone开发之深入浅出 (2) — ARC之@property使用
上一回我们学到了一些ARC的基本概念,这一次我们来看看ARC对@property的使用规则有何影响。
我们先来看看与所有权有关系的属性,关键字间的对应关系。
属性值 | 关键字 | 所有权 |
---|---|---|
strong | __strong | 有 |
weak | __weak | 无 |
unsafe_unretained | __unsafe_unretained | 无 |
copy | __strong | 有 |
assign | __unsafe_unretained | 无 |
retain | __strong | 有 |
该属性值对应 __strong 关键字,即该属性所声明的变量将成为对象的持有者。
该属性对应 __weak 关键字,与 __weak 定义的变量一致,该属性所声明的变量将没有对象的所有权,并且当对象被破弃之后,对象将被自动赋值nil。
并且,delegate 和 Outlet 应该用 weak 属性来声明。同时,如上一回介绍的 iOS 5 之前的版本是没有 __weak 关键字的,所以 weak 属性是不能使用的。这种情况我们使用 unsafe_unretained。
等效于__unsafe_unretaind关键字声明的变量;像上面说明的,iOS 5之前的系统用该属性代替 weak 来使用。
与 strong 的区别是声明变量是拷贝对象的持有者。
一般Scalar Varible用该属性声明,比如,int, BOOL。
该属性与 strong 一致;只是可读性更强一些。
读写相关的属性有 readwrite 和 readonly 两种,如果使用ARC之后,我么需要注意一下 readonly 属性的使用。
比如下面的变量声明。
1 |
@property (nonatomic, readonly) NSString *name; |
一般声明为 readonly 的变量按理说应该不需要持有所有权了,但是在ARC有效的情况下,将出现下面的错误信息 :
“ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute”
如果定义了ARC有效,那么必须要有所有者属性的定义;所以我们的代码改成这样,就OK了
1 |
@property (nonatomic, strong, readonly) NSString *name; |
不过有一点,Scalar Varible的变量缺省都有 assign 的属性定义,所以不需要给他们单独的明示声明了。
]]>iPhone开发之深入浅出 (2) — ARC之@property使用
上一回我们学到了一些ARC的基本概念,这一次我们来看看ARC对@property的使用规则有何影响。
我们先来看看与所有权有关系的属性,关键字间的对应关系。
属性值 | 关键字 | 所有权 |
---|---|---|
strong | __strong | 有 |
weak | __weak | 无 |
unsafe_unretained | __unsafe_unretained | 无 |
copy | __strong | 有 |
assign | __unsafe_unretained | 无 |
retain | __strong | 有 |
该属性值对应 __strong 关键字,即该属性所声明的变量将成为对象的持有者。
该属性对应 __weak 关键字,与 __weak 定义的变量一致,该属性所声明的变量将没有对象的所有权,并且当对象被破弃之后,对象将被自动赋值nil。
并且,delegate 和 Outlet 应该用 weak 属性来声明。同时,如上一回介绍的 iOS 5 之前的版本是没有 __weak 关键字的,所以 weak 属性是不能使用的。这种情况我们使用 unsafe_unretained。
等效于__unsafe_unretaind关键字声明的变量;像上面说明的,iOS 5之前的系统用该属性代替 weak 来使用。
与 strong 的区别是声明变量是拷贝对象的持有者。
一般Scalar Varible用该属性声明,比如,int, BOOL。
该属性与 strong 一致;只是可读性更强一些。
读写相关的属性有 readwrite 和 readonly 两种,如果使用ARC之后,我么需要注意一下 readonly 属性的使用。
比如下面的变量声明。
1 |
@property (nonatomic, readonly) NSString *name; |
一般声明为 readonly 的变量按理说应该不需要持有所有权了,但是在ARC有效的情况下,将出现下面的错误信息 :
“ARC forbids synthesizing a property of an Objective-C object with unspecified ownership or storage attribute”
如果定义了ARC有效,那么必须要有所有者属性的定义;所以我们的代码改成这样,就OK了
1 |
@property (nonatomic, strong, readonly) NSString *name; |
不过有一点,Scalar Varible的变量缺省都有 assign 的属性定义,所以不需要给他们单独的明示声明了。
]]>iPhone开发之深入浅出 (1) — ARC是什么
新年伊始,万象更新。新一年开始,我们来更加深入了解一下iPhone开发的内部。作为开始,我们先来了解一下ARC。
ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting)。简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。
该机能在 iOS 5/ Mac OS X 10.7 开始导入,利用 Xcode4.2 可以使用该机能。简单地理解ARC,就是通过指定的语法,让编译器(LLVM 3.0)在编译代码时,自动生成实例的引用计数管理部分代码。有一点,ARC并不是GC,它只是一种代码静态分析(Static Analyzer)工具。
通过一小段代码,我们看看使用ARC前后的变化点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@interface NonARCObject : NSObject { NSString *name; } -(id)initWithName:(NSString *)name; @end @implementation NonARCObject -(id)initWithName:(NSString *)newName { self = [super init]; if (self) { name = [newName retain]; } return self; } -(void)dealloc { [name release]; [Super dealloc]; } @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@interface ARCObject : NSObject { NSString *name; } -(id)initWithName:(NSString *)name; @end @implementation ARCObject -(id)initWithName:(NSString *)newName { self = [super init]; if (self) { name = newName; } return self; } @end |
而使用ARC后,我们可以不需要这样做了,甚至连最基础的release都不需要了。
使用ARC有什么好处呢?
关于第二点,由于 XCode4.2 中缺省ARC就是 ON 的状态,所以编译旧代码的时候往往有"Automatic Reference Counting Issue"的错误信息。
这个时候,可以将项目编译设置中的“Objectice-C Auto Reference Counteting”设为NO。如下所示。
如果只想对某个.m文件不适应ARC,可以只针对该类文件加上 -fno-objc-arc 编译FLAGS,如下图。
由于ARC并不是GC,并需要一些规则让编译器支持代码插入,所以必须清楚清楚了这些规则后,才能写出健壮的代码。
ObjectiveC中的对象,有强参照(Strong reference)和弱参照(Weak reference)之分,当需要保持其他对象的时候,需要retain以确保对象引用计数加1。对象的持有者(owner)只要存在,那么该对象的强参照就一直存在。
firstName作为”natsu”字符串对象的最初持有者,是该NSString类型对象的Strong reference。
这里将firstName代入到aName中,即aName也成为了@”natsu”字符串对象的持有者,对于该对象,aName也是Strong reference。
这里,改变firstName的内容。生成新的字符串对象”maki”。这时候firstName成为”maki”的持有者,而@”natsu”的持有者只有aName。每个字符串对象都有各自的持有者,所以它们都在内存中都存在。
追加新的变量otherName, 它将成为@”maki”对象的另一个持有者。即NSString类型对象的Strong reference。
将otherName代入到aName,这时,aName将成为@”maki”字符串对象的持有者。而对象@”natsu”已经没有持有者了,该对象将被破弃。
接下来我们来看看弱参照 (Weak reference) 的使用方式。
与强参照方式同样,firstName作为字符串对象@”natsu”的持有者存在。即是该NSString类型对象的Strong reference。
使用关键字__weak,声明弱参照weakName变量,将firstName代入。这时weakName虽然参照@”natsu”,但仍是Weak reference。即weakName虽然能看到@”natsu”,但不是其持有者。
firstName指向了新的对象@”maki”,成为其持有者,而对象@”natsu”因为没有了持有者,即被破弃。同时weakName变量将被自动代入nil。
ARC中关于对象的引用参照,主要有下面几关键字。使用strong, weak, autoreleasing限定的变量会被隐式初始化为nil。
变量声明缺省都带有__strong关键字,如果变量什么关键字都不写,那么缺省就是强参照。
上面已经看到了,这是弱参照的关键字。该概念是新特性,从 iOS 5/ Mac OS X 10.7 开始导入。由于该类型不影响对象的生命周期,所以如果对象之前就没有持有者,那么会出现刚创建就被破弃的问题,比如下面的代码。
1 2 |
NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; NSLog(@"string: %@", string); //此时 string为空 |
如果编译设定OS版本 Deployment Target 设定为这比这低的版本,那么编译时将报错(The current deployment target does not support automated __weak references),这个时候,我们可以使用下面的__unsafe_unretained。
弱参照还有一个特征,即当参数对象失去所有者之后,变量会被自动付上nil (Zeroing)。
该关键字与__weak一样,也是弱参照,与__weak的区别只是是否执行nil赋值(Zeroing)。但是这样,需要注意变量所指的对象已经被破弃了,地址还还存在,但内存中对象已经没有了。如果还是访问该对象,将引起「BAD_ACCESS」错误。
该关键字使对像延迟释放。比如你想传一个未初始化的对像引用到一个方法当中,在此方法中实例化此对像,那么这种情况可以使用__autoreleasing。他被经常用于函数有值参数返回时的处理,比如下面的例子。
1 2 3 4 5 6 7 8 9 10 11 |
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError { .... *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary]; } .... { NSError *error = nil; [self generateErrorInVariable:&error]; NSLog(@"Error = %@", error); } |
又如函数的返回值是在函数中申请的,那么希望释放是在调用端时,往往有下面的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-(NSString *)stringTest { NSString *retStr = [NSString stringWithString:@"test"]; return [[retStr retain] autorelease]; } // 使用ARC -(NSString *)stringTest { __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"]; return retStr; } |
即当方法的参数是id*,且希望方法返回时对象被autoreleased,那么使用该关键字。
今后,我们将更加深入ARC,学习其更多的特性。
]]>iPhone开发之深入浅出 (1) — ARC是什么
新年伊始,万象更新。新一年开始,我们来更加深入了解一下iPhone开发的内部。作为开始,我们先来了解一下ARC。
ARC是iOS 5推出的新功能,全称叫 ARC(Automatic Reference Counting)。简单地说,就是代码中自动加入了retain/release,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了。
该机能在 iOS 5/ Mac OS X 10.7 开始导入,利用 Xcode4.2 可以使用该机能。简单地理解ARC,就是通过指定的语法,让编译器(LLVM 3.0)在编译代码时,自动生成实例的引用计数管理部分代码。有一点,ARC并不是GC,它只是一种代码静态分析(Static Analyzer)工具。
通过一小段代码,我们看看使用ARC前后的变化点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@interface NonARCObject : NSObject { NSString *name; } -(id)initWithName:(NSString *)name; @end @implementation NonARCObject -(id)initWithName:(NSString *)newName { self = [super init]; if (self) { name = [newName retain]; } return self; } -(void)dealloc { [name release]; [Super dealloc]; } @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@interface ARCObject : NSObject { NSString *name; } -(id)initWithName:(NSString *)name; @end @implementation ARCObject -(id)initWithName:(NSString *)newName { self = [super init]; if (self) { name = newName; } return self; } @end |
而使用ARC后,我们可以不需要这样做了,甚至连最基础的release都不需要了。
使用ARC有什么好处呢?
关于第二点,由于 XCode4.2 中缺省ARC就是 ON 的状态,所以编译旧代码的时候往往有"Automatic Reference Counting Issue"的错误信息。
这个时候,可以将项目编译设置中的“Objectice-C Auto Reference Counteting”设为NO。如下所示。
如果只想对某个.m文件不适应ARC,可以只针对该类文件加上 -fno-objc-arc 编译FLAGS,如下图。
由于ARC并不是GC,并需要一些规则让编译器支持代码插入,所以必须清楚清楚了这些规则后,才能写出健壮的代码。
ObjectiveC中的对象,有强参照(Strong reference)和弱参照(Weak reference)之分,当需要保持其他对象的时候,需要retain以确保对象引用计数加1。对象的持有者(owner)只要存在,那么该对象的强参照就一直存在。
firstName作为”natsu”字符串对象的最初持有者,是该NSString类型对象的Strong reference。
这里将firstName代入到aName中,即aName也成为了@”natsu”字符串对象的持有者,对于该对象,aName也是Strong reference。
这里,改变firstName的内容。生成新的字符串对象”maki”。这时候firstName成为”maki”的持有者,而@”natsu”的持有者只有aName。每个字符串对象都有各自的持有者,所以它们都在内存中都存在。
追加新的变量otherName, 它将成为@”maki”对象的另一个持有者。即NSString类型对象的Strong reference。
将otherName代入到aName,这时,aName将成为@”maki”字符串对象的持有者。而对象@”natsu”已经没有持有者了,该对象将被破弃。
接下来我们来看看弱参照 (Weak reference) 的使用方式。
与强参照方式同样,firstName作为字符串对象@”natsu”的持有者存在。即是该NSString类型对象的Strong reference。
使用关键字__weak,声明弱参照weakName变量,将firstName代入。这时weakName虽然参照@”natsu”,但仍是Weak reference。即weakName虽然能看到@”natsu”,但不是其持有者。
firstName指向了新的对象@”maki”,成为其持有者,而对象@”natsu”因为没有了持有者,即被破弃。同时weakName变量将被自动代入nil。
ARC中关于对象的引用参照,主要有下面几关键字。使用strong, weak, autoreleasing限定的变量会被隐式初始化为nil。
变量声明缺省都带有__strong关键字,如果变量什么关键字都不写,那么缺省就是强参照。
上面已经看到了,这是弱参照的关键字。该概念是新特性,从 iOS 5/ Mac OS X 10.7 开始导入。由于该类型不影响对象的生命周期,所以如果对象之前就没有持有者,那么会出现刚创建就被破弃的问题,比如下面的代码。
1 2 |
NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]]; NSLog(@"string: %@", string); //此时 string为空 |
如果编译设定OS版本 Deployment Target 设定为这比这低的版本,那么编译时将报错(The current deployment target does not support automated __weak references),这个时候,我们可以使用下面的__unsafe_unretained。
弱参照还有一个特征,即当参数对象失去所有者之后,变量会被自动付上nil (Zeroing)。
该关键字与__weak一样,也是弱参照,与__weak的区别只是是否执行nil赋值(Zeroing)。但是这样,需要注意变量所指的对象已经被破弃了,地址还还存在,但内存中对象已经没有了。如果还是访问该对象,将引起「BAD_ACCESS」错误。
该关键字使对像延迟释放。比如你想传一个未初始化的对像引用到一个方法当中,在此方法中实例化此对像,那么这种情况可以使用__autoreleasing。他被经常用于函数有值参数返回时的处理,比如下面的例子。
1 2 3 4 5 6 7 8 9 10 11 |
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError { .... *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary]; } .... { NSError *error = nil; [self generateErrorInVariable:&error]; NSLog(@"Error = %@", error); } |
又如函数的返回值是在函数中申请的,那么希望释放是在调用端时,往往有下面的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-(NSString *)stringTest { NSString *retStr = [NSString stringWithString:@"test"]; return [[retStr retain] autorelease]; } // 使用ARC -(NSString *)stringTest { __autoreleasing NSString *retStr = [NSString alloc] initWithString:@"test"]; return retStr; } |
即当方法的参数是id*,且希望方法返回时对象被autoreleased,那么使用该关键字。
今后,我们将更加深入ARC,学习其更多的特性。
]]>