在编写Objective-C和Cocoa时,您使用的最佳实践是什么?

我知道高的(这是相当方便的!),但你在编写Objective-C时使用什么编程实践,更具体地说,当使用Cocoa(或CocoaTouch)时。

123500 次浏览

黄金法则:如果你alloc,那么你release!

更新:除非你正在使用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的硬件支持浮点数,但与单精度相比,执行双精度算术可能仍需要更多时间。引用:

在老式手机上,计算的速度应该是相同的,但你可以在寄存器中使用更多的单精度组件,所以对于许多计算来说,单精度最终会更快。

5)设置你的属性为nonatomic。它们默认是atomic,在合成时,将创建信号量代码以防止多线程问题。99%的人可能不需要担心这个,当设置为nonatomic时,代码就不会那么臃肿,内存效率更高。

6) SQLite是一种非常非常快的缓存大型数据集的方法。例如,地图应用程序可以将其磁贴缓存到SQLite文件中。最昂贵的部分是磁盘I/O。通过在大块之间发送BEGIN;COMMIT;来避免许多小的写操作。例如,我们使用一个2秒的计时器,在每次新提交时重置。当它过期时,我们发送COMMIT;,这将导致所有写操作都集中在一个大块中。SQLite将事务数据存储到磁盘,这样开始/结束包装可以避免创建许多事务文件,将所有事务分组到一个文件中。

此外,如果GUI在主线程上,SQL将阻塞它。如果你有一个很长的查询,最好将查询存储为静态对象,并在单独的线程上运行SQL。确保在@synchronize() {}块中包装任何修改查询字符串的数据库。对于简短的查询,为了更方便,只需将内容留在主线程中。

更多的SQLite优化技巧在这里,虽然文档看起来过时了,但许多要点可能仍然是好的;

http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html

@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

所以它们是实际类的一部分——而不是类之外的(私有)类别。细微但重要的区别。

在dealloc中清理。

这是最容易忘记的事情之一——尤其是在以150英里/小时的速度编码时。总是,总是,总是清理dealloc中的属性/成员变量。

我喜欢使用objc2属性——这个新的点表示法——这样就可以轻松地清理。通常很简单:

- (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];
}

这样做可以确保不再发送更多的委托方法。当你准备dealloc并消失在以太中时,你要确保没有任何东西可以意外地再给你发送任何消息。还记得自己。someObject可以被另一个对象保留(它可以是一个单例对象或在自动释放池中或其他任何对象),直到您告诉它“停止向我发送消息!”,它才会认为您即将被释放的对象是公平的游戏。

养成这种习惯将使您避免大量令人痛苦的调试的奇怪崩溃。

同样的原理也适用于键值观察和NSNotifications。

编辑:

更有防御性的改变:

self.someObject.delegate = NULL;

成:

if (self.someObject.delegate == self)
self.someObject.delegate = NULL;

尽量避免我现在决定称之为“新类别yaholism”的现象。当Objective-C的新手发现类别时,他们通常会疯狂地为现有的每个类添加有用的小类别(“什么?我可以添加一个方法来转换一个数字到罗马数字到NSNumber岩石上!”)。

不要这样做。

你的代码将更容易移植,更容易理解,因为没有几十个小类别方法散布在20多个基础类之上。

大多数时候,当你真的认为你需要一个类别方法来帮助简化一些代码时,你会发现你永远不会重用这个方法。

还有其他的危险,除非你对你的分类方法进行命名空间(除了疯狂的ddribin,还有谁会这么做呢?),在你的地址空间中运行的苹果、插件或其他东西也有可能用相同的名称定义相同的分类方法,但副作用略有不同....

好的。现在你已经被警告过了,忽略“不要做这部分”。但是要极度克制。

还有一个半相关的话题(还有更多的回复空间!):

那些Xcode的小技巧是什么?你希望两年前就知道的诀窍?

编写单元测试。你可以在Cocoa中测试很多的东西,这在其他框架中可能更难。例如,对于UI代码,您通常可以验证事物是否按照应有的方式连接,并相信它们在使用时能够正常工作。你可以设置state &很容易调用委托方法来测试它们。

在编写内部测试时,也不会有公共、受保护和私有方法可见性。

抵制世界的子类化。在Cocoa中,很多工作都是通过委托和使用底层运行时完成的,而在其他框架中则是通过子类化完成的。

例如,在Java中你会大量使用匿名*Listener子类的实例,而在. net中你会大量使用EventArgs子类。在Cocoa中,你什么都不用做——而是使用目标-动作。

不要把Objective-C写成Java/ c# / c++等。

