我知道HIG(这非常方便!),但是在编写Objective-C时使用了什么编程实践,更具体地说,在使用Cocoa(或CocoaTouch)时.
我开始做的一些事情我不认为是标准的:
1)随着属性的出现,我不再使用"_"来加上"私有"类变量的前缀.毕竟,如果一个变量可以被其他类访问,那么它应该没有属性吗?我总是不喜欢使用"_"前缀来使代码变得更加丑陋,现在我可以把它留下来.
2)说到私有事物,我更喜欢将.m文件中的私有方法定义放在类扩展中,如下所示:
#import "MyClass.h" @interface MyClass () - (void) someMethod; - (void) someOtherMethod; @end @implementation MyClass
为什么用外人不应该关心的东西来混淆.h文件?empty()适用于.m文件中的私有类,如果不实现声明的方法,则发出编译警告.
3)我已经将dealloc放在.m文件的顶部,就在@synthesize指令的下面.你不应该把你想要在课堂上想到的东西放在最重要的位置吗?在像iPhone这样的环境中尤其如此.
3.5)在表格单元格中,使每个元素(包括单元格本身)对性能不透明.这意味着在所有内容中设置适当的背景颜色.
3.6)使用NSURLConnection时,通常您可能希望实现委托方法:
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { return nil; }
我发现大多数Web调用都是非常单一的,并且它比您希望缓存响应的规则更为例外,尤其是对于Web服务调用.实现如图所示的方法会禁用响应的缓存.
同样有趣的是,Joseph Mattiello的一些优秀的iPhone特定提示(在iPhone邮件列表中收到).还有更多,但这些是我认为最普遍有用的(请注意,现在已经从原始版本略微编辑了一些内容以包含响应中提供的详细信息):
4)如果必须,只使用双精度,例如使用CoreLocation时.确保以'f'结束常量以使gcc将它们存储为浮点数.
float val = someFloat * 2.2f;
这someFloat
可能实际上是一个双倍,这是非常重要的,你不需要混合模式数学,因为你在存储上'val'的精度会失去.虽然iPhone上的硬件支持浮点数,但与单精度相比,双精度算术可能仍需要更多时间.参考文献:
iPhone上的Double vs float
iPhone/iPad双精度数学
在较旧的手机上,据说计算以相同的速度运行,但是你可以在寄存器中拥有比双精度更多的单精度元件,因此对于许多计算,单精度最终会更快.
5)将您的属性设置为nonatomic
.他们是atomic
默认的,在合成时,将创建信号量代码以防止出现多线程问题.99%的人可能不需要担心这一点,并且当设置为非原子时,代码不那么臃肿,内存效率更高.
6)SQLite可以是一种非常快速的缓存大数据集的方法.例如,地图应用程序可以将其图块缓存到SQLite文件中.最昂贵的部分是磁盘I/O. 通过在大块之间发送BEGIN;
和避免许多小写操作COMMIT;
.例如,我们使用2秒计时器重置每个新提交.到期时,我们发送COMMIT; ,这会导致所有写入都进入一个大块.SQLite将事务数据存储到磁盘并执行此开始/结束包装可避免创建许多事务文件,将所有事务分组到一个文件中.
此外,如果您的GUI位于主线程上,SQL将阻止您的GUI.如果您有一个很长的查询,最好将查询存储为静态对象,并在单独的线程上运行SQL.确保为@synchronize() {}
块中的查询字符串包装修改数据库的任何内容.对于简短的查询,只需将内容留在主线程上以方便使用.
这里有更多的SQLite优化技巧,虽然文档看起来已经过时,但许多要点可能仍然很好;
http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html
当方法或函数采用格式字符串参数时,应确保您可以控制格式字符串的内容.
例如,在记录字符串时,很有可能将字符串变量作为唯一参数传递给NSLog
:
NSString *aString = // get a string from somewhere; NSLog(aString);
这个问题是字符串可能包含被解释为格式字符串的字符.这可能导致错误的输出,崩溃和安全问题.相反,您应该将字符串变量替换为格式字符串:
NSLog(@"%@", aString);
使用标准的Cocoa命名和格式约定和术语,而不是您在其他环境中习惯使用的任何内容.目前有很多的Cocoa开发在那里,当他们中的一个又一个开始你的代码的工作,如果它的外观和感觉类似于其他可可代码它会更平易近人.
做什么和不做什么的例子:
不要id m_something;
在对象的接口中声明并将其称为成员变量或字段 ; 使用something
或_something
为其名称,并将其称为实例变量.
不要说吸气剂-getSomething
; 正确的Cocoa名称就是-something
.
不要说一个二传手-something:
; 它应该是-setSomething:
方法名称散布着参数并包含冒号; 是的-[NSObject performSelector:withObject:]
,不是NSObject::performSelector
.
在方法名称,参数,变量,类名等中使用inter-caps(CamelCase)而不是underbars(下划线).
类名以大写字母开头,变量和方法名称以小写字母开头.
无论你做什么,都不要使用Win16/Win32风格的匈牙利表示法.甚至微软也放弃了迁移到.NET平台.
从历史上看,网点的内存管理一直很差.目前的最佳做法是将出口声明为财产:
@interface MyClass :NSObject { NSTextField *textField; } @property (nonatomic, retain) IBOutlet NSTextField *textField; @end
使用属性使内存管理语义清晰; 如果使用实例变量合成,它还提供一致的模式.
注意:在Xcode 4下,现在内置于IDE中.
您可以使用Clang静态分析器 - 毫不奇怪 - 在Mac OS X 10.5上分析您的C和Objective-C代码(还没有C++).安装和使用它是微不足道的:
从此页面下载最新版本.
从命令行cd
到项目目录.
执行scan-build -k -V xcodebuild
.
(还有一些额外的约束等,特别是你应该在其"调试"配置中分析一个项目 - 请参阅http://clang.llvm.org/StaticAnalysisUsage.html了解详细信息 - 但这或多或少归结为什么.)
然后,分析器会为您生成一组网页,显示可能的内存管理以及编译器无法检测到的其他基本问题.
这是微妙的,但很方便.如果您将自己作为委托传递给另一个对象,请在您之前重置该对象的委托dealloc
.
- (void)dealloc { self.someObject.delegate = NULL; self.someObject = NULL; // [super dealloc]; }
通过这样做,您可以确保不再发送任何委托方法.当你即将dealloc
消失并消失在以太中时,你要确保没有任何东西可以偶然发送给你更多的消息.记住self.someObject可以被另一个对象保留(它可以是单例或自动释放池或其他任何对象),直到你告诉它"停止向我发送消息!",它认为你刚刚被释放的对象是公平的游戏.
养成这种习惯可以避免许多奇怪的崩溃,这些都是很难调试的.
同样的原则也适用于Key Value Observation和NSNotifications.
编辑:
更具防御性的变化:
self.someObject.delegate = NULL;
成:
if (self.someObject.delegate == self) self.someObject.delegate = NULL;
@kendell
代替:
@interface MyClass (private) - (void) someMethod - (void) someOtherMethod @end
使用:
@interface MyClass () - (void) someMethod - (void) someOtherMethod @end
Objective-C 2.0中的新功能.
Apple的Objective-C 2.0参考中描述了类扩展.
"类扩展允许您在主类@interface块以外的位置为类声明其他必需的API"
所以它们是实际课程的一部分 - 而不是课堂上的(私人)课程.微妙但重要的区别.
由于您通常(1)无法直接控制其生命周期,因此自动释放的对象可能会持续相对较长的时间并不必要地增加应用程序的内存占用量.虽然在桌面上这可能没什么影响,但在更受限制的平台上,这可能是一个重要问题.因此,在所有平台上,特别是在更受约束的平台上,最好的做法是避免使用会导致自动释放对象的方法,而是鼓励您使用alloc/init模式.
因此,而不是:
aVariable = [AClass convenienceMethod];
如果能够,你应该使用:
aVariable = [[AClass alloc] init]; // do things with aVariable [aVariable release];
当您编写自己的方法返回一个新创建的对象时,您可以利用Cocoa的命名约定,通过在方法名称前添加"new"来向接收方标记必须释放它.
因此,而不是:
- (MyClass *)convenienceMethod { MyClass *instance = [[[self alloc] init] autorelease]; // configure instance return instance; }
你可以写:
- (MyClass *)newInstance { MyClass *instance = [[self alloc] init]; // configure instance return instance; }
由于方法名称以"new"开头,因此API的使用者知道他们负责释放所接收的对象(例如,参见NSObjectController的newObject
方法).
(1)您可以使用自己的本地自动释放池来控制.有关详细信息,请参阅自动释放池.
其中一些已被提及,但这是我能想到的最重要的事情:
遵循KVO命名规则.即使您现在不使用KVO,根据我的经验,它往往在将来仍然有用.如果您正在使用KVO或绑定,您需要知道事情按照预期的方式进行.这不仅包括访问器方法和实例变量,还包括多对多关系,验证,自动通知依赖键等.
将私有方法放在一个类别中.不仅是界面,还包括实现.私有和非私有方法之间在概念上有一定距离是很好的.我在.m文件中包含了所有内容.
将后台线程方法放在一个类别中.与上述相同.我发现当你在思考主线程中的内容以及什么不是时,保持明确的概念障碍是很好的.
使用#pragma mark [section]
.通常我按照自己的方法分组,每个子类都覆盖,以及任何信息或正式协议.这使得跳转到我正在寻找的东西要容易得多.在同一主题上,将类似的方法(如表视图的委托方法)组合在一起,不要只是将它们粘贴在任何地方.
使用_前缀私有方法和ivars.我喜欢它看起来的样子,当我意外地意味着财产时,我不太可能使用伊娃.
不要在init和dealloc中使用mutator方法/属性.我从来没有因为它而发生过任何不好的事情,但是如果你改变方法来做一些取决于对象状态的事情,我就能看到逻辑.
将IBOutlets放在属性中.我其实只是在这里阅读这篇文章,但我将开始这样做.无论任何记忆效益,它似乎风格更好(至少对我而言).
避免编写您不一定需要的代码.这真的涵盖了很多东西,比如在做遗嘱时制作ivars #define
,或者在每次需要数据时缓存数组而不是对它进行排序.关于这一点我可以说很多,但最重要的是在你需要之前不要编写代码,或者分析器告诉你.从长远来看,它使事情变得更容易维护.
完成你的开始.拥有大量半完成的错误代码是杀死项目死亡的最快方法.如果你需要一个很好的存根方法,只需通过放入NSLog( @"stub" )
内部来指示它,或者你想要跟踪事物.
写单元测试.您可以在Cocoa中测试很多其他框架中可能更难的东西.例如,使用UI代码,您通常可以验证事物是否应该连接,并相信它们在使用时会起作用.您可以轻松设置状态和调用委托方法来测试它们.
您也没有公开与受保护与私有方法的可见性妨碍为您的内部编写测试.
黄金法则:如果你alloc
那么你release
!
更新:除非您使用ARC
不要将Objective-C写成Java/C#/ C++ /等.
我曾经看到一个用来编写Java EE Web应用程序的团队尝试编写一个Cocoa桌面应用程序.好像它是一个Java EE Web应用程序.当他们真正需要的是Foo类和可能的Fooable协议时,有很多AbstractFooFactory和FooFactory以及IFoo和Foo飞来飞去.
确保您不这样做的一部分是真正理解语言的差异.例如,您不需要上面的抽象工厂和工厂类,因为Objective-C类方法与实例方法一样动态调度,并且可以在子类中重写.
确保为Debugging Magic页面添加书签.当你试图找到可可虫的来源时,这应该是你头撞墙的第一站.
例如,它将告诉您如何找到首先分配内存的方法,该方法稍后会导致崩溃(例如在应用程序终止期间).
尽量避免我现在决定称之为Newbiecategoryaholism.当Objective-C的新手发现类别时,他们经常会疯狂,为现有的每个类添加有用的小类别("什么?我可以添加一种方法将数字转换为罗马数字到NSNumber摇滚!").
不要这样做.
您的代码将更易于理解,并且可以通过在二十几个基础类之上的几十个小类别方法进行理解.
大多数时候,当你真的认为你需要一个类别方法来帮助简化一些代码时,你会发现你永远不会重复使用这个方法.
还有其他的危险,除非你的命名空间是你的类别方法(除了完全疯狂的ddribin之外的人是谁?),你的地址空间中运行的Apple,插件或其他东西也有可能定义相同的类别具有相同名称且副作用略有不同的方法....
好.既然你已被警告,请忽略"不要做这个部分".但要行使极端的克制.
对字符串进行排序以呈现给用户时,不应使用简单compare:
方法.相反,您应该始终使用本地化比较方法,如localizedCompare:
或localizedCaseInsensitiveCompare:
.
有关更多详细信息,请参阅搜索,比较和排序字符串.
抵制世界的子类化.在Cocoa中,通过委托和使用底层运行时来完成很多工作,在其他框架中通过子类化完成.
例如,在Java中,您经常使用匿名*Listener
子类的实例,而在.NET中,您EventArgs
经常使用子类.在Cocoa中,您不会这样做 - 而是使用目标操作.
您通常应该为所有属性使用Objective-C 2.0声明的属性功能.如果它们不公开,请将它们添加到类扩展中.使用声明的属性可以立即清除内存管理语义,并使您更容易检查dealloc方法 - 如果将属性声明组合在一起,则可以快速扫描它们并与dealloc方法的实现进行比较.
在不将属性标记为"非原子"之前,您应该认真思考.正如Objective C Programming Language Guide所指出的,默认情况下属性是原子的,并且会产生相当大的开销.而且,简单地使所有属性成为原子并不会使您的应用程序成为线程安全的.当然,还要注意,如果你没有指定'nonatomic'并实现你自己的访问器方法(而不是合成它们),你必须以原子方式实现它们.
正如本问题所述,消息nil
在Objective-C中有效.虽然这通常是一个优势 - 导致更清晰,更自然的代码 - 如果nil
你在不期望的时候得到一个价值,这个功能偶尔会导致特殊的,难以追踪的错误.
使用NSAssert和朋友.我一直使用nil作为有效对象...尤其是将消息发送到nil在Obj-C中完全有效.但是,如果我真的想确定变量的状态,我使用NSAssert和NSParameterAssert,这有助于轻松追踪问题.
简单但经常被遗忘的一个.根据规格:
通常,具有相同选择器(同名)的不同类中的方法也必须共享相同的返回和参数类型.编译器强制执行此约束以允许动态绑定.
在这种情况下,所有相同的命名选择器,即使在不同的类中,也将被视为具有相同的返回/参数类型.这是一个简单的例子.
@interface FooInt:NSObject{} -(int) print; @end @implementation FooInt -(int) print{ return 5; } @end @interface FooFloat:NSObject{} -(float) print; @end @implementation FooFloat -(float) print{ return 3.3; } @end int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id f1=[[FooFloat alloc]init]; //prints 0, runtime considers [f1 print] to return int, as f1's type is "id" and FooInt precedes FooBar NSLog(@"%f",[f1 print]); FooFloat* f2=[[FooFloat alloc]init]; //prints 3.3 expectedly as the static type is FooFloat NSLog(@"%f",[f2 print]); [f1 release]; [f2 release] [pool drain]; return 0; }
如果您使用的是Leopard(Mac OS X 10.5)或更高版本,则可以使用Instruments应用程序查找和跟踪内存泄漏.在Xcode中构建程序后,选择Run> Start with Performance Tool> Leaks.
即使您的应用程序没有显示任何泄漏,您可能会将对象保留太长时间.在Instruments中,您可以使用ObjectAlloc仪器.在Instruments文档中选择ObjectAlloc仪器,然后选择View> Detail(它旁边应该有一个复选标记),调出仪器的详细信息(如果尚未显示).在ObjectAlloc详细信息中的"Allocation Lifespan"下,确保选择"Created&Still Living"旁边的单选按钮.
现在,无论何时停止记录应用程序,选择ObjectAlloc工具都会在"#Net"列中显示应用程序中每个仍然存活的对象的引用数量.确保您不仅要查看自己的类,还要查看NIB文件的顶级对象的类.例如,如果屏幕上没有窗口,并且您看到对仍然存在的NSWindow的引用,则可能未在代码中发布它.
在dealloc清理.
这是最容易忘记的事情之一 - 尤其是 当编码为150英里每小时.始终,始终,始终清理dealloc中的属性/成员变量.
我喜欢使用Objc 2属性 - 使用新的点符号 - 所以这使得清理无痛.通常很简单:
- (void)dealloc { self.someAttribute = NULL; [super dealloc]; }
这将照顾你的释放并将属性设置为NULL(我考虑防御性编程 - 如果dealloc中的另一个方法再次访问成员变量 - 很少但可能发生).
在10.5中打开GC时,不再需要这么多 - 但您可能仍需要清理您创建的其他资源,您可以在finalize方法中执行此操作.
所有这些评论都很棒,但我真的很惊讶没人提到谷歌的Objective-C风格指南已经发布了一段时间.我认为他们做得非常彻底.
此外,半相关主题(有更多回复的空间!):
你希望在2年前知道的那些小Xcode提示和技巧是什么?.
不要忘记NSWindowController和NSViewController将释放他们管理的NIB文件的顶级对象.
如果手动加载NIB文件,则负责在完成NIB的顶级对象后释放它们.
一个相当明显的初学者使用:利用Xcode的自动缩进功能为您的代码.即使您从其他来源复制/粘贴,一旦粘贴了代码,您可以选择整个代码块,右键单击它,然后选择重新缩进该块中的所有内容的选项.
Xcode实际上将解析该部分并基于括号,循环等缩进它.它比每行的空格键或Tab键更有效.
我知道在第一次进入Cocoa编程时我忽略了这一点.
确保您了解有关NIB文件的内存管理职责.您负责释放加载的任何NIB文件中的顶级对象.阅读有关该主题的Apple文档.
打开所有GCC警告,然后关闭那些经常由Apple标头引起的警告,以减少噪音.
还经常运行Clang静态分析; 您可以通过"运行静态分析器"构建设置为所有构建启用它.
编写单元测试并在每次构建时运行它们.
变量和属性
1 /保持标头清洁,隐藏实现
不要在标头中包含实例变量.私有变量作为属性放入类继续.公共变量在标头中声明为公共属性.如果只应读取它,则将其声明为readonly并在类连续中将其覆盖为readwrite.基本上我根本不使用变量,只使用属性.
2 /为您的属性提供非默认变量名称,例如:
@synthesize property = property_;
原因1:你会发现忘记"自我"造成的错误.在分配财产时.原因2:从我的实验中,仪器中的泄漏分析仪在检测具有默认名称的泄漏属性时存在问题.
3 /切勿直接在属性上使用保留或释放(或仅在非常特殊的情况下使用).在你的dealloc中只给他们一个零.保留属性意味着自己处理保留/释放.你永远不知道setter是不是,例如,添加或删除观察者.您应该仅在其setter和getter中直接使用该变量.
查看
1 /如果可以,将每个视图定义放入xib(例外通常是动态内容和图层设置).它节省了时间(比编写代码更容易),它易于更改并保持代码清洁.
2 /不要尝试通过减少视图数量来优化视图.不要仅仅因为想要在其中添加子视图而在代码中创建UIImageView而不是xib.使用UIImageView作为背景.视图框架可以毫无问题地处理数百个视图.
3/IBOutlets不必总是保留(或强).请注意,大多数IBOutlet都是视图层次结构的一部分,因此会隐式保留.
4 /在viewDidUnload中释放所有IBOutlets
5 /从dealloc方法调用viewDidUnload.它没有被暗中调用.
记忆
1 /创建对象时自动释放对象.将释放调用移动到一个if-else分支或返回语句之后会导致许多错误.释放而不是自动释放只应在特殊情况下使用 - 例如,当您等待runloop并且您不希望过早地自动释放对象时.
2 /即使您使用的是Authomatic Reference Counting,也必须完全理解retain-release方法的工作原理.手动使用retain-release并不比ARC复杂,在这两种情况下你都需要关于泄漏和保留周期的事情.考虑在大项目或复杂的对象层次结构上手动使用retain-release.
评论
1 /使您的代码自动记录.每个变量名称和方法名称都应该告诉它正在做什么.如果代码编写正确(您需要进行大量练习),则不需要任何代码注释(与文档注释不同).算法可能很复杂,但代码应该总是很简单.
2 /有时候,你需要发表评论.通常用于描述非明显的代码行为或hack.如果您认为必须撰写评论,请首先尝试将代码重写为更简单且无需注释.
缩进
1 /不要过多增加压痕.大多数方法代码都应该在方法级别缩进.嵌套块(如果,等等)会降低可读性.如果您有三个嵌套块,则应尝试将内部块放入单独的方法中.应该永远不要使用四个或更多嵌套块.如果你的大多数方法代码都在if中,则取消if条件,例如:
if (self) { //... long initialization code ... } return self;
if (!self) { return nil; } //... long initialization code ... return self;
理解C代码,主要是C结构
请注意,Obj-C只是C语言上的轻型OOP层.您应该了解C中的基本代码结构(枚举,结构,数组,指针等).例:
view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height + 20);
是相同的:
CGRect frame = view.frame; frame.size.height += 20; view.frame = frame;
还有很多
Mantain您自己的编码标准文档并经常更新.尝试从你的错误中学习.了解创建错误的原因,并尝试使用编码标准来避免错误.
我们的编码标准目前大约有20页,包括Java编码标准,Google Obj-C/C++标准和我们自己的添加.记录您的代码,在正确的位置使用标准标准缩进,空格和空白行等.
更实用.
Objective-C是面向对象的语言,但Cocoa框架功能风格意识,并且在很多情况下都是设计的功能风格.
存在可变性的分离.使用不可变类作为主要类,将可变对象用作辅助类.例如,主要使用NSArray,并仅在需要时使用NSMutableArray.
有纯粹的功能.没有那么多,购买许多框架API的设计就像纯函数一样.看看诸如CGRectMake()
或之类的功能CGAffineTransformMake()
.显然指针形式看起来更有效.然而,使用指针的间接参数不能提供无副作用.尽可能地设计结构.甚至分离状态对象.使用-copy
而不是-retain
将值传递给其他对象.因为共享状态可以默默地影响其他对象中的值突变.所以不能无副作用.如果您有来自object的外部值,请复制它.因此,尽可能减少设计共享状态也很重要.
但是也不要害怕使用不纯的功能.
有懒惰的评价.看到类似-[UIViewController view]
财产的东西.创建对象时不会创建视图.它将view
在第一次调用者读取属性时创建.UIImage
在实际绘制之前不会加载.这种设计有很多实现方式.这种设计对资源管理非常有帮助,但如果你不了解懒惰评估的概念,就不容易理解它们的行为.
关闭.尽可能使用C块.这将大大简化您的生活.但在使用它之前,请再次阅读有关块内存管理的内容.
有半自动GC.NSAutoreleasePool.使用-autorelease
主要.-retain/-release
在您真正需要时使用手动辅助.(例如:内存优化,显式资源删除)
我看到Apple提供的示例将App委托视为全局数据存储,是各种数据管理器.那是错误的.创建一个单例并可以在App委托中实例化它,但不要使用App委托作为应用程序级事件处理.我衷心地在这篇博文中提出建议.这个帖子让我失望了.