编写Objective-C和Cocoa时使用的最佳实践是什么?
我知道HIG (这非常方便!),但是在编写Objective-C时,使用Cocoa(或CocoaTouch)时,使用哪些编程实践。
有几件事我已经开始做,我不认为是标准的:
1)随着属性的出现,我不再使用“_”作为“private”类variables的前缀。 毕竟,如果一个variables可以被其他类访问不应该有一个属性呢? 我总是不喜欢“_”前缀代码丑陋,现在我可以离开它。
2)说到私有的东西,我更喜欢将.m文件中的私有方法定义放在类扩展中,如下所示:
#import "MyClass.h" @interface MyClass () - (void) someMethod; - (void) someOtherMethod; @end @implementation MyClass
为什么外面的东西弄乱了.h文件不应该在意? empty()适用于.m文件中的专用类别,如果不执行已声明的方法,将发出编译警告。
3)我已经把dealloc放在.m文件的顶部,就在@synthesize指令的下面。 你不应该在你想在课堂上想到的东西的顶部列出什么? 在像iPhone这样的环境中尤其如此。
3.5)在表格单元格中,使每个元素(包括单元格本身)的performance都不透明。 这意味着在一切中设置适当的背景颜色。
3.6)使用NSURLConnection时,通常可能需要实现委托方法:
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { return nil; }
我发现大部分的networking调用都是非常单一的,而且它比你想要响应caching的规则更加例外,特别是对于Web服务调用。 如图所示执行方法会禁用响应的caching。
另外感兴趣的是,一些来自Joseph Mattiello的iPhone特定技巧(从iPhone邮件列表中收到)。 还有更多,但这些是我认为最普遍的用途(注意,现在已经稍微修改了几个小部分,以包括在回答中提供的细节):
4)如果你必须使用双精度,比如使用CoreLocation。 确保你在'f'结束你的常量,让gcc把它们存储为浮点数。
float val = someFloat * 2.2f;
当一些someFloat
实际上可能是双someFloat
时,这是非常重要的,因为你在存储的'val'中失去了精确度,所以你不需要混合模式math。 尽pipeiPhone上的硬件支持浮点数,但与单精度相比,双精度algorithm可能还需要更多的时间。 参考文献:
- iPhone上的双重浮动
- iPhone / iPad的双精度math
在较旧的手机上,假devise算以相同的速度运行,但是在寄存器中可以有更多的单精度分量而不是双精度分量,所以对于许多计算来说,单精度将会变得更快。
5)将你的属性设置nonatomic
。 它们在默认情况下是atomic
的,在合成时,将会创build信号量代码以防止multithreading问题。 99%的人可能不需要担心这个问题,而且当设置为非primefaces时,代码会更less膨胀和更高的内存效率。
6)SQLite可以是一个非常快速的方式来caching大型数据集。 例如,一个地图应用程序可以将其磁贴caching到SQLite文件中。 最昂贵的部分是磁盘I / O。 通过发送BEGIN;
避免许多小写操作BEGIN;
和COMMIT;
在大块之间。 我们使用一个2秒钟的计时器来重置每一个新的提交。 当到期时,我们发送COMMIT; ,这会导致所有的写入操作都在一个大块中。 SQLite将事务数据存储到磁盘,并执行此Begin / End包装避免创build许多事务文件,将所有事务分组到一个文件中。
另外,如果SQL在主线程上,则会阻塞你的GUI。 如果有很长的查询,最好将查询存储为静态对象,然后在单独的线程上运行SQL。 确保在@synchronize() {}
块中包装修改查询string数据库的所有内容。 对于简短的查询,只需在主线程上留下一些东西,以方便使用。
更多的SQLite优化技巧在这里,虽然文件显得过时了许多点可能仍然不错,
http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html
不要使用未知的string作为格式string
当方法或函数采用格式string参数时,应确保您可以控制格式string的内容。
例如,在loggingstring时,将stringvariables作为唯一parameter passing给NSLog
是很诱人的:
NSString *aString = // get a string from somewhere; NSLog(aString);
与此问题是该string可能包含解释为格式string的字符。 这可能会导致错误的输出,崩溃和安全问题。 相反,您应该将stringvariablesreplace为格式string:
NSLog(@"%@", aString);
使用标准的cocoa命名和格式约定和术语,而不是从其他环境中习惯的任何东西。 那里有很多Cocoa开发者,当他们中的另外一个开始使用你的代码时,如果它和其他Cocoa代码看起来和感觉相似,它会更加平易近人。
做什么和不做什么的例子:
- 不要声明
id m_something;
在一个对象的接口中,并将其称为成员variables或字段 ; 使用something
或_something
作为它的名字并将其称为实例variables 。 - 不要命名getter
-getSomething
; 正确的cocoa名称只是一些-something
。 - 不要命名setter –
-something:
它应该是-setSomething:
- 方法名称中散布着参数并包含冒号; 它是
-[NSObject performSelector:withObject:]
,而不是NSObject::performSelector
。 - 在方法名,参数,variables,类名等中使用内部大小写(CamelCase),而不是下划线(下划线)。
- 类名以小写字母,variables和方法名称开头。
无论你做什么, 都不要使用Win16 / Win32风格的匈牙利符号。 即使微软也放弃了.NET平台。
IBOutlets
历史上,网点的内存pipe理一直很差。 目前的最佳做法是宣布网点为属性:
@interface MyClass :NSObject { NSTextField *textField; } @property (nonatomic, retain) IBOutlet NSTextField *textField; @end
使用属性使内存pipe理语义清晰; 它也提供了一致的模式,如果你使用实例variables合成。
使用LLVM / Clang静态分析器
注:在Xcode 4下,现在已经内置在IDE中。
您可以使用Clang静态分析器 – 不出所料 – 在Mac OS X 10.5上分析您的C和Objective-C代码(没有C ++)。 安装和使用是微不足道的:
- 从此页面下载最新版本。
- 从命令行,
cd
到您的项目目录。 - 执行
scan-build -k -V xcodebuild
。
(还有一些额外的限制等,特别是你应该在其“debugging”configuration中分析一个项目 – 详情请参阅http://clang.llvm.org/StaticAnalysisUsage.html – 但是这或多或less它归结为什么。)
然后,分析器为您生成一组网页,显示可能的内存pipe理以及编译器无法检测到的其他基本问题。
这是微妙的,但方便的一个。 如果您将自己作为委托传递给另一个对象,请在您dealloc
之前重置该对象的委托。
- (void)dealloc { self.someObject.delegate = NULL; self.someObject = NULL; // [super dealloc]; }
通过这样做,您可以确保不再发送委托方法。 当你即将释放并消失在以太,你想确保没有任何东西可以发送给你任何意外的消息。 记住self.someObject可以被另一个对象(它可以是一个单身或在autorelease池或任何其他)保留,直到你告诉它“停止发送消息!”,它认为你只是即将被处理的对象是公平的游戏。
进入这个习惯将会使你免于很多奇怪的崩溃,这是很难debugging的。
同样的原理也适用于关键值观察和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中的新增function
Apple的Objective-C 2.0参考中描述了类扩展。
“类扩展允许您在除了主类@interface块之外的位置声明其他所需的API”
所以他们是实际class级的一部分 – 而不是class级的(私人)类别。 微妙但重要的区别。
避免自动释放
由于您通常(1)不能直接控制其使用期限,因此自动释放的对象可能会持续相当长的时间,并且不必要地增加应用程序的内存占用量。 虽然在桌面上这可能没有多大意义,但在更多受限制的平台上,这可能是一个重要的问题。 因此,在所有平台上,特别是在更受限制的平台上,避免使用会导致自动释放对象的方法被认为是最佳实践,而鼓励使用alloc / init模式。
因此,而不是:
aVariable = [AClass convenienceMethod];
在哪里,你应该使用:
aVariable = [[AClass alloc] init]; // do things with aVariable [aVariable release];
当你编写你自己的方法来返回一个新创build的对象时,你可以利用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)您可以使用自己的本地autorelease池进行控制。 有关更多信息,请参阅自动释放池 。
其中一些已经被提及,但是这是我能想到的:
- 遵循KVO命名规则。 即使你现在不使用KVO,根据我的经验,往往还是有益的。 如果你使用KVO或绑定,你需要知道事情正在按照他们应该的方式进行。 这不仅包括访问方法和实例variables,还包括一对多关系,validation,自动通知相关键等等。
- 将私有方法放在一个类别中。 不仅仅是接口,还有实现。 在私人和非私人方法的概念上有一定的距离是很好的。 我将所有内容都包含在我的.m文件中。
- 将后台线程方法放在一个类别中。 同上。 我发现当你想到主线上和主线上有什么时,保持清晰的概念屏障是很好的。
- 使用
#pragma mark [section]
。 通常我按自己的方法,每个子类的覆盖,以及任何信息或正式的协议进行分组。 这使得我更容易跳到我正在寻找的东西。 在同一个主题上,将类似的方法(如表视图的委托方法)组合在一起,不要把它们粘在任何地方。 - 使用_前缀私人方法和ivars。 我喜欢它的外观,当我的意思是财产意外时,我不太可能使用伊娃。
- 不要在init&dealloc中使用mutator方法/属性。 我从来没有发生过任何不好的事情,但是我可以看到逻辑,如果你改变方法来做一些事情,取决于你的对象的状态。
- 把IBOutlets放在属性中。 其实我只是在这里看过这个,但是我要开始这样做了。 无论有什么记忆力的好处,在风格上似乎都更好(至less对我来说)。
- 避免编写你不是绝对需要的代码。 这实际上涵盖了很多东西,比如在
#define
执行时创buildivars,或者caching数组,而不是在每次需要数据时对其进行sorting。 关于这一点我有很多可以说的,但是底线是,直到你需要时才写代码,或者剖析器告诉你。 从长远来看,这使事情变得容易得多。 - 完成你开始。 有很多的一半完成,错误的代码是杀死一个项目死亡的最快方法。 如果你需要一个很好的存根方法,只需要把
NSLog( @"stub" )
放在里面,或者你想跟踪事情。
编写unit testing。 你可以在Cocoa中testing很多东西,在其他框架中可能会比较困难。 例如,使用UI代码,通常可以validation事物是否应该连接,并相信它们在使用时会起作用。 你可以设置状态并轻松调用委托方法来testing它们。
你也没有公共vs保护与私人方法可见性,为你的内部写作testing的方式。
黄金规则:如果你alloc
那么你release
!
更新:除非你使用ARC
不要把Objective-C编写成Java / C#/ C ++ /等。
我曾经看到一个用于编写Java EE Web应用程序的团队尝试编写一个Cocoa桌面应用程序。 就好像它是一个Java EE Web应用程序一样。 有很多的AbstractFooFactory和FooFactory以及IFoo和Foo,当他们真正需要的是Foo类和可能的Fooable协议时,
确保你不这样做的一部分是真正理解语言的差异。 例如,您不需要上面的抽象工厂和工厂类,因为Objective-C类方法与实例方法一样dynamic调度,并且可以在子类中重写。
确保您为Debugging Magic页面添加书签。 这应该是你的第一站,当你试图find一个cocoa错误的来源时,撞在墙上的头。
例如,它会告诉你如何find你最先分配内存的方法,以后会导致崩溃(比如在程序终止的时候)。
按用户需要对string进行sorting
当对string进行sorting以呈现给用户时,不应该使用简单的compare:
方法。 相反,您应该始终使用本地化的比较方法,例如localizedCompare:
或localizedCaseInsensitiveCompare:
有关更多详细信息,请参阅search,比较和sortingstring 。
尽量避免我现在决定称为Newbiecategoryaholism。 当Objective-C的新手发现类别时,他们经常会疯狂地为每个类添加有用的小类( “什么?我可以添加一个方法来将一个数字转换为罗马数字到NSNumber rock!” )。
不要这样做。
你的代码将会更便于使用,并且更容易理解,因为在二十几个基础类的基础上,有很多种类的方法。
大多数时候,当你真的认为你需要一个类别的方法来帮助简化一些代码,你会发现你永远不会重复使用该方法。
除此之外,还有其他的危险,除非你的命名空间是你的分类方法(除了完全疯狂的ddribin是谁?),你的地址空间中运行的苹果,插件或者别的东西也有可能定义相同的类别方法同名,副作用略有不同….
好。 现在你已经被警告了,不要理睬“不要做这个部分”。 但行使极端克制。
抵制分类世界。 在Cocoa中,很多是通过委托和使用底层的运行时来完成的,在其他框架中是通过子类来完成的。
例如,在Java中,您可以使用匿名*Listener
子类的实例,而在.NET中,您可以使用很多EventArgs
子类。 在Cocoa中,你也不这样做 – 使用target-action。
声明的属性
您通常应该为您的所有属性使用Objective-C 2.0声明的属性function。 如果他们不公开,请将它们添加到类扩展中。 使用声明的属性可以立即清除内存pipe理语义,并使您更容易检查dealloc方法 – 如果将属性声明组合在一起,则可以快速扫描它们,并与dealloc方法的实现进行比较。
在不把属性标记为“非primefaces”之前,你应该好好思考。 正如“Objective C编程语言指南”所指出的,属性在默认情况下是primefaces的,会产生相当大的开销。 而且,简单地将所有属性设为primefaces并不会使应用程序线程安全。 另外请注意,如果不指定“非primefaces”并实现自己的访问器方法(而不是综合它们),则必须以primefaces方式实现它们。
考虑零值
正如这个问题所指出的,在Objective-C中,消息是无效的。 虽然这往往是一个优势 – 导致更清洁,更自然的代码 – 如果你没有期待的话,这个function偶尔会导致特殊和难以追踪的错误,如果你nil
价值的话。
使用NSAssert和朋友。 我一直使用nil作为有效的对象…尤其是发送消息给nil在Obj-C中是完全有效的。 但是,如果我真的想确定一个variables的状态,我使用NSAssert和NSParameterAssert,这有助于轻松地查找问题。
Simple but oft-forgotten one. According to spec:
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.
in which case all the same named selectors, even if in different classes , will be regarded as to have identical return/argument types. Here is a simple example.
@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; }
If you're using Leopard (Mac OS X 10.5) or later, you can use the Instruments application to find and track memory leaks. After building your program in Xcode, select Run > Start with Performance Tool > Leaks.
Even if your app doesn't show any leaks, you may be keeping objects around too long. In Instruments, you can use the ObjectAlloc instrument for this. Select the ObjectAlloc instrument in your Instruments document, and bring up the instrument's detail (if it isn't already showing) by choosing View > Detail (it should have a check mark next to it). Under "Allocation Lifespan" in the ObjectAlloc detail, make sure you choose the radio button next to "Created & Still Living".
Now whenever you stop recording your application, selecting the ObjectAlloc tool will show you how many references there are to each still-living object in your application in the "# Net" column. Make sure you not only look at your own classes, but also the classes of your NIB files' top-level objects. For example, if you have no windows on the screen, and you see references to a still-living NSWindow, you may have not released it in your code.
Clean up in dealloc.
This is one of the easiest things to forget – esp. when coding at 150mph. Always, always, always clean up your attributes/member variables in dealloc.
I like to use Objc 2 attributes – with the new dot notation – so this makes the cleanup painless. Often as simple as:
- (void)dealloc { self.someAttribute = NULL; [super dealloc]; }
This will take care of the release for you and set the attribute to NULL (which I consider defensive programming – in case another method further down in dealloc accesses the member variable again – rare but could happen).
With GC turned on in 10.5, this isn't needed so much any more – but you might still need to clean up others resources you create, you can do that in the finalize method instead.
All these comments are great, but I'm really surprised nobody mentioned Google's Objective-C Style Guide that was published a while back. I think they have done a very thorough job.
Also, semi-related topic (with room for more responses!):
What are those little Xcode tips & tricks you wish you knew about 2 years ago? 。
Don't forget that NSWindowController and NSViewController will release the top-level objects of the NIB files they govern.
If you manually load a NIB file, you are responsible for releasing that NIB's top-level objects when you are done with them.
One rather obvious one for a beginner to use: utilize Xcode's auto-indentation feature for your code. Even if you are copy/pasting from another source, once you have pasted the code, you can select the entire block of code, right click on it, and then choose the option to re-indent everything within that block.
Xcode will actually parse through that section and indent it based on brackets, loops, etc. It's a lot more efficient than hitting the space bar or tab key for each and every line.
I know I overlooked this when first getting into Cocoa programming.
Make sure you understand memory management responsibilities regarding NIB files. You are responsible for releasing the top-level objects in any NIB file you load. Read Apple's Documentation on the subject.
Turn on all GCC warnings, then turn off those that are regularly caused by Apple's headers to reduce noise.
Also run Clang static analysis frequently; you can enable it for all builds via the "Run Static Analyzer" build setting.
Write unit tests and run them with each build.
Variables and properties
1/ Keeping your headers clean, hiding implementation
Don't include instance variables in your header. Private variables put into class continuation as properties. Public variables declare as public properties in your header. If it should be only read, declare it as readonly and overwrite it as readwrite in class continutation. Basically I am not using variables at all, only properties.
2/ Give your properties a non-default variable name, example:
@synthesize property = property_;
Reason 1: You will catch errors caused by forgetting "self." when assigning the property. Reason 2: From my experiments, Leak Analyzer in Instruments has problems to detect leaking property with default name.
3/ Never use retain or release directly on properties (or only in very exceptional situations). In your dealloc just assign them a nil. Retain properties are meant to handle retain/release by themselves. You never know if a setter is not, for example, adding or removing observers. You should use the variable directly only inside its setter and getter.
查看
1/ Put every view definition into a xib, if you can (the exception is usually dynamic content and layer settings). It saves time (it's easier than writing code), it's easy to change and it keeps your code clean.
2/ Don't try to optimize views by decreasing the number of views. Don't create UIImageView in your code instead of xib just because you want to add subviews into it. Use UIImageView as background instead. The view framework can handle hundreds of views without problems.
3/ IBOutlets don't have to be always retained (or strong). Note that most of your IBOutlets are part of your view hierarchy and thus implicitly retained.
4/ Release all IBOutlets in viewDidUnload
5/ Call viewDidUnload from your dealloc method. It is not implicitly called.
记忆
1/ Autorelease objects when you create them. Many bugs are caused by moving your release call into one if-else branch or after a return statement. Release instead of autorelease should be used only in exceptional situations – eg when you are waiting for a runloop and you don't want your object to be autoreleased too early.
2/ Even if you are using Authomatic Reference Counting, you have to understand perfectly how retain-release methods work. Using retain-release manually is not more complicated than ARC, in both cases you have to thing about leaks and retain-cycles. Consider using retain-release manually on big projects or complicated object hierarchies.
注释
1/ Make your code autodocumented. Every variable name and method name should tell what it is doing. If code is written correctly (you need a lot of practice in this), you won't need any code comments (not the same as documentation comments). Algorithms can be complicated but the code should be always simple.
2/ Sometimes, you'll need a comment. Usually to describe a non apparent code behavior or hack. If you feel you have to write a comment, first try to rewrite the code to be simpler and without the need of comments.
Indentation
1/ Don't increase indentation too much. Most of your method code should be indented on the method level. Nested blocks (if, for etc.) decrease readability. If you have three nested blocks, you should try to put the inner blocks into a separate method. Four or more nested blocks should be never used. If most of your method code is inside of an if, negate the if condition, example:
if (self) { //... long initialization code ... } return self;
if (!self) { return nil; } //... long initialization code ... return self;
Understand C code, mainly C structs
Note that Obj-C is only a light OOP layer over C language. You should understand how basic code structures in C work (enums, structs, arrays, pointers etc). 例:
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;
And many more
Mantain your own coding standards document and update it often. Try to learn from your bugs. Understand why a bug was created and try to avoid it using coding standards.
Our coding standards have currently about 20 pages, a mix of Java Coding Standards, Google Obj-C/C++ Standards and our own addings. Document your code, use standard standard indentation, white spaces and blank lines on the right places etc.
Be more functional .
Objective-C is object-oriented language, but Cocoa framework functional-style aware, and is designed functional style in many cases.
-
There is separation of mutability. Use immutable classes as primary, and mutable object as secondary. For instance, use NSArray primarily, and use NSMutableArray only when you need.
-
There is pure functions. Not so many, buy many of framework APIs are designed like pure function. Look at functions such as
CGRectMake()
orCGAffineTransformMake()
. Obviously pointer form looks more efficient. However indirect argument with pointers can't offer side-effect-free. Design structures purely as much as possible. Separate even state objects. Use-copy
instead of-retain
when passing a value to other object. Because shared state can influence mutation to value in other object silently. So can't be side-effect-free. If you have a value from external from object, copy it. So it's also important designing shared state as minimal as possible.
However don't be afraid of using impure functions too.
-
There is lazy evaluation. See something like
-[UIViewController view]
property. The view won't be created when the object is created. It'll be created when caller readingview
property at first time.UIImage
will not be loaded until it actually being drawn. There are many implementation like this design. This kind of designs are very helpful for resource management, but if you don't know the concept of lazy evaluation, it's not easy to understand behavior of them. -
There is closure. Use C-blocks as much as possible. This will simplify your life greatly. But read once more about block-memory-management before using it.
-
There is semi-auto GC. NSAutoreleasePool. Use
-autorelease
primary. Use manual-retain/-release
secondary when you really need. (ex: memory optimization, explicit resource deletion)