编写Objective-C和Cocoa时使用的最佳实践是什么?
我知道HIG(非常方便!),但是在编写Objective-C时,尤其是在使用Cocoa(或者CocoaTouch)时,会使用哪些编程实践。
解决方案
黄金法则:如果我们分配
,那么我们释放
!
更新:除非我们使用ARC
我已经开始做一些我认为不标准的事情:
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"。默认情况下,它们是"原子的",并且在综合后,将创建信号量代码以防止多线程问题。我们中的99%可能不需要担心这一点,并且将代码设置为非原子时,代码的肿程度会大大降低,内存使用效率也会更高。
6)SQLite可以是一种非常非常快的缓存大型数据集的方法。例如,地图应用程序可以将其图块缓存到SQLite文件中。最昂贵的部分是磁盘I / O。通过在大块之间发送BEGIN;
和COMMIT;
来避免许多小的写操作。例如,我们使用2秒计时器来重置每个新提交。当它到期时,我们发送COMMIT。 ,这会使所有写入工作都集中在一个很大的块中。 SQLite将事务数据存储到磁盘,并且此操作的开始/结束包装避免了创建多个事务文件,而是将所有事务分组到一个文件中。
另外,如果SQL在主线程上,它将阻止GUI。如果查询时间很长,最好将查询存储为静态对象,然后在单独的线程上运行SQL。确保在@@ synchronize(){}`块中包装所有会修改数据库以查询字符串的内容。对于短查询,只需将内容留在主线程上即可,以方便使用。
这里有更多的SQLite优化技巧,尽管该文档看起来过时了,但许多观点可能仍然不错。
http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html
@肯德尔
代替:
@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"
因此,它们是实际类的一部分,而不是除类之外的(私有)类别。细微但重要的区别。
在dealloc中清理。
这是最容易忘记的事情之一。当以150mph的速度编码时。总是,总是,总是在dealloc中清理属性/成员变量。
我喜欢将Objc 2属性与新的点符号一起使用,因此这使清理工作变得轻松自如。通常很简单:
- (void)dealloc { self.someAttribute = NULL; [super dealloc]; }
这将为我们解决发行问题,并将属性设置为NULL(如果在dealloc中进一步下降的另一种方法再次访问成员变量的情况很少发生,则我认为这是防御性编程,但可能会发生)。
在10.5中打开GC后,就不需要太多了,但是我们可能仍需要清理创建的其他资源,可以改为使用finalize方法。
这是一个微妙的但方便的一。如果要将自己作为委托传递给另一个对象,请在" dealloc"之前重置该对象的委托。
- (void)dealloc { self.someObject.delegate = NULL; self.someObject = NULL; // [super dealloc]; }
这样,我们可以确保不再发送委托方法。当我们将要"释放"并消失在以太坊中时,我们要确保没有任何东西可以意外发送给我们更多的消息。记住self.someObject可能被另一个对象(可能是单例对象或者在自动释放池中或者其他对象)保留,直到我们告诉它"停止向我发送消息!",它才认为我们即将被释放的对象是公平的游戏。
养成这种习惯将使我们免于调试时很痛苦的怪异崩溃。
相同的原则适用于键值观察和NSNotifications。
编辑:
更具防御性的是,更改:
self.someObject.delegate = NULL;
进入:
if (self.someObject.delegate == self) self.someObject.delegate = NULL;
尽量避免我现在决定称之为"新手类别狂热症"的事情。当Objective-C的新手发现类别时,他们经常会大吃一惊,向现有的每个类别添加有用的小类别("什么?我可以添加一种方法,将数字转换为罗马数字,然后再转换为NSNumber!")。
不要这样
在几十个基础类的基础上增加了数十种小类别方法,代码将更易于移植,更易于理解。
大多数时候,当我们确实认为我们需要一种类别方法来帮助简化某些代码时,我们会发现我们永远都不会再使用该方法。
还有其他危险,除非我们为类别方法命名(除非是完全疯狂的ddribin谁?),否则Apple或者插件或者在地址空间中运行的其他东西也有可能定义相同的类别具有相同名称的方法,但副作用稍有不同。
好的。现在我们已经得到警告,请忽略"请勿执行此部分"。但是要保持克制。
此外,还有半相关的主题(还有更多的回应空间!):
我们希望大约2年前知道的Xcode小技巧和窍门是什么?
编写单元测试。我们可以在Cocoa中测试很多在其他框架中可能很难完成的事情。例如,使用UI代码,我们通常可以验证事物是否按其应有的方式连接,并相信它们在使用时会起作用。我们可以轻松设置状态并调用委托方法以对其进行测试。
我们也没有公共方法,保护方法和私有方法的可见性,从而无法编写内部测试。
抵抗继承世界。在Cocoa中,很多事情是通过委派和使用基础运行时完成的,而在其他框架中则是通过子类完成的。
例如,在Java中,我们经常使用匿名* Listener
子类的实例,而在.NET中,我们经常使用EventArgs
子类。在可可中,我们无需执行target-action。
不要像编写Java / C#/ C ++ / etc那样编写Objective-C。
我曾经见过一个用来编写Java EE Web应用程序的团队尝试编写Cocoa桌面应用程序。好像它是一个Java EE Web应用程序。当他们真正需要的只是一个Foo类以及可能的Fooable协议时,有很多AbstractFooFactory和FooFactory以及IFoo和Foo到处飞。
确保我们不这样做的一部分,是真正了解语言的差异。例如,我们不需要上面的抽象工厂类和工厂类,因为Objective-C类方法的调度与实例方法一样动态,并且可以在子类中重写。
使用标准的可可命名和格式约定以及术语,而不要使用其他环境中常用的内容。有很多Cocoa开发人员,当他们中的另一个开始使用代码时,如果外观和感觉与其他Cocoa代码相似,则将更容易上手。
做什么和不做什么的示例:
- 不要在对象的接口中声明
id m_something;
并将其称为成员变量或者字段;使用something
或者_something
作为其名称,并将其称为实例变量。 - 不要将getter命名为-getSomething。正确的可可名称只是"某物"。
- 不要命名二传手
-something:
;它应该是-setSomething: - 方法名称中穿插了参数,并包括冒号。它是
-[NSObject performSelector:withObject:]
,而不是NSObject :: performSelector
。 - 在方法名称,参数,变量,类名称等中使用大写字母(CamelCase),而不要使用下划线(下划线)。
- 类名以大写字母开头,变量和方法名以小写字母开头。
无论我们做什么,都不要使用Win16 / Win32风格的匈牙利表示法。甚至Microsoft都放弃了向.NET平台的迁移。
我知道我第一次进入Cocoa编程时就忽略了这一点。
确保我们了解有关NIB文件的内存管理职责。我们有责任在我们加载的所有NIB文件中释放顶级对象。阅读有关该主题的Apple文档。
确保将"调试魔术"页面添加为书签。这是我们在试图寻找可可小虫的根源时将头撞在墙上的第一站。
例如,它将告诉我们如何在首先分配内存的位置找到该方法,该内存后来导致崩溃(例如在应用程序终止期间)。
如果我们使用的是Leopard(Mac OS X 10.5)或者更高版本,则可以使用Instruments应用程序查找并跟踪内存泄漏。在Xcode中构建程序后,选择"运行">"从性能工具开始">"泄漏"。
即使应用程序未显示任何泄漏,我们也可能将对象放置的时间过长。在仪器中,我们可以为此使用ObjectAlloc仪器。在"仪器"文档中选择ObjectAlloc仪器,然后通过选择"视图">"详细信息"(它旁边应有一个复选标记)来调出该仪器的详细信息(如果尚未显示)。在ObjectAlloc详细信息的"分配寿命"下,确保选择"已创建并仍然存在"旁边的单选按钮。
现在,无论何时停止记录应用程序,选择ObjectAlloc工具都将在"网络"列中显示对应用程序中每个仍然存在的对象有多少引用。确保不仅查看自己的类,而且查看NIB文件的顶级对象的类。例如,如果屏幕上没有窗口,并且看到了对仍然有效的NSWindow的引用,则可能尚未在代码中释放它。
不要忘记NSWindowController和NSViewController将释放它们管理的NIB文件的顶级对象。
如果我们手动加载NIB文件,则在处理完NIB的顶级对象后,我们有责任释放该NIB的顶级对象。
IBOutlets
从历史上看,网点的内存管理很差。
当前的最佳实践是将网点声明为属性:
@interface MyClass :NSObject { NSTextField *textField; } @property (nonatomic, retain) IBOutlet NSTextField *textField; @end
使用属性可以使内存管理语义清晰明了;如果我们使用实例变量综合,它还提供了一致的模式。
声明的属性
通常,我们应该对所有属性都使用" Objective-C 2.0声明的属性"功能。如果它们不是公开的,则将它们添加到类扩展中。使用声明的属性可以使内存管理语义立即清晰,并使我们更容易检查dealloc方法-如果将属性声明组合在一起,则可以快速对其进行扫描,并与dealloc方法的实现进行比较。
在不将属性标记为"非原子"之前,我们应该认真考虑。如《 Objective C编程语言指南》所述,默认情况下,属性是原子的,并且会产生相当大的开销。而且,仅使所有属性原子化并不能使应用程序具有线程安全性。当然,还请注意,如果我们不指定" nonatomic"并实现自己的访问器方法(而不是对它们进行综合),则必须以原子方式实现它们。
使用LLVM / Clang静态分析器
注意:在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-但这差不多。归结为。)
然后,分析器为我们生成了一组网页,这些网页显示了可能的内存管理以及编译器无法检测到的其他基本问题。
不要使用未知字符串作为格式字符串
当方法或者函数采用格式字符串参数时,应确保对格式字符串的内容具有控制权。
例如,在记录字符串时,很容易将字符串变量作为唯一参数传递给NSLog:
NSString *aString = // get a string from somewhere; NSLog(aString);
问题在于字符串可能包含被解释为格式字符串的字符。这可能导致错误的输出,崩溃和安全问题。相反,我们应该将字符串变量替换为格式字符串:
NSLog(@"%@", aString);
根据用户需要对字符串进行排序
在对要呈现给用户的字符串进行排序时,不应使用简单的" compare:"方法。相反,我们应该始终使用本地化的比较方法,例如" localizedCompare:"或者" localizedCaseInsensitiveCompare:"。
有关更多详细信息,请参见搜索,比较和排序字符串。
避免自动释放
由于我们通常(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)我们可以使用自己的本地自动释放池来控制。有关更多信息,请参见自动释放池。
考虑零值
正如该问题所指出的,在Objective-C中,发送到nil的消息是有效的。虽然这通常是一个优势-导致代码更清晰,更自然-但是,如果我们在没有期望的时候得到" nil"值,则该功能有时会导致特殊且难以跟踪的错误。
其中一些已经被提及,但是这是我想到的:
- 遵循KVO命名规则。即使我们现在不使用KVO,以我的经验,通常它在将来仍然是有益的。而且,如果我们使用的是KVO或者绑定,则需要知道一切按预期的方式进行。这不仅涉及访问器方法和实例变量,还涉及许多关系,验证,自动通知依赖项等。
- 将私有方法放在一个类别中。不只是接口,还有实现。在概念上在私有方法和非私有方法之间保持一定距离是很好的。我将所有内容都包含在我的.m文件中。
- 将后台线程方法放在一个类别中。与上述相同。我发现在考虑主线程上的内容和未线程上的内容时,最好保持明确的概念障碍。
- 使用#pragma标记[section]。通常,我按自己的方法,每个子类的覆盖以及任何信息或者正式协议进行分组。这使跳转到我要寻找的内容变得容易得多。在同一主题上,将相似的方法(如表视图的委托方法)组合在一起,而不只是将它们粘在任何地方。
- 用_前缀私有方法和ivars。我喜欢它的外观,并且当我偶然地表示财产时,我不太可能使用ivar。
- 不要在init和dealloc中使用mutator方法/属性。因此,我从来没有发生过任何不好的事情,但是如果我们更改方法以执行依赖于对象状态的某些事情,那么我可以看到逻辑。
- 将IBOutlets放在属性中。我实际上只是在这里读过这篇文章,但是我将开始做。不管有什么内存方面的好处,从风格上看,它似乎更好(至少对我而言)。
- 避免编写不需要的代码。这确实涵盖了很多内容,例如在
#define
将执行操作时制作ivars,或者缓存数组而不是在每次需要数据时对其进行排序。关于这一点,我有很多话要说,但是最重要的是不要编写代码,除非我们需要它,否则探查器会告诉我们。从长远来看,它使事情变得更容易维护。 - 完成开始。包含大量未完成的错误代码是杀死项目失败的最快方法。如果我们需要一个很好的存根方法,只需在其中放入
NSLog(@" stub")
即可表明该方法,或者我们想跟踪情况。
所有这些评论都很棒,但是我真的很惊讶没有人提到不久前发布的Google的《 Objective-C风格指南》。我认为他们做得非常透彻。
使用NSAssert和朋友。
我一直都将nil用作有效对象...尤其是在Obj-C中向nil发送消息是完全有效的。
但是,如果我真的想确定变量的状态,可以使用NSAssert和NSParameterAssert,这有助于轻松地查找问题。
我看到的Apple提供的示例将App委托视为全局数据存储,即各种数据管理器。这是错误的想法。创建一个单例并可以在App委托中实例化它,但是除了使用应用程序级事件处理外,不要使用App委托。我衷心地赞同此博客文章中的建议。这个线程使我失望。
打开所有GCC警告,然后关闭由Apple标头引起的常规警告,以减少噪音。
也要经常运行Clang静态分析;我们可以通过"运行静态分析器"构建设置为所有构建启用它。
编写单元测试,并在每次构建时运行它们。
对于初学者来说,一个相当明显的用途是:对代码使用Xcode的自动缩进功能。即使我们是从另一个源复制/粘贴,粘贴代码后,也可以选择整个代码块,右键单击它,然后选择重新缩进该代码块中所有内容的选项。
Xcode实际上将解析该部分并根据方括号,循环等对其进行缩进。这比敲击每行的空格键或者Tab键要高效得多。
简单但经常被遗忘的一个。根据规格:
In general, methods in different classes that have the same selector (the same name) must also share the same return and argument types. This constraint is imposed by the compiler to allow dynamic binding.
在这种情况下,即使是在不同的类中,所有相同的命名选择器也将被视为具有相同的返回/参数类型。这是一个简单的例子。
@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; }