我曾经看到一个编写Java EE web应用程序的团队尝试编写Cocoa桌面应用程序。就好像它是一个Java EE web应用程序。有很多AbstractFooFactory、FooFactory、IFoo和Foo,而他们真正需要的只是一个Foo类,可能还有一个Fooable协议。

确保你不这样做的部分原因是真正理解语言的差异。例如,您不需要上面的抽象工厂和工厂类,因为Objective-C类方法和实例方法一样是动态分派的,并且可以在子类中重写。

使用标准的Cocoa命名和格式化约定和术语,而不是使用其他环境中使用的任何术语。有许多Cocoa开发人员在那里,当另一个人开始使用你的代码时,如果它看起来和感觉类似于其他Cocoa代码,它会更容易接近。

做什么和不做什么的例子:

  • 不要在对象接口中声明id m_something;并调用它成员变量;使用something_something作为它的名称,并称其为实例变量
  • 不要将getter命名为-getSomething;正确的Cocoa名称是-something
  • 不要将setter命名为-something:;它应该是-setSomething:
  • 方法名在参数中穿插,包括冒号;它是-[NSObject performSelector:withObject:],不是NSObject::performSelector
  • 在方法名、参数、变量、类名等中使用内部大写(CamelCase),而不是下划线(下划线)。
  • 类名以大写字母开头,变量和方法名以小写字母开头。

无论你做什么,使用Win16/ win32风格的匈牙利符号。甚至微软也在转向。net平台时放弃了这一点。

我知道我在第一次接触Cocoa编程时忽略了这一点。

确保您了解有关NIB文件的内存管理职责。您负责释放所加载的任何NIB文件中的顶级对象。阅读有关该主题的苹果公司的文档

确保你收藏了调试魔法页面。这应该是你在寻找Cocoa bug的源头时碰壁的第一站。

例如,它会告诉你如何找到你第一次分配内存的方法,后来导致崩溃(比如在应用程序终止期间)。

如果您使用的是Leopard (Mac OS X 10.5)或更高版本,则可以使用Instruments应用程序来查找和跟踪内存泄漏。在Xcode中构建程序后,选择使用性能工具> Leaks运行>。

即使你的应用程序没有显示任何泄漏,你可能把对象保存得太久了。在Instruments中,您可以为此使用ObjectAlloc工具。在Instruments文档中选择ObjectAlloc乐器,并通过选择View > detail(它旁边应该有一个复选标记)来显示乐器的详细信息(如果还没有显示的话)。在ObjectAlloc详细信息中的“分配生命周期”下,确保您选择了“已创建&仍然生活”。

现在,每当您停止记录应用程序时,选择ObjectAlloc工具将在“# Net”列中显示对应用程序中每个仍然活动的对象的引用数。确保不仅要查看自己的类,还要查看NIB文件的顶级对象的类。例如,如果你在屏幕上没有窗口,而你看到一个仍然存在的NSWindow的引用,你可能没有在你的代码中释放它。

不要忘记NSWindowController和NSViewController会释放它们所控制的NIB文件的顶级对象。

如果手动加载NIB文件,则在使用完该NIB的顶级对象后,需要负责释放它们。

iboutlet

从历史上看,outlet的内存管理一直很差。 当前的最佳实践是将outlet声明为属性:

@interface MyClass :NSObject {
NSTextField *textField;
}
@property (nonatomic, retain) IBOutlet NSTextField *textField;
@end

使用属性使内存管理语义清晰;如果使用实例变量综合,它还提供了一致的模式。

声明属性

对于所有属性,您通常应该使用Objective-C 2.0声明属性特性。如果它们不是公共的,将它们添加到类扩展中。使用声明的属性使内存管理语义立即清晰,并使您更容易检查dealloc方法——如果您将属性声明分组在一起,您可以快速扫描它们并与dealloc方法的实现进行比较。

在不将属性标记为“nonatomic”之前,您应该仔细考虑一下。正如Objective C编程语言指南所指出的,属性默认是原子的,并且会产生相当大的开销。此外,简单地将所有属性都设置为原子化并不能使应用程序具有线程安全性。当然,还要注意,如果你没有指定“nonatomic”,并且实现你自己的访问方法(而不是合成它们),你必须以原子的方式实现它们。

使用LLVM/Clang静态分析仪

注意:在Xcode 4中,这是内置在IDE中。

你在Mac OS X 10.5上使用Clang静态分析仪来分析你的C和Objective-C代码(还没有c++)——这并不奇怪。安装和使用起来很简单:

  1. 这个页面下载最新版本。
  2. 从命令行cd到你的项目目录。
  3. 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];

