|
http://wenku.baidu.com/link?url=oYpqonvYUYhxZWRbiHS3T35is1Gnv8wXqdYazhQyDBnqwbSpQsOQ7n6VbXGUT488atej5CVt7xabxhyPJ65K6abK_BEf1oC9_AYJPeV3pPG
iOS 很有用到一些知识点!!!
2013-04-08 11:54
624人阅读 评论(0)
收藏
举报
目录(?)[+]
iOS应用为什么要用两套图
Mar 13th, 2013 | Posted by
njuxjy
No comments 之前有个疑问,既然现在retina的屏幕已经很主流,3GS用户越来越少,而且只放@2x的图在低分辨率屏幕上仍然是可以展示的,那么是否可以在项目中只用一套@2x的图呢,也可以减轻美工部分工作量?
看了stackOverFlow上一个帖子解了我的疑惑:http://stackoverflow.com/questions/10576520/ios-apps-why-include-both-2x-and-low-res-images
主要来讲原因有:
1. 在3GS等非retina屏幕的设备上,系统发现@2x的图片没有其对应的非retina版本,于是只能用比原来尺寸大很多的@2x的图,会占用很多内存
2. 这些设备每次遇到需要画图的地方都需要将retina的图读入内存,并且进行一次复杂的缩放操作,既耗内存又耗cpu,影响性能。在iPad1上尤其明显。
3. 经由系统缩放的图的效果肯定不如自己提供的非retina图好
同事说不提供两套图是通不过苹果审核的,这点倒不是很同意,因为自己以前的项目有些图片只有一套图,app store上一些应用也是。
随着非retina设备逐步进入历史,即使苹果有在这点上有审核,也应该调整策略了吧。一套@2x就够了。
Tags:
iOS6 内存警告处理
Mar 11th, 2013 | Posted by
njuxjy
No comments 在iOS6中将之前的viewDidUnload deprecated掉了,现在使用didReceiveMemoryWarning来处理内存警告。
用个小demo来模拟下这个过程。在某个页面里,在viewDidLoad中开辟了一大块内存:
1
2
3
4
5
6
7
8
NSMutableArray *array = [[NSMutableArray alloc] init];
self.bigArray = array;
[array release];
for(NSInteger i =0; i <
1000000; i++)
{
[array addObject:@"hello"];
} 可以看到内存使用率陡然上升:
然后点击某个按钮跳转到另一个页面中,此时模拟一个内存警告。在模拟器中很容易模拟,在真机上可以通过代码来模拟:
1
2
3
4
- (void)sendMemWarning
{
[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)];
} 因为是私有API,不能打包机发布版本中。实现之前页面中的didReceiveMemoryWarning函数释放可以重建的一些资源:
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
- (void)didReceiveMemoryWarning
{
[superdidReceiveMemoryWarning];
if([self isNeedClearMemory])
{
self.view = nil;
if([self respondsToSelector:@selector(clearMemory)])
{
[self performSelector:@selector(clearMemory)];
}
}
}
- (BOOL)isNeedClearMemory
{
if(self.view.window == nil)
{
returnYES;
}
returnNO;
}
- (void)clearMemory
{
self.button = nil;
self.bigArray = nil;
} 由于每个vc都会接收到此消息,而当前展示页面不能释放内存,因此需要先加以判断是否为活跃页面。需要自己手动释放view,系统不像之前会自动释放不活跃的view。对于在接到警告时需要释放掉内存的变量,不要在init中初始化,这里面的东西属于不可再生资源。
接到内存警告后,内存使用量减至之前水平(之后的再次上升是因为又回到了之前的页面,重新viewDidLoad加载了大块内存):
that’s all.
Tags:
iOS性能优化(WWDC笔记)
Mar 6th, 2013 | Posted by
njuxjy
No comments
程序启动的几个阶段:链接和加载, UIKit初始化,应用回调,第一次的Core Animation transaction
1. 启动速度优化:
方法一:测量启动时间,在main里面第一行记录开始时间,在didfinishLaunching第一行记录结束时间。
方法二:使用Time Profiling工具
1)链接和加载阶段。减少不必要的框架引用,可以减少程序启动时链接和加载的时间;不要将必要的框架设置为optional,因为optional会让链接器做额外工作;避免使用静态初始化,包括静态c++对象,加载时会运行的代码,如+(void) load{} ,会造成在Main函数之前运行额外的代码,
2)UIKit初始化阶段。减少main nib文件的大小; 在preferences中不要存入太多的数据,比如在某个Key中存入一个很大的NSData对象如图片,因为preferences是用plist来保存的,所有的plist都是一次性反序列化出来的;
3) 应用回调阶段。UIKit会首先调用willfinishLaunchingWithOptions,然后恢复应用状态,然后调用didfinishLaunchingWithOptions。
4) 最后阶段。在_reportAppLaunchFinished中会调用CA::Transaction::commit()。
一些戒律:
用工具度量,不要去猜。
不要做比不必要的工作。不必要的shadows和masks,同一数据的多次查询,启动时无数的logging。
重用,而不是重复。重用那些创建代价昂贵的类:tablecell,date/number formatters,正则表达式,sqlite语句。[NSCalendar currentCalendar]看起来像是个单例,其实不是,每次调用都会创建一个新对象。调用nslog的时候会创建一个calendar对象,不要过度nslog。 每次使用sqlite_prepare会将SQL查询编译成字节码,要使用bind,重用那些已经prepared的语句。
提高代码效率。要选择合适的数据结构和算法。 1)选择正确的数据类型。少量数据用plist,大量数据用core data或者sqlite。由于要访问plist中某个数据需要反序列化整个表,所以只能存少量数据。一些API底层使用plist实现的:preferences,通过NSCoding序列化的数据。2)优化数据库查询。用sqlite3_trace和sqlite3_profile来查找性能差的语句。
预计算结果存入内存或者磁盘。1)注意内存增长。如screenSizedImage就不适合用static来一直保存在内存里。
异步加载。1)GCD等API
保持在大数据量下伸缩性良好。如iPhone上的通讯录程序在通讯录里的人变多时启动时间控制的不错。1)保持关键方法快速。比如在Table加载sections时,numberofsectionsintableview,titleforheaderinsecion,numberofrowsinsection等方法就要性能好。
2. 用户事件处理优化:
1)保持主线程用来响应用户事件。
a) 在主线程中加快CPU处理速度,比如UILabel的cache处理;
b) 将事情移到主线程外面来做。隐式并发和显式并发。隐式:view和layer的动画,layer的组合,PNG解码;显式:GCD,NSOperationQueue,NSThread。
c)不要block掉主线程。
3. 内存优化
1)系统如何回收内存?不是把page放到disk,而是直接扔掉不保存。
2)干净内存和脏内存?干净:磁盘上存在的一些东西的拷贝,如代码、框架、内存映射文件。 脏:其他所有,如堆分配、解压的图片、数据库缓存。 NSString * a = [NSString stringWithFormat:@"hello"];是dirdy;NSString * a = @”hello”是clean。
3)在你重复一个操作的时候,内存不应该持续增长。比如:push然后pop一个view controller,滚动table,执行数据库查询。
Tags:
记一次tableview滚动性能调优
Mar 1st, 2013 | Posted by
njuxjy
No comments 项目中的会话界面逻辑很复杂,滚动起来有些卡。从发现问题到一步步解决,过程如下:
1. 锁定问题就和tableview的几个代理函数有关。于是基于二分法的思想,注释掉部分代码,检查只运行其余代码会不会卡。
2. 这样找下来发现性能瓶颈在设置用户头像的函数。 是之前项目里已有的一个UIButton的网络图片下载分类,里面会对图片路径进行MD5的加密,并且没用内存缓存,每次都从磁盘将头像读入UIImage,而头像照片的尺寸有的很大,于是把头像下载控件换成开源的SDWebImage,原以为就能圆满解决,结果发现还是有问题。
3. 只能靠工具了。用了Profile工具查CPU发现是每个cell在计算是否需要加上消息显示日期的时候,日期部分的计算占了大量cpu。每个cell的绘制都频繁创建和销毁NSCalendar和NSDateTimeFormatterz对象,而这两个对象的创建是比较昂贵的,难怪会卡。于是将NSCalendar和NSDateTimeFormatter改为全局cache,不需要每次都创建,粗略测了下发现不卡了。
4. 过了阵子又去测测发现又开始卡了(肯定遇到了未覆盖到的测试用例)。 肯定还有其他地方性能不够好。。 再接再厉,再次运行profile工具,多滚动列表一会,然后观察时间最长的部分在哪里。发现NSDateFormatter的dateFromString函数和获取登录用户头像路径的函数都比较耗时,于是采取逐个击破的方式,先注释掉其中一个的代码,调试另一个。首先将耗时的计算结果尽可能的缓存,比如这里的获取登录用户头像路径。改好后还是卡,说明dateFromString函数的计算时间需要减少,但这是系统函数(我还一度以为是该函数的性能有问题,去SO上搜索半天没人反映它有什么问题)。于是就假设是该函数调用次数太多了。
5. 经检查发现在计算cell高度和绘制cell时,为了确定是否要显示时间条,需要计算两个时间之差是否在五分钟内,计算的函数都要调用dateFromString。必须把计算移到外面去,把结果保存好。改完后好了一阵,多测测又不行了。。还是有丢帧的感觉。。后来怀疑是debug模式下log没关,因为据说每一句NSLog底层都会调用[NSCalendar currentCalendar],但关了以后发现还是不行。有些束手无策。
6. 然后发现整个会话中没有时间条的时候都很顺畅,有了几个时间条以后滚动就会有点卡,于是目光聚焦在绘制时间条的地方。由于之前对时间条UILabel的layer使用了圆角设置了borderColor,这个肯定很耗时,去掉,用默认的UILabel格式,然后在draw的时候根据是否需要展示时间条来设置其hidden属性。发现还是卡。然后发现把UILabel的颜色改成clearColor(跟tableView的backgroundColor一样)就不卡了。之前网上看到说clearColor会影响性能,但这里竟然好像改善了性能。原因可能是在tableView背景色透明时,在上面画不同色的view比较耗时。
然后在label后面加了个背景图,就解决了原先需要用layer的问题了。最终问题解决。当然肯定还有优化空间,但至少比较流畅了,就不做“过早优化”了。
总结下:
1. Time Profile很重要,光凭感觉去猜是不靠谱的。这一点WWDC里也提到了。
2. 性能问题有可能由单个大问题引起,也可能由很多小问题叠加引起。对于后者,用“控制变量法”逐个击破。
Tags:
类微信向上滑动取消发送音频的实现
Feb 27th, 2013 | Posted by
njuxjy
No
comments 之前在项目中实现了音频对讲功能,现在要加上类似微信最新版的向上滑动取消发送功能,看似很简单,也花了些功夫。有更方便的实现欢迎交流。
界面如上所示。底部是一个UIView的子类ChatVoiceView,“按住说话”按钮是该view上的子view。现在要实现的效果是长按该按钮时,向上滑动时画面中央出现“松开手指取消发送”的提示,向下滑动时出现其他提示,同时一直在录着音。手势的滑动可以发生在该界面的任何部分,在上面的tableview伤滑动也有效。
在ChatVoiceView中首先给按钮加上一些事件关联:
1
2
3
[self.longPressBtn addTarget:self action:@selector(voiceHoldButtonTouchUpInside:withEvent:) forControlEvents:UIControlEventTouchUpInside];
[self.longPressBtn addTarget:self action:@selector(voiceHoldButtonTouchUpOutside:withEvent:) forControlEvents:UIControlEventTouchUpOutside];
[self.longPressBtn addTarget:self action:@selector(voiceHoldButtonDragOutside:withEvent:) forControlEvents:UIControlEventTouchDragOutside]; 在响应函数中,除了录音相关的操作外,将事件传递给按钮的nextResponder,也就是让ChatVoiceView根据按钮被按下去的状态做不同的处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)voiceHoldButtonDragOutside:(id)sender withEvent:(UIEvent *)event
{
self.longPressBtn.highlighted = YES;
shouldResponseToSwipe = YES;
[[sender nextResponder] touchesMoved:[event allTouches] withEvent:event];
}
- (void)voiceHoldButtonTouchUpInside:(id)sender withEvent:(UIEvent *)event
{
shouldResponseToSwipe = YES;
//do sth here
[[sender nextResponder] touchesEnded:[event allTouches] withEvent:event];
}
- (void)voiceHoldButtonTouchUpOutside:(id)sender withEvent:(UIEvent *)event
{
shouldResponseToSwipe = YES;
//do sth here
[[sender nextResponder] touchesEnded:[event allTouches] withEvent:event];
}
在ChatVoiceView中加上触摸事件捕获函数:
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
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if(shouldResponseToSwipe == NO)
{
return;
}
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.superview];
CGPoint prevLocation = [touch previousLocationInView:self.superview];
if(location.y - prevLocation.y >
0&& direction ==
1)//向下
{
//do sth
}
elseif(location.y - prevLocation.y <0
&& direction == 2)//向上
{
//do sth
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(shouldResponseToSwipe == NO)
{
return;
}
shouldResponseToSwipe = NO;
} done.
Tags:
iOS字体
Dec 12th, 2012 | Posted by
njuxjy
No comments 可以参考:http://iosfonts.com
Tags:
去除Grouped UITableView的边框与背景
Dec 3rd, 2012 | Posted by
njuxjy
No comments viewDidLoad中,
self.tableView.backgroundView = nil; //去除table背景
[self.tableView setSeparatorColor:[UIColor clearColor]]; //去除边框
cell创建过程中,
[cell setBackgroundColor:[UIColor clearColor]]; //去除背景
然后就可以根据cell的位置是top、middle、bottom和single来贴不同的自定义背景图了。
ps: 如果只要去掉某个cell的背景和边框,可以对某个cell调用:
cell.backgroundView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
Tags:
CFURLCreateWithString在模拟器上返回null的问题
Nov 7th, 2012 | Posted by
njuxjy
No
comments 在程序中用到了
CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, (CFStringRef)fileName, NULL);
其中fileName的值是应用目录下的某个文件,在真机上一切正常,但在模拟器上url始终为Null。
最后查出是因为fileName的值在模拟器上是“~/Library/Application Support/iPhone Simulator/…/”,路径中间有空格,而真机上没有,所以要对fileName中的空格进行处理:
CFStringRef fileNameEscaped = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)fileName, NULL, NULL, kCFStringEncodingUTF8);
这下就对了。
Tags:
iOS摇动检测
Oct 31st, 2012 | Posted by
njuxjy
No comments 首先在AppDelegate中开启摇动支持:
安师大
1
2
3
4
5
- (void)applicationDidFinishLaunching:(UIApplication *)application {
application.applicationSupportsShakeToEdit = YES;
[window addSubview:viewController.view];
[window makeKeyAndVisible];
} 首先在AppDelegate中开启摇动支持:ViewController中增加三个方法:canbecomeFirstResponder:, viewDidAppear:和viewWillDisappear:。
1
2
3
4
5
6
7
8
9
10
11
12
13
-(BOOL)canBecomeFirstResponder {
returnYES;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[self resignFirstResponder];
[super viewWillDisappear:animated];
} 增加motionEnded方法:
1
2
3
4
5
6
7
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if(motion == UIEventSubtypeMotionShake)
{
// your code
}
} ok, that’s it.
Tags:
代码戒律:Objective-C最佳实践(译文)
Oct 12th, 2012 | Posted by
njuxjy
No
comments 在逛V2EX时候发现了这篇文章,遂决定翻译一下,自己也顺带学习下。
作者的一些观点有些偏激,自己心里有数就好。
原文网址:http://ironwolf.dangerousgames.com/blog/archives/913
=========================
2012.4.26更新,加入了ARC
前言
我通常在自己的博客中不会写太技术性的东西,但这次例外,因为我希望为Mac和iOS(iPhone&iPad)开发者社区做些贡献。如果你不是社区中的一员,请自行绕道吧。
介绍
这篇文章是在我多年的Objective-C使用经验中所目睹的那些最容易被Objective-C程序员触犯的最佳实践积累下来的一个列表。我称之为“戒律”,我们有太多理由要去遵守它们,几乎没理由不去遵守。然而当我向其他开发者展示这些实践经验时,他们往往非常反对…
强烈反对:这不会影响性能吗?
这个反对基本没什么道理。如果你能向我举出一个例子来证明遵循以下任何一条规范写出来的代码竟然成了性能瓶颈,那我祝愿你能不按照规范优化出好的代码,只要你能够在代码中清楚地写明你是怎样打破规范的以及为什么。
为什么?
这些规范为了让代码更安全,节省内存,提高可读性和可维护性。大多数规范不能帮助提高性能。然而,写代码时不应该首先考虑性能。我们应该写出简洁、正确的代码,仅仅当性能分析告诉你有必要优化时才去做优化。在典型的UI驱动应用中,最大的瓶颈在于用户,然后是网络访问,接着是磁盘访问。但是,过早优化在程序员中还是非常普遍,他们认为如果他们不用尽所有手段去优化的话,他们的应用将会慢的跟狗一样。这肯定是不对的。
重要建议
即使在你写的时候知道你的代码是如何工作的,那也不意味着其他人在尝试修改你的代码前会真正理解它,也不意味着你在几个月后再次看这段代码时能理解它。使用详细的符号命名(译者注:即变量和方法名等用详细的词来命名),在仍然不够易于理解的代码段中加入详细的注释,这样就能总是确保你的代码能够自说明(译者注:self-documenting,即代码易于理解,不需要写文档也能让人读懂它)。每当你解bug或者API误用的时候,将你认为可能会出问题的地方定位在某个方法里,解释这个方法,然后在注释中加入类似KLUDGE的关键字,之后你就能轻易的发现并且解决这些问题了。
文章(尚)未涉及的内容
- 键值编码(KVC)和基于键值的观察者(KVO)。你有可能会用到它们。
2012.4.26新增
戒律…
总是使用自动引用技术(ARC)。这篇文章已经移除了非ARC代码风格。所有的新代码应当用ARC来写,所有的遗留代码应当更新为ARC(或者封装完备并给出精妙的接口)。
戒律…
总是在任何实例变量的声明前加上@private指令,永远不要在类外面直接访问实例变量。
为什么?
- 将信息隐藏(封装)
- 只在类实现的方法中才去直接访问实例变量
- 实例变量的默认访问属性是@protected,意味着子类可以自由访问这些实例变量。但没有足够的理由不要允许子类这么做——父类暴露给外界的所有东西是它的一种契约,改变类成员的内部表示而不改变它的接口或者契约是面向对象封装的重要好处。将你的实例变量设为私有,你就告诉外界它们是实现的细节而非类的接口
坏的做法
@interface Foo : NSObject {
int a;
NSObject* b;
// …
}
// method & property declarations…
@end
好的做法
@interface Foo : NSObject {
@private
int a;
NSObject* b;
// …
}
// method & property declarations…
- @end
戒律…
总是为每个数据成员创建@property,然后在类实现中用”self.name”去访问它。永远不要直接访问你的实例变量。
为什么?
- 属性强制加上了访问限制(例如readonly)
- 属性强制加上了内存管理策略(strong,weak)
- 属性帮你实现了setter和getter
- 使用属性可以强制执行线程安全策略
- 单一的访问实例变量的方法可以增加代码可读性
坏的做法
@interface Foo : NSObject {
@private
NSObject* myObj;
}
@end
@implementation Foo
- (void)bar {
myObj = nil;
}
@end
好的做法
@interface Foo : NSObject {
@private
NSObject* myObj;
}
@property(strong, nonatomic) NSObject* myObj;
@end
@implementation Foo
- (void)bar {
self.myObj = nil;
}
- @end
戒律…
总是在你的属性上加上”nonatomic”,除非你在写线程安全的类,确实需要原子访问,那就在代码中写注释表明你是故意这么做的。
为什么?
那些带有未声明为”nonatomic”属性的类会让人们觉得这是个设计成线程安全的类,但实际上并不是。只有在你确实将类设计成线程安全时,才不要在属性前加上”nonatomic”。
坏的做法
@interface Foo : NSObject
@property(strong) NSObject* myObj;
@end
好的做法
@interface Foo : NSObject
@property(strong, nonatomic) NSObject* myObj;
@end
好的做法
// This class and all it’s properties are thread-safe.
@interface Foo : NSObject
@property(strong) NSObject* myObj;
@end
戒律…
永远不要让你的实例变量名字和属性名或者数据成员名有所混淆。总是在实例变量名结尾加上下划线。 除非你继承了第三方的类,这个类已经有一个数据成员有同样的名字,这样的话就选择一个不同的名字,或者再加一条下划线,然后加入注视解释你为什么要这么做。
在实现中使用”@synthesize name = name_;”,而不要只写”@synthesize name;”
为什么?
- 即使是在类实现中你也最好属性访问器来访问数据成员,而不要直接访问,除非特殊情况。
- 苹果对于他们的私有实例变量用了”_name”的方式,那么你自己的就用”name_”的方式好了,可以避免命名冲突。
- 苹果已经开始在那些应用级的代码示例及模板中使用”name_”的命名习惯。
坏的做法
@interface Foo : NSObject
@property(strong, nonatomic) NSObject* myObj;
@end
// …
@implementation Foo
@synthesize myObj;
- @end
好的做法
@interface Foo : NSObject
@property(strong, nonatomic) NSObject* myObj;
@end
// …
@implementation Foo
@synthesize myObj = myObj_;
@end
戒律…
NEVER redundantly add the data member to the class @interface yourself. Allow the @synthesize directive to implicitly add the data member.
Following this practice will yield many classes that explicitly declare no instance variables in the @interface section. When a class has no instance variables, omit the @private declaration, and even omit the opening and closing braces of the data member
section.
永远不要自己在类接口中重复增加数据成员。让@synthesize指令来为你隐式地增加数据成员。
遵从这条经验,许多类可以不用在类接口中显式地声明实例变量。当一个类没有实例变量时,可以省略@private声明,甚至连数据成员段的开闭括号都可以去掉。
为什么?
- 减少重复。
- 简化类的头文件。
- 不需要把类的声明都写在公共头文件中,你可以在实现文件中声明那些真正会直接访问到的类成员。
坏的做法
@interface Foo : NSObject {
@private
NSObject* myObj_;
}
@property(strong, nonatomic) NSObject* myObj;
@end
// …
@implementation Foo
@synthesize myObj = myObj_;
- @end
好的做法
@interface Foo : NSObject
@property(strong, nonatomic) NSObject* myObj;
@end
// …
@implementation Foo
@synthesize myObj = myObj_;
@end
You may still need to declare the underlying name of the variable if you need to access it directly, as when writing custom getters and setters:
当你自己在为实例变量写setter和getter的时候,如果你在其中需要直接访问变量的话,你得声明名字带下划线的变量:
错误的写法
@interface Foo : NSObject
@property(strong, nonatomic) NSObject* myObj;
@end
// …
@implementation Foo
@synthesize myObj;
- (NSObject*)myObj
{
return self.myObj; // 会递归调用getter!
}
- (void)setMyObj:(NSObject*)myObj
{
self.myObj = myObj; // 会递归调用setter!
}
- @end
正确的写法
@interface Foo : NSObject
@property(strong, nonatomic) NSObject* myObj;
@end
// …
@implementation Foo
@synthesize myObj = myObj_;
- (NSObject*)myObj
{
return myObj_; // 没问题.
}
- (void)setMyObj:(NSObject*)myObj
{
// 没问题
myObj_ = myObj; // 进行赋值(ARC会处理必要的retain和release)
}
- @end
戒律…
NEVER access a data member through an underscore-suffixed symbol UNLESS you are writing a setter or getter.
永远不要访问名字以下划线开头的变量,除非你在写setter或getter。
坏的做法
@interface Foo : NSObject
@property(strong, nonatomic) Bar* myObj;
@end
// …
@implementation Foo
@synthesize myObj = myObj_;
- (void)someMethod
{
myObj_ = [[Bar alloc] init];
}
- @end
好的做法
@interface Foo : NSObject
@property(strong, nonatomic) Bar* myObj;
@end
// …
@implementation Foo
@synthesize myObj = myObj_;
- (void)someMethod
{
self.myObj = [[Bar alloc] init];
}
- @end
戒律…
永远不要在类的头文件中声明内部(私有)方法或属性。总是将所有的内部方法和属性声明放入实现文件中的“类扩展”中。
坏的做法
//
// Foo.h
//
@interface Foo : NSObject
@property(nonatomic) int myPublicProperty;
@property(strong, nonatomic) Bar* myPrivateProperty; // This can be accessed by anyone who includes the header
- (int)myPublicMethod;
- (int)myPrivateMethod; // So can this.
- @end
好的做法
//
// Foo.h
//
@interface Foo : NSObject
// Only the public API can be accessed by including the header
@property(nonatomic) int myPublicProperty;
- (int)myPublicMethod;
- @end
//
// Foo.m
//
@interface Foo () // This is a “class extension” and everything declared in it is private, because it’s in the implementation file
@property(strong, nonatomic) Bar* myPrivateProperty;
- (int)myPrivateMethod;
@end
@implementation Foo
// …
- @end
戒律…
永远不要在一个方法里有超过一句返回语句,让方法中的最后一个语句带有一个非空值的返回类型。
在返回非空值的方法中,第一句语句就声明一个变量保存返回值,给它一个有意义的默认值。必要时在代码不同路径中给它赋值。在最后一个语句中返回它的值。永远不要过早地用返回语句返回它的值。
为什么?
过早的返回语句会增加有一些必要资源释放没有被执行的可能性。
坏的做法
@implementation Foo
- (Bar*)barWithInt:(int)n
{
// Allocate some resource here…
if(n == 0) {
// …and you have to deallocate the resource here…
return [[Bar alloc] init];
} else if(n == 1) {
// …and here…
return self.myBar;
}
// …and here.
return nil;
}
- @end
好的做法
@implementation Foo
- (Bar*)barWithInt:(int)n
{
Bar* result = nil;
// Allocate some resource here…
if(n == 0) {
result = [[Bar alloc] init];
} else if(n == 1) {
result = self.myBar;
}
// …and deallocate the resource here, you’re done!
return result;
}
- @end
戒律…
理解自动释放池用来做什么,什么时候为你创建与释放,以及什么时候你需要自己来创建和释放它。
- 由NSRunLoop在每一个循环自动创建及释放
- 由NSOperation自动创建及释放
- 在线程的开始及结尾处手动创建及释放
- 每当你需要在某个循环中创建及释放大量对象的时候,在循环外手动创建及释放
在ARC模式下,你通过@autoreleasepool{…}指令来创建自动释放池。
戒律…
总是倾向于使用类层面的便利构造函数,而非init构造函数。所有基础框架里的容器类都提供这些。
坏的做法
NSMutableDictionary* dict = [[NSMutableDictionary alloc]init];
好的做法
NSMutableDictionary* dict = [NSMutableDictionarydictionary];
戒律…
总是在你所写类的接口中提供类层面的便利构造函数。
为什么?
这样你所写类的使用者就能够遵循上面那条戒律了。
坏的做法
@interface Foo : NSObject
- (id)initWithBar:(int)bar baz:(int)baz;
@end
好的做法
@interface Foo : NSObject
- (id)initWithBar:(int)bar baz:(int)baz;
+ (Foo*)fooWithBar:(int)bar baz:(int)baz;
@end
戒律…
一定要理解对象所有权的转移什么时候会发生。ARC会帮你处理很多,但你还是得知道在背后到底发生了什么。
常见的获得对象所有权的方法:
- 当你对一个类调用+alloc的时候你就拥有了新对象。
- 当你对一个实例调用-copy或者-mutableCopy的时候你就拥有了新对象。
- 当你将对象赋值给retain或者strong属性时,你就成为了该对象的拥有者。
常见的释放对象所有权的方法:
- Assign another object (or nil) to a property with the (strong) attribute.
- Let an owning local variable go out of scope.
- Another object holding a reference to the object is destroyed.
- 将另一个对象(或者nil)赋值给strong属性。
- 本来拥有其所有权的一个局部变量超出了其生命周期范围。
- 拥有该对象引用的另一个对象被释放了。
戒律…
一定要理解你属性和实例变量的内存管理策略,尤其当你在写自己的getter和setter时。确保你的实例变量上定义的存储策略是正确的,这样ARC才会正确工作。最简单的办法是单用一个@synthesize,然后覆盖setter及/或getter,在其中访问名字带下划线的实例变量,其行为跟属性中所声明的存储策略是一致的。
@interface bar
@property (strong, nonatomic) id foo;
@end
@implementation bar
@synthesize foo = foo_;
- (id)foo
{
return foo_;
}
- (void)setFoo:(id)foo;
{
foo_ = foo; // Retained/released automatically by ARC because of (strong) attribute on @property above
[self syncToNewFoo]; // The reason for our custom setter
}
@end
戒律…
永远不要在你类中出现-dealloc方法,除非需要释放一些特殊的资源(关闭文件,释放由malloc()申请的内存,使定时器失效等)。其他的事情ARC都会帮你做的。
戒律…
只要你为属性加上了自己写的setter,那么总是也要加上自己写的getter,反之亦然。
为什么?
Getter和setter在内存管理、线程安全以及其他一些它们可能会造成副作用的地方需要有对等的行为。你不能指望一个合成的getter或setter能跟一个自己写的getter或setter有对等的行为。因此,如果你打算自己写一个getter或者setter的话,请你两个都写上。
戒律…
如果你总是需要在释放一个对象的时候做一些事情(比如使定时器失效),那么你一定要为它的属性自己写一个setter,然后在你的-dealloc方法中将属性置为nil。
坏的做法
@implementation Foo
@synthesize myTimer;
- (void)dealloc
{
self.myTimer = nil; // 定时器没有置为失效,在对象回收后我们可能还会收到回调!
}
@end
好的做法
@implementation Foo
@synthesize myTimer = myTimer_;
- (NSTimer*)myTimer
{
return myTimer_;
}
- (void)setMyTimer:(NSTimer*)myTimer
{
[myTimer_ invalidate];
myTimer_ = myTimer;
}
- (void)dealloc
{
self.myTimer = nil; // 定时器在对象消失后肯定不会再回调了!在ARC模式下也需要这样。
}
@end
为什么?
ARC负责在-dealloc的时候释放你的strong或retain实例变量,但它是直接释放变量,而不会调用你自己写的setter。因此如果你自己写的setter有其他的一些副作用(比如使定时器失效),你还是得自己去调用它。
戒律…
在写构造函数的时候,一定不要在[super init]调用前写很多代码,写得越少越好。
为什么?
一般情况下,对父类构造函数的调用有肯能会失败,导致调用返回nil。如果发生这样的事,在调用父类构造函数之前你所做的一切初始化工作将变得毫无价值,甚至是有害的,必须要撤销。
坏的做法
@implementation Foo
- (id)initWithBar:(Bar*)bar
{
[bar someMethod];
// other pre-initialization here
if(self = [super init]) {
// other initialization here
} else {
// oops! failed to initialize super class
// undo anything we did above
}
return self;
}
@end
好的做法
@implementation Foo
- (id)init
{
if(self = [super init]) {
// minimal initialization here
}
return self;
}
// Other methods that put a Foo into a usable state
@end
戒律…
当你没有使用nib文件在写UIViewController的时候,一定要在-loadView中创建你的视图层次结构,永远不要在-init中做。你只能够在-loadView的实现中对view属性进行赋值。
戒律…
永远不要自己调用-loadView!UIViewController的view属性在它被访问时会懒加载。它也会在低内存情况下被自动清除。因此永远不要假设UIViewController的view会跟controller活的一样长。
戒律…
总是只创建你需要的视图一次,然后在必要时展示、隐藏或移动它们。永远不要在每次一有变化就重复回收和再创建你的视图层次结构。
戒律…
永远不要自己在UIView上调用-drawRect。可以调用-setNeedsDisplay。
戒律…
一定要避免在代码中做很长很复杂的操作。局部变量是你的朋友。
坏的做法
NSMutableDictionary* listDict = [[NSMutableDictionaryalloc] initWithDictionary:[[NSUserDefaultsstandardUserDefaults] objectForKey:@”foo”]];
好的做法
NSUserDefaults* defaults = [NSUserDefaultsstandardUserDefaults];
NSDictionary* dataModelDict = [defaults objectForKey:@"foo"];
NSMutableDictionary* listDict = [NSMutableDictionarydictionaryWithDictionary:dataModelDict];
为什么?
戒律…
当你没有在画什么东西的时候,方法名永远不要类似于-drawUI这样。总是用类似于-setupUI或者-resetUI这样的名字,尤其是当方法可能被调用不止一次的时候。
戒律…
永远不要重复。针对每个信息或者功能,都有个地方单独存放,程序中哪里能够用到就部署进去,甚至意味着有时候要触及到他人的代码。永远不要通过复制粘贴来编程。
2010.10.15更新
戒律…
永远不要调用-stringWithString,除非你:
- 正将NSString转为NSMutableString。
- 正将NSMutableString转为NSString。
- 需要保证你正在处理的NSString*确实是不可变的。
- 真的需要一份NSMutableString的拷贝,因为你打算分别修改它们。
为什么?
NSString是不可变的。一定不要去拷贝一个NSString,除非你想要一份可变的拷贝,或者确保一个你想保存的NSString的指针并没有变为NSMutableString的指针(那样的话,之后其他代码可能会改变了它的值)。实际上,试图拷贝NSString的行为仅仅是增加了该字符串的引用计数,然后再返回该字符串本身。
[/code]
2012.04.26新增
戒律...
对于那些可以接收有可变子类的对象(像NSString或NSArray)的属性,存储类型一定要使用(copy)。
为什么?
属性的(copy)存储类型(还有-copy方法)总是产生这些对象的不可变拷贝。因此你可以依靠这些拷贝的不可变性(译者注:不用担心会改变原来对象的值),而你不能依靠原来对象的不可变性。这也是对调用者的一个承诺:你将不会改变通过这种方式传给你的对象的值,即使你不小心改变了该对象拷贝的值(译者注:也不会影响原对象的值)。
戒律...
When overriding a method in a superclass, ALWAYS call the superclass' implementation,even if you know that super's implementation does nothing, unless you have a very good reason to not call super's implementation,
in which case you must document your reason in the comments.
当覆盖父类中的某个方法时,总是要调用父类的实现,即使你知道父类的实现什么事情也没做,除非你有不调用父类实现的很好的理由,这种情况下你必须在注释中写下你的理由。
为什么?
- 父类的实现在将来可能不会一直什么都不做。
- 可能有其他类最终会介入到你的类和你的父类之间,并且对父类的这个方法有一个非空的实现。
- 使你的代码更加能自说明,看你的代码就能知道这是个覆盖父类的方法,因为调用了父类的实现。
坏的做法
@implementation Foo
- (void)awakeFromNib
{
// do my setup
}
@end
好的做法
@implementation Foo
- (void)awakeFromNib
{
[super awakeFromNib];
// do my setup
}
@end
2010.10.15新增
戒律...
在条件语句中,永远不要把指针或数值当成布尔值。
为什么?
布尔值有两个值:true和false(按照Objective-C的习惯我们使用YES和NO)。而指针的值是一个对象或者nil。数值的值是某个数或0。在条件语句上下文中,o和nil和布尔值的false是等同的,这是一种形式的隐式类型转换,会让你的代码难以理解。因此要显式的测试这些值,对于指针看它是否等于nil,对于整型看它是否等于0,对于浮点看它是否等于0.0,等等。
坏的做法
- (void)fooWithBar:(Bar*)bar baz:(BOOL)baz quux:(float)quux
{
if(bar && baz && quux) {
// do something interesting
}
}
好的做法
- (void)fooWithBar:(Bar*)bar baz:(BOOL)baz quux:(float)quux
{
if(bar != nil && baz && quux != 0.0) {
// do something interesting
}
}
2010.10.15新增
戒律...
NEVER use conditional statements to check for nil when you don't have to. In particular, understand that in Objective-C, the act of calling any method on a nil object reference is a no-op that returns zero (or NO, or nil), and write your
code accordingly.
当没必要的时候,永远不要用条件语句去测试是否为nil。特别的是,在Objective-C里,在nil对象上调用任何方法相当于做了一步返回值为0(或者NO,或者nil)的空操作,你应该相应去写你的代码。
坏的做法
- (void)setObj:(NSObject*)obj
{
if(obj_ != nil) {
[obj_ doCleanup];
}
if(obj != nil) {
[obj doSetup];
}
obj_ = obj;
}
好的做法
- (void)setObj:(NSObject*)obj
{
[obj_ doCleanup]; // Does nothing if obj_ == nil
[obj doSetup]; // Does nothing if obj == nil
obj_ = obj;
}
Tags: |
|