编写Objective-C和Cocoa时使用的最佳实践是什么?[关闭]


346

我知道HIG(非常方便!),但是在编写Objective-C时,尤其是在使用Cocoa(或CocoaTouch)时,会使用哪些编程实践。


看到这篇博客文章,非常好。ironwolf.dangerousgames.com/blog/archives/913
user392412'4

Answers:


398

我已经开始做一些我认为不标准的事情:

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%可能不需要担心这一点,并且将代码设置为非原子时,代码的肿程度会大大降低,内存效率更高。

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


3
关于双精度算法的不错的建议。
亚当·恩斯特

8
类的外延现在是私有方法的首选方式:developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/...
Casebash

9
您对在iPhone上双打的建议是过时的stackoverflow.com/questions/1622729/...
Casebash

3
不会过时;完全错误:最初支持的iPhone在硬件上以大约相同的速度浮动并翻倍。SQLite也不将事务保留在内存中;它们记录在磁盘上。只有长查询会阻塞您的用户界面;在主线程中运行所有内容并使用更快的查询,不会太麻烦。
tc。

1
@tc:我更正了有关事务的SQL项,请注意,我本人没有写最后四个左右的项。我还澄清了有关将查询移至后台的部分仅适用于非常长的查询(有时您不能使查询更短)。但是,由于几点,把整个事情称为“错误”是我的一种极端。同样,上面的回答已经表明:“在较旧的电话上,计算应该以相同的速度进行”,但是请注意有关单精度寄存器数量更多的部分,这使它们仍然是可取的。
肯德尔·赫尔姆斯特·吉尔纳

109

不要使用未知字符串作为格式字符串

当方法或函数采用格式字符串参数时,应确保对格式字符串的内容具有控制权。

例如,在记录字符串时,很容易将字符串变量作为唯一参数传递给NSLog

    NSString *aString = // get a string from somewhere;
    NSLog(aString);

问题在于字符串可能包含被解释为格式字符串的字符。这可能导致错误的输出,崩溃和安全问题。相反,您应该将字符串变量替换为格式字符串:

    NSLog(@"%@", aString);

4
我以前被这个咬过。
亚当·恩斯特,

这对于任何编程语言都是一个很好的建议
Tom Fobear 2011年

107

使用标准的可可命名和格式约定以及术语,而不要使用其他环境中常用的内容。目前很多的Cocoa开发在那里,当他们中的一个又一个开始你的代码的工作,如果它的外观和感觉类似于其他可可代码它会更平易近人。

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

  • 不要id m_something;在对象的接口中声明并将其称为成员变量字段;使用something_something为其名称,并称其为实例变量
  • 不要说一个吸气剂-getSomething; 可可的正确名字是-something
  • 不要说塞特犬-something:; 它应该是-setSomething:
  • 方法名称中插入了参数,并包含冒号;是的-[NSObject performSelector:withObject:],不是NSObject::performSelector
  • 在方法名称,参数,变量,类名称等中使用大写字母(CamelCase),而不要使用下划线(下划线)。
  • 类名以大写字母开头,变量和方法名以小写字母开头。

无论您做什么,都不要使用Win16 / Win32风格的匈牙利符号。甚至微软也放弃了向.NET平台的迁移。


5
我会争辩,根本不要使用setSomething:/ something-而是使用属性。在这一点上,真正需要针对Tiger的人很少(不使用属性的唯一原因)
Kendall Helmstetter Gelner

18
属性仍然为您生成访问器方法,并且属性上的getter = / setter =属性使您可以指定方法的名称。另外,可以使用[foo something]语法而不是具有属性的foo.something语法。因此,访问者命名仍然很重要。
克里斯·汉森

3
对于那些来自C ++的人来说,这是一个很好的参考,在C ++中我做了您建议的大多数事情。
克林顿·布莱克莫尔

4
设置器不应导致将某些内容保存到数据库。原因是Core Data在NSManagedObjectContext上具有-save:方法,而不是让setter产生立即更新的原因。
克里斯·汉森

2
我怀疑这不是一个选择,但是它可能需要重新访问您的应用程序体系结构。(要明确:我不是说“您应该使用Core Data。”我是说“ Setter不应该保存到数据库。”)具有上下文来管理对象图,而不是在其中保存单个对象,几乎总是可能的,并且是更好的解决方案。
克里斯·汉森

106

IBOutlets

从历史上看,网点的内存管理很差。当前的最佳做法是将网点声明为属性:

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

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


1
装入笔尖不会保留两次吗?(一次出现在笔尖中,其次是分配给属性)。我应该释放那些在dealloc中的吗?
Kornel

6
您必须在viewDidUnload(iPhone OS 3.0+)或自定义setView:方法中将出口设置为零,以避免泄漏。显然,您也应该在dealloc中释放。
Frank Szczerba 09年

2
请记住,并非所有人都同意这种风格:weblog.bignerdranch.com/?p=95
Michael

这也是苹果做事的方式。“ iPhone 3的开始开发”也提到了从以前的版本开始的这一变化。
ustun

我在另一条评论中提到了这一点,但应该将其放在此处:一旦iOS应用程序开始进行动态ivar合成(如果/何时?),您会很高兴将IBOutlet放在属性和ivar之间!
Joe D'Andrea

97

使用LLVM / Clang静态分析器

注意:在Xcode 4下,它现在内置在IDE中。

毫不奇怪,您可以使用Clang静态分析器在Mac OS X 10.5上分析您的C和Objective-C代码(尚无C ++)。安装和使用很简单:

  1. 从此页面下载最新版本。
  2. 从命令行cd到您的项目目录。
  3. 执行scan-build -k -V xcodebuild

(还有一些其他限制,等等,特别是您应该在“调试”配置下分析项目- 有关详细信息,请参见http://clang.llvm.org/StaticAnalysisUsage.html-但这或多或少归结为

然后,分析器为您生成一组网页,这些网页显示了可能的内存管理和编译器无法检测到的其他基本问题。


1
在按照以下说明进行操作之前,我遇到了一些麻烦:oiledmachine.com/posts/2009/01/06/…– bbrown
09年

15
在Snow Leopard的XCode 3.2.1中,它已经内置。您可以使用Run-> Build and Analyze手动运行它,也可以通过“ Run Static Analyzer”构建设置为所有构建启用它。请注意,该工具目前仅支持C和Objective-C,但不支持C ++ / Objective-C ++。
oefe

94

这是一个微妙的但方便的一。如果要将自己作为委托传递给另一个对象,请先重置该对象的委托dealloc

- (void)dealloc
{
self.someObject.delegate = NULL;
self.someObject = NULL;
//
[super dealloc];
}

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

养成这种习惯将使您免于调试时很痛苦的怪异崩溃。

相同的原则适用于键值观察和NSNotifications。

编辑:

更具有防御性的变化:

self.someObject.delegate = NULL;

变成:

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

8
这没有什么微妙之处,文档明确指出您必须执行此操作。来自Memory Management Programming Guide for CocoaAdditional cases of weak references in Cocoa include, but are not restricted to, table data sources, outline view items, notification observers, and miscellaneous targets and delegates. In most cases, the weak-referenced object is aware of the other object’s weak reference to it, as is the case for circular references, and is responsible for notifying the other object when it deallocates.
约翰(Johne)

最好使用nil而不是NULL,因为NULL不会释放内存。
Naveen Shan

@NaveenShan nil == NULL。它们是完全相同的,只是nilidNULLvoid *。您的说法不正确。

@WTP是的,nil == NULL,但是使用nil显然是首选方法,如果仔细查看苹果示例代码片段,它们在各处都使用nil,而且正如您所说,nil是id,这使其比void更可取* ,即在您发送ID的情况下。
Ahti

1
完全Nil是@Ahti,而(大写)是类型Class*。即使它们都是平等的,使用错误的代码也会引入讨厌的小错误,尤其是在Objective-C ++中。

86

@肯德尔

代替:

@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”

因此,它们是实际类的一部分-除了该类之外,不是(私有)类别。细微但重要的区别。


您可以这样做,但我想将其明确标记为“私有”部分(比功能更多的文档),尽管显然已经从它位于.m文件中了……
Kendall Helmstetter Gelner

2
除了存在私人的类别和类分机之间的差:“类扩展允许你声明额外需要API为在比主类@interface块内的其它位置的一类,如下面的例子所示:”查看修改链接。
斯瓦

我同意,当您尚未实现CE方法时,编译器会向您发出警告,这是有区别的;但是,当所有方法都在同一文件中且全部为私有文件时,我认为这方面不是很重要。我仍然更喜欢将前向参考块标记为私有的可维护性方面
Kendall Helmstetter Gelner's

3
我真的不认为(Private)比()更可维护。如果您担心的话,那么大量的评论可能会有所帮助。但显然生活并放任不管。YMMV等
schwa,

17
使用一个相当重要的优势来()代替(Private)(或其他一些类别名称):您可以将属性重新声明为可读写,而对公众来说它们只是只读的。:)
Pascal

75

避免自动释放

由于您通常(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的newObjectmethod)。

(1)您可以使用自己的本地自动释放池进行控制。有关更多信息,请参见自动释放池


6
我发现使用自动发布的好处胜过它的代价(即更多的内存泄漏错误)。无论如何,主线程上的代码都应该运行得很短(否则您将冻结UI),并且对于运行时间较长的占用大量内存的后台代码,您始终可以将占用内存的部分包装在本地自动释放池中。
adib 2010年

56
我不同意。您应该尽可能使用自动释放的对象。如果它们增加了过多的内存占用,则应使用另一个NSAutoreleasePool。但是只有在您确认这确实是一个问题之后。过早的优化以及所有这些……
Sven

3
我花了不到40秒。每天键入[someObject release]并在实例化新对象时阅读“额外的代码”,但是我曾经花了17个小时来寻找一个仅在特殊情况下才会出现并且在控制台中未出现连贯错误的自动发布错误。因此,我同意adib的话:“我发现不使用自动发布的好处胜于其成本”。
RickiG 2010年

7
我同意斯文。主要目标应该是代码清晰并减少编码错误,仅在需要的地方进行内存优化。键入[[[Foo alloc] init]自动释放]很快,您会立即处理释放此新对象的问题。阅读代码时,您不必四处寻找相应的发行版以确保它不会被泄漏。
Mike Weller 2010年

3
自动释放对象的生命周期定义明确,可以在足够的水平上确定。
Eonil'3

69

其中一些已经被提及,但是这是我想到的:

  • 遵循KVO命名规则。即使您现在不使用KVO,根据我的经验,通常它在将来仍然是有益的。而且,如果您使用的是KVO或绑定,则需要知道事情正在按预期的方式进行。这不仅涉及访问器方法和实例变量,还涉及许多关系,验证,自动通知依赖项等。
  • 将私有方法归为一类。不只是接口,还有实现。在概念上在私有方法和非私有方法之间保持一定距离是很好的。我将所有内容都包含在.m文件中。
  • 将后台线程方法放在一个类别中。同上。我发现在考虑主线程上的内容和未线程上的内容时,最好保持明确的概念障碍。
  • 使用#pragma mark [section]通常,我按自己的方法,每个子类的覆盖以及任何信息或正式协议进行分组。这使跳转到我正在寻找的内容变得容易得多。在同一主题上,将相似的方法(如表视图的委托方法)组合在一起,不要只是将它们粘在任何地方。
  • 用_前缀私有方法和ivars。我喜欢它的外观,并且当我偶然地表示财产时,我不太可能使用ivar。
  • 不要在init和dealloc中使用mutator方法/属性。因此,我从来没有发生过任何不好的事情,但是如果您更改方法以执行依赖于对象状态的操作,那么我可以看到逻辑。
  • 将IBOutlets放在属性中。我实际上只是在这里读过这篇,但是我将开始做。不管有什么内存方面的好处,从样式上看似乎更好(至少对我而言)。
  • 避免编写您不需要的代码。这确实涵盖了很多内容,例如在a #definewill会做时制作ivars ,或者缓存数组而不是在每次需要数据时对其进行排序。关于这一点,我有很多话要说,但是最重要的是不要编写代码,除非您需要它,否则探查器会告诉您。从长远来看,它使事情变得更容易维护。
  • 完成您的开始。有大量未完成的错误代码是杀死项目失败的最快方法。如果您需要一个很好的存根方法,则只需将其放入NSLog( @"stub" )内部即可,或者您想跟踪情况。

3
我建议您将私有方法放在类的延续中。(即@interface MyClass()... @end在您的.m中)
Jason Medeiros,

3
代替#PRAGMA,您可以使用注释// //标记:[Section],它更便于携带且工作原理相同。
aleemb

除非缺少特殊的语法,否则// Mark:不会在Xcode的功能下拉菜单中添加标签,这实际上是使用它的一半原因。
Marc Charbonneau

6
您需要使用大写字母“ // MARK:...”才能使其显示在下拉菜单中。
罗尔特