当你编写自己的方法返回一个新创建的对象时,你可以利用可可的命名约定来标记接收者必须通过在方法名前加上"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方法)。

你可以通过使用你自己的本地自动释放池来控制。有关此的更多信息,请参见Autorelease池

想想nil值

正如这个问题所指出的,nil的消息在Objective-C中是有效的。虽然这通常是一个优势——导致更干净和更自然的代码——但如果你在没有预料到的情况下获得nil值,该特性偶尔会导致特殊且难以追踪的错误。

其中一些已经被提到过,但以下是我能想到的:

  • 即使你现在不使用KVO,根据我的经验,它在未来仍然是有益的。如果您正在使用KVO或绑定,您需要知道事情会以它们应该的方式工作。这不仅包括访问器方法和实例变量,还包括多对象关系、验证、自动通知依赖键等。
  • 将私有方法放在一个类别中。不仅仅是接口,还有实现。在私有方法和非私有方法之间保持概念上的距离是很好的。我把所有东西都包含在。m文件中。
  • 把后台线程方法放在一个类别中。同上。我发现,当你在思考主线程上有什么,没有什么时,保持一个清晰的概念障碍是很好的。
  • 使用#pragma mark [section]通常我根据我自己的方法,每个子类的覆盖,以及任何信息或正式协议进行分组。这让我更容易跳转到我想要的内容。在同一个主题上,将相似的方法(如表视图的委托方法)组合在一起,不要只是把它们放在任何地方。
  • 前缀私有方法&Ivars与_。我喜欢它看起来的方式,我不太可能使用ivar当我指的是一个意外的属性。
  • 不要在init &中使用mutator方法/属性;dealloc。我从来没有任何不好的事情发生,因为它,但我可以看到逻辑,如果你改变方法做一些取决于你的对象的状态。
  • 将iboutlet放入属性中。我实际上刚刚读了这个,但我要开始做了。撇开内存的好处不谈,它在风格上似乎更好(至少对我来说是这样)。
  • 避免编写不是绝对需要的代码。这确实涵盖了很多事情,比如在#define可以做的时候创建ivars,或者在每次需要数据时缓存数组而不是对其排序。关于这一点,我有很多要说的,但底线是,除非你需要它,或者剖析器告诉你,否则不要写代码。从长远来看,这会让事情更容易维持。
  • 有很多半成品,bug代码是杀死一个项目的最快方式。如果你需要一个存根方法,这是可以的,只需通过将NSLog( @"stub" )放在里面来指示它,或者以任何你想要跟踪的方式。

所有这些评论都很棒,但我真的很惊讶没有人提到谷歌的Objective-C风格指南,这是不久前发布的。我认为他们做得很彻底。

使用NSAssert和朋友。 我一直使用nil作为有效对象…特别是发送消息给nil在Obj-C中是完全有效的。 然而,如果我真的想确定一个变量的状态,我使用NSAssert和NSParameterAssert,这有助于轻松跟踪问题

我看到的苹果提供的示例将App委托视为一个全局数据存储,一种数据管理器。这是错误的。创建一个单例,并在App委托中实例化它,但不要将App委托用作应用程序级事件处理以外的任何东西。我由衷地赞同这个博客条目中的建议。这个线程给我通风报信。

打开所有GCC警告,然后关闭那些通常由Apple头文件引起的警告,以减少噪音。

还经常运行叮当静态分析;您可以通过“运行静态分析器”构建设置为所有构建启用它。

编写单元测试,并在每次构建时运行它们。

对于初学者来说,一个很明显的方法是:利用Xcode的自动缩进功能。即使是从其他源复制/粘贴代码,粘贴完代码后,也可以选择整个代码块,右键单击它,然后选择选项重新缩进该代码块中的所有内容。

Xcode会解析这个section并根据括号,循环等缩进它。这比每一行都按空格键或tab键要有效率得多。

简单但经常被遗忘。根据规格:

一般来说,方法不同 具有相同选择器的类 (相同的名字)也必须共享 相同的返回值和参数类型。这 约束是由编译器施加的 允许动态绑定

在这种情况下,所有相同的命名选择器即使在不同的班级将被视为具有相同的返回/参数类型。这里有一个简单的例子。

@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;
}

功能

Objective-C是面向对象的语言,但是Cocoa框架是函数式风格的,并且在很多情况下是函数式设计的。

  1. 有可变性的分离。使用immutable类作为主要类,可变对象作为次要类。例如,主要使用NSArray,只有在需要时才使用NSMutableArray。

  2. 是纯函数。没有那么多,很多框架api都是像纯函数一样设计的。看看诸如CGRectMake()CGAffineTransformMake()这样的函数。显然,指针形式看起来更有效率。然而,带有指针的间接参数不能提供无副作用。尽可能纯粹地设计结构。 分离偶数状态对象。将值传递给其他对象时,使用-copy而不是-retain。因为共享状态会无声地影响其他对象对值的变化。所以不可能没有副作用。如果你有一个来自外部from对象的值,复制它。所以尽可能少地设计共享状态也很重要。李< / p > < / >

但是也不要害怕使用不纯函数。

  1. 有懒惰的评估。看到类似-[UIViewController view]属性的东西。创建对象时,视图不会被创建。它将在调用者第一次读取view属性时创建。UIImage在实际被绘制之前不会被加载。像这样的设计有很多实现。这种设计对资源管理非常有帮助,但是如果您不知道懒惰求值的概念,就不容易理解它们的行为。

  2. 这就是终结。尽量使用c字型。这将大大简化你的生活。但是在使用它之前,请再阅读一遍关于块内存管理的内容。

  3. 有半自动GC。NSAutoreleasePool。使用-autorelease primary。在真正需要时使用手动-retain/-release辅助。(例如:内存优化,显式资源删除)

只在dealloc方法中释放财产。如果你想释放财产所持有的内存,只需将其设置为nil:

self.<property> = nil;

变量和属性

1/保持头文件干净,隐藏实现
不要在头文件中包含实例变量。作为属性放入类延续中的私有变量。公共变量在头文件中声明为公共属性。 如果它应该是只读的,将其声明为readonly,并在类延续中将其重写为readwrite。 基本上我没有使用变量,只有属性

2/给你的属性一个非默认变量名,例如:


@synthesize property = property_;
原因1:在分配属性时,您将捕获由忘记“self.”引起的错误。 原因2:从我的实验中,仪器中的泄漏分析仪在检测默认名称的泄漏属性时存在问题

3/不要在属性上直接使用retain或release(或仅在非常特殊的情况下)。在dealloc中,给它们分配一个nil。保留属性意味着自己处理保留/释放。例如,您永远不知道setter是否没有添加或删除观察器。您应该只在它的setter和getter中直接使用变量。

的观点

1/如果可以的话,将每个视图定义放入xib中(例外通常是动态内容和层设置)。它节省时间(比写代码简单),易于更改,并保持代码整洁。

2/不要试图通过减少视图数量来优化视图。不要在代码中创建UIImageView而不是xib,因为你想在其中添加子视图。使用UIImageView作为背景。视图框架可以毫无问题地处理数百个视图。

3/ iboutlet不需要总是被保留(或强大)。注意,大多数iboutlet都是视图层次结构的一部分,因此隐式保留。

4/释放viewDidUnload中的所有iboutlet

5/从dealloc方法调用viewDidUnload。它不是隐式调用的。

内存

1/创建对象时自动释放对象。许多错误是由于将释放调用移动到一个if-else分支或在返回语句之后引起的。释放而不是自动释放应该只在特殊情况下使用——例如,当你在等待一个运行循环时,你不想让你的对象过早被自动释放。

即使你正在使用自动引用计数,你也必须完全理解保留-释放方法是如何工作的。手动使用保留-释放并不比ARC更复杂,在这两种情况下,您都必须考虑泄漏和保留周期。 考虑在大型项目或复杂的对象层次结构上手动使用保留释放

评论

1/使您的代码自动文档化。 每个变量名和方法名都应该说明它在做什么。如果代码编写正确(这方面需要大量实践),则不需要任何代码注释(与文档注释不同)。算法可以很复杂,但代码应该总是简单的

2/有时候,你需要别人的评论。通常用来描述一种不明显的代码行为或黑客行为。如果您觉得必须写注释,首先尝试重写代码,使其更简单,不需要注释。

缩进

1/不要增加太多缩进。 大多数方法代码应该在方法级别上缩进。嵌套块(if, for等)降低了可读性。如果您有三个嵌套块,您应该尝试将内部块放入一个单独的方法中。四个或更多嵌套的块永远不应该使用。 如果大部分方法代码都在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语言的基本代码结构(枚举、结构体、数组、指针等)是如何工作的。 例子:< / p >

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;

还有更多

维护自己的编码标准文档,并经常更新它。试着从错误中学习。理解错误产生的原因,并尝试使用编码标准来避免它。

我们的编码标准目前大约有20页,混合了Java编码标准,谷歌Obj-C/ c++标准和我们自己的添加。记录你的代码,在正确的地方使用标准的缩进,空格和空行等等。

#import "MyClass.h"


@interface MyClass ()
- (void) someMethod;
- (void) someOtherMethod;
@end


@implementation MyClass