3
关于,Finish what you start您还可以使用// TODO:标记代码完成,该代码将显示在下拉列表中。
iwasrobbed

56

编写单元测试。您可以在Cocoa中测试很多在其他框架中可能很难完成的事情。例如,使用UI代码,您通常可以验证事物是否按其应有的方式连接,并相信它们在使用时会起作用。您可以轻松设置状态并调用委托方法以对其进行测试。

您也没有公开,受保护和私有方法的可见性,从而无法编写内部测试。


您推荐什么测试框架?
melfar

13
Xcode包括OCUnit,Objective-C单元测试框架,并支持在构建过程中运行单元测试包。
克里斯·汉森

55

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

更新:除非您使用ARC


26
此外,如果你copymutableCopynewretain
斯文

54

不要像编写Java / C#/ C ++ / etc那样编写Objective-C。

我曾经见过一个用来编写Java EE Web应用程序的团队尝试编写Cocoa桌面应用程序。好像它是一个Java EE Web应用程序。当他们真正需要的只是一个Foo类以及可能的Fooable协议时,有很多AbstractFooFactory和FooFactory以及IFoo和Foo到处飞。

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


10
作为在Objective-C中编写抽象工厂的Java开发人员,我发现这很有趣。您介意解释一下它的工作原理吗?
teabot

2
您是否仍然认为自从您发布此答案以来,我们一直不需要抽象工厂类?
kirk.burleson

50

确保将“ 调试魔术”页面添加为书签。这是您在试图寻找可可小虫的源头时将头撞在墙上的第一站。

例如,它将告诉您如何在首先分配内存的位置找到该方法,该内存后来导致崩溃(例如在应用程序终止期间)。


1
现在有一个特定于iOS的Debugging Magic页面。
Jeethu 2011年

38

尽量避免我现在决定称之为“新手分类狂”的东西。当Objective-C的新手发现类别时,他们常常会大吃一惊,向现有的每个类别添加有用的小类别(“什么?我可以添加一种方法,将数字转换为罗马数字,然后再转换为NSNumber!”)。

不要这样

在几十个基础类的基础上增加了数十种小类别方法,您的代码将更易于移植,更易于理解。

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

还有其他的危险,除非您为类别方法命名(除了完全疯狂的ddribin之外,还有谁?)Apple或插件或在地址空间中运行的其他东西也有可能定义相同的类别具有相同名称的方法,但副作用略有不同。

好。现在,您已经得到警告,请忽略“请勿执行此部分”。但是要保持克制。


我喜欢您的答案,我的建议是不要使用类别来存储实用程序代码,除非您要在多个地方复制某些代码并且该代码显然属于您要分类的类...
Kendall Helmstetter Gelner

我只是想表达我对命名空间类别方法的支持。这似乎是正确的选择。
Michael Buckley

+1(如果仅用于罗马数字)。我完全会那样做!
布莱恩·波斯托

14
反对点:在过去的一年半中,我遵循了完全相反的政策:“如果可以在某个类别中实施,请这样做。” 因此,与Apple提供的详细示例代码相比,我的代码更加简洁,更具表现力并且更易于阅读。一次名称空间冲突使我总共损失了大约10分钟的时间,而我为自己创造的效率可能使我获得了数月的工作量。对于每个人自己,但是我在了解风险的情况下采取了这项政策,对此我感到非常高兴。
cduhn

7
我不同意 如果它将成为一个函数并且适用于Foundation对象,并且您可以想到一个好名字,请将其归类。您的代码将更具可读性。我认为这里的重点是:适度地做所有事情。
mxcl

37

抵抗继承世界。在Cocoa中,很多事情是通过委派和使用底层运行时完成的,而在其他框架中则是通过子类化完成的。

例如,在Java中,您经常使用匿名*Listener子类的实例,而在.NET中,您EventArgs经常使用子类的实例。在可可中,您都不执行任何操作,而是使用target-action。


6
否则称为“继承构成”。
Andrew Ebling

37

根据用户需要对字符串进行排序

对要呈现给用户的字符串进行排序时,不应使用简单的compare:方法。相反,您应该始终使用本地化的比较方法,例如localizedCompare:localizedCaseInsensitiveCompare:

有关更多详细信息,请参见搜索,比较和排序字符串


31

申报物业

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

在不将属性标记为“非原子”之前,您应该认真考虑。正如《 Objective C编程语言指南》所述,默认情况下,属性是原子的,并且会产生相当大的开销。而且,仅使所有属性原子化并不能使应用程序具有线程安全性。当然,还请注意,如果您未指定“ nonatomic”并实现自己的访问器方法(而不是综合它们),则必须以原子方式实现它们。


26

考虑零值

该问题所述,消息to nil在Objective-C中有效。尽管这通常是一个优势-导致代码更清晰,更自然-但是,如果nil您在不期望的情况下获得了价值,那么该功能有时会导致特有且难以跟踪的错误。


我有这样的:#define SXRelease(o); o = nil与同为CFReleasefree。这简化了一切。


23

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

通常,具有相同选择器(相同名称)的不同类中的方法还必须共享相同的返回和参数类型。该约束由编译器强加以允许动态绑定。

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

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

这是一个容易忘记的人。尽管如此
Brock Woolf 2010年

3
仅在避免静态键入时才需要关注。如果编译器知道类型,则参数和返回类型可以不同而不会出现问题。从人格上来说,我发现这并不经常成为问题。苹果也有很多名称相同但返回类型不同的方法。最后,有一个编译器标志可以在模棱两可的情况下警告您。
Nikolai Ruhe

如果我们遵循Apple的命名约定准则,则不会发生这种情况:)
Eonil 2011年

22

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

即使您的应用程序未显示任何泄漏,您也可能将对象放置的时间过长。在仪器中,您可以使用ObjectAlloc仪器。在“仪器”文档中选择“ ObjectAlloc”仪器,然后通过选择“视图”>“详细信息”(旁边应有一个复选标记)来调出该仪器的详细信息(如果尚未显示)。在ObjectAlloc详细信息的“分配寿命”下,确保选择“已创建并仍然存在”旁边的单选按钮。

现在,无论何时停止记录应用程序,选择ObjectAlloc工具都将在“#Net”列中为您的应用程序中的每个仍然存在的对象显示多少引用。确保您不仅查看自己的类,而且查看NIB文件的顶级对象的类。例如,如果屏幕上没有窗口,并且看到了对仍然有效的NSWindow的引用,则可能尚未在代码中释放它。


21

在dealloc中清理。

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

我喜欢使用Objc 2属性(带有新的点符号),因此使清理工作变得轻松自如。通常很简单:

- (void)dealloc
{
    self.someAttribute = NULL;
    [super dealloc];
}

这将为您解决发行问题,并将属性设置为NULL(我认为这是防御性编程,以防在dealloc之后出现的另一种方法再次访问成员变量,这种情况很少见,但有可能发生)。

在10.5中启用GC后,就不需要太多了-但是您可能仍需要清理创建的其他资源,可以改为使用finalize方法。


12
一般来说,你应该使用的dealloc(或init)访问方法。
mmalc

1
除了性能原因(访问器比直接访问慢一点)之外,为什么我不应该在dealloc或init中使用访问器?
schwa,

1
(a)性能原因本身就足够了(尤其是如果访问器是原子的)。(b)您应该避免访问器可能产生的任何副作用。如果您的类可能是子类的,则后者尤其成问题。
mmalc

3
我会注意到,如果您在运行时使用合成ivars运行时,则必须在dealloc中使用访问器。许多现代的运行时代码是GC,但并非全部。
Louis Gerbarg

1
有关是否使用访问器方法/属性-init-dealloc方法的更详细的信息,请参见:mikeash.com/?page=pyblog/…
Johan Kool,2009年



13

不要忘记NSWindowController和NSViewController将释放它们管理的NIB文件的顶级对象。

如果您手动加载NIB文件,则在处理完NIB的顶级对象后,您有责任释放它们。


12

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

Xcode实际上将解析该部分并根据方括号,循环等对其进行缩进。这比敲击每行的空格键或Tab键要高效得多。


您甚至可以将Tab设置为缩进,然后执行Cmd-A和Tab。
Plumenator


10

打开所有GCC警告,然后关闭由Apple标头引起的常规警告,以减少噪音。

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

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


并且,如果可以,请打开“将警告视为错误”。不允许存在任何警告。
Peter Hosey,

2
可在此处使用方便的脚本来设置带有建议警告的项目:rentzsch.tumblr.com/post/237349423/hoseyifyxcodewarnings-scpt
Johan Kool,2009年

10

变量和属性

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

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


@synthesize property = property_;

原因1:您会发现由于忘记“自我”而导致的错误。分配属性时。原因2:根据我的实验,Instruments中的Leak Analyzer难以检测具有默认名称的泄漏属性。

3 /切勿直接在属性上使用保留或释放(或仅在非常特殊的情况下)。在您的dealloc中,只需将它们分配为零即可。保留属性旨在自行处理保留/释放。您永远不会知道设置器是否不是例如添加或删除观察者。您应该仅在其setter和getter内部直接使用该变量。

观看次数

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

2 /不要尝试通过减少视图数量来优化视图。不要仅在您的代码中创建UIImageView而是因为要向其中添加子视图。请改用UIImageView作为背景。视图框架可以处理数百个视图而不会出现问题。

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

4 /在viewDidUnload中释放所有IBOutlets

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

记忆

1 /创建对象时自动释放它们。许多错误是由于将发布调用移至一个if-else分支或return语句之后引起的。仅在特殊情况下才应使用释放而不是自动释放,例如,在等待运行循环并且您不希望对象过早地自动释放时。

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

注释

1 /使您的代码自动记录在案。每个变量名和方法名都应说明其作用。如果代码编写正确(您需要进行大量练习),则无需任何代码注释(与文档注释不同)。算法可能很复杂,但是代码应该总是很简单。

2 /有时,您需要发表评论。通常用来描述不明显的代码行为或黑客行为。如果您觉得必须编写注释,请首先尝试重写代码,以使其更简单并且不需要注释。

缩进

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;

还有很多

维护自己的编码标准文档并经常进行更新。尝试从您的错误中学习。了解为什么会创建错误,并尝试使用编码标准来避免它。

目前,我们的编码标准大约有20页,混合了Java编码标准,Google Obj-C / C ++标准和我们自己的新增内容。记录您的代码,在正确的地方使用标准的标准缩进,空格和空白行等。


9

更具功能性

Objective-C是一种面向对象的语言,但是Cocoa框架支持功能样式,并且在许多情况下被设计为功能样式。

  1. 变异性是分离的。使用不可变类作为主要对象,使用可变对象作为辅助对象。例如,主要使用NSArray,仅在需要时使用NSMutableArray。

  2. 有纯函数。并不是很多,购买的框架API的设计就像纯函数一样。查看诸如CGRectMake()或的函数CGAffineTransformMake()。显然指针形式看起来更有效。但是,带有指针的间接参数不能提供无副作用的功能。尽可能纯粹地设计结构。分离偶数状态对象。在将值传递给其他对象时使用-copy代替-retain。因为共享状态可以静默地影响其他对象中价值的变异。因此不能无副作用。如果您有来自object的external值,请复制它。因此,将共享状态设计得尽可能小也很重要。

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

  1. 懒惰的评价。看到像-[UIViewController view]财产。创建对象时将不会创建视图。呼叫者view首次读取属性时将创建它。UIImage在实际绘制之前不会加载。有许多类似这种设计的实现。这种设计对资源管理非常有帮助,但是如果您不了解惰性评估的概念,那么理解它们的行为就不容易了。

  2. 有关闭。尽可能使用C块。这将大大简化您的生活。但是在使用它之前,请再次阅读有关块内存管理的信息。

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


2
对于3),我将提出相反的方法:尽可能使用手动保留/释放!谁知道此代码将如何使用-如果将其用于紧密循环,可能会不必要地消耗您的内存。
Eiko

@Eiko只是过早的优化,不能作为一般指导。
恩尼尔(Eonil)2011年

1
我认为这是更多的设计工作,尤其是在处理模型类时。我认为增加内存是一个副作用,而这并不是我想要经常出现的东西。更糟糕的是,另一个使用我的代码的开发人员没有机会,只能将昂贵的调用包装到自动释放池中(如果可能的话-我的对象可能会发送到其他一些库代码)。这些问题以后很难诊断,但首先要避免的是廉价的。如果复制/自动释放传入的对象,则如果它们比预期的要大得多,则可能会丢失。不过,我对GUI代码更加放松。
Eiko

我同意@Eiko autorelease通常会保留更长的retain/release内存,在这种情况下,手动可以减少内存消耗。但是,它应该作为特殊情况优化的指南(即使您总是感觉不到!),也不是将过早优化归纳为实践的原因。实际上,您的建议与我并不相反。我提到它是
出于
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.