构建现代化的 Objective-C 已翻译 100%

isaced 投递于 2014/02/14 15:44 (共 8 段, 翻译完成于 02-23)
阅读 3437
收藏 86
4
加载中

当学习一个新技能时,比如编程语言,我们经常为了能运行,而把所有能用的都揉合在一起。再后来,我们回归到这些习惯,并进行重新估计,采用社区中的最佳实践并写出更好、更有结构化的代码。

最近,Objective-C语言收到了过多的新特性,但社区的最佳实践还没持续更新。这就超出了“风格”的范畴,进入了“结构”的领域。

我在最近一段时间里,审视了我自己的代码实践,评估了我可以在哪里做的更好,所以我想我应该和你们分享我的发现。

欢迎光临现代Objective-C。

Ley
Ley
翻译于 2014/02/21 09:22
2

访问实例变量(Instance Variable)

唉,实例变量。从何说起呢。一句话,实例变量很糟。如果你会这样写:

@interface MyClass : NSObject {
    BOOL someVariable;
}

@end

别这么写了。现在就改。

不要再声明实例变量了,尤其别声明在头文件里。你应该把它们声明为property,然后用消息机制或者“.”来访问它们。

之前说过为什么通过实例变量来访问property没有明显的益处。事实上,通过getter/setter方法来访问有几个优势。

  • 一致性: 你不需要再去怀疑getter/setter方法有没有副作用了,假如有的话,也肯定早就会被人注意到。

  • Debug: 你可以简便地在getter/setter方法上设置断点,而不用在运行时针对实例变量的内存地址设置watchpoint。

戴仓薯
翻译于 2014/02/21 23:44
2

真的,没有理由再去声明实例变量、再去直接访问这些实例变量形式的属性了——除非是在本身覆盖(override) getter/setter的方法里,或者在initializer/dealloc 方法里,取决于你想要代码有多少防御性(感谢 Bryan 提供链接)。再除非就是习惯了,但你应该改掉这个习惯。我就改了。

更新: 我找到了官方文档的一个链接,建议不要在dealloc中调用getter/setter方法。供参考。

那么只读的属性怎么办呢?既然没有setter方法,你不还是需要访问实例变量吗?好问题。这引出了本文的下一点。

戴仓薯
翻译于 2014/02/22 00:25
2

在头文件中定义readonly属性

在你public的接口中要暴露属性或组件,使用readonly属性是一个很好的方式。但是如果不直接访问实例变量,怎么设置它们的值呢?答案是在 .m 文件中定义一个private的class extension。

在你的头文件中,声明如下:

@interface MyClass : NSObject

@property (nonatomic, readonly) Type propertyName;

@end

然后,在类的 implementation 文件中,在以上定义的基础上,再定义如下:

@interface MyClass ()

// Private Access
@property (nonatomic, strong, readwrite) Type propertyName;

@end

这样你就定义了 public 的 getter 和 private 的 setter。可喜可贺!现在你不需要再访问实例变量了。

Schwa 补充了一条建议:

@ashfurrow 回复:《在头文件中定义readonly属性》。我还要加一条,不要在头文件里暴露可变(mutable)对象。顺便赞一下,很好的文章。

— Jonathan Wight (@schwa) January 24, 2014
戴仓薯
翻译于 2014/02/22 00:41
3

合理地定义 BOOL 类型的属性

定义属性时,遵从Apple官方指南总是没错的。但我承认,我也不总是看得那么勤快。要记住,定义BOOL类型的属性时,要同时手动定义一个getter方法。

@property (nonatomic, assign, getter = isSomething) BOOL something;

如非必要,不要把#import写在头文件里

我经常在Objective-C新手写的代码里看到这种情况。总体来说,问题在于大多数的#import语句应该只写在 .m 文件里,而不应该写在 .h 头文件里。

如下面的例子。

#import "MyOtherClass.h"

@interface MyClass : NSObject

@property (nonatomic, strong) MyOtherClass property;

@end

你可以改写为如下代码,然后在类的 implementation 文件 yourMyClass.m 中再去 #import MyOtherClass.h 头文件。

@class MyOtherClass;

@interface MyClass : NSObject

@property (nonatomic, strong) MyOtherClass property;

@end

@class MyOtherClass的写法是类的前向声明(forward class declaration)

这样写的益处良多。用类的前向声明来代替 #import 头文件可以提高编译速度,可以避免循环#import,还可以让你的头文件更轻盈——本该如此。

戴仓薯
翻译于 2014/02/22 01:00
1

只有一个例外:继承某个自己实现的类时,需要 #import 父类的头文件。

现在,咱们来谈谈一个显而易见的问题吧:你不需要在头文件里 #import Foundation 和 UIKit 的头文件, 几乎永远不需要。没错—— Apple 在 Xcode 中提供的模板类是错的

我是从 Mike Lee 那儿学到这个小技巧的。有空读读他的 style guide 吧,写得很棒。

我们来看看预编译头 (namedAppName-Prefix.pch),会发现什么呢?

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

啊哈!反正事实已经默认 #import 过它们了,所以没必要再 #import 它们第二遍。

除非你是在编写 framework 或者其他的第三方组件,否则大多数头文件里的 #import 都是没有必要的。

更新: Steve Streza 指出预编译头实际上本来不是这样用的。我不一定百分之百同意,但还是想让你们了解情况之后再选择。

@ashfurrow pch 文件是用来优化的。项目应该没有它也可以通过编译。所以还是保留 Foundation 的 import 吧,如果你的代码依赖它的话。

— Derpy Streza (@SteveStreza) January 21, 2014
戴仓薯
翻译于 2014/02/22 13:30
1

为 #import 语句分组

这一点我们都时不时遇到过。有多少次你写了一个(也许是)长长的 implementation 文件,十几、二十个 #import 语句随意堆在开头?唉呀唉呀唉呀。没关系!我们来帮忙解决。

#import 的顺序是否重要尚存争议,因此我们先不说它。确定重要的是你要为它们分组。然后为每一组添加注释。

// Frameworks
#import <QuartzCore data-preserve-html-node="true"/QuartzCore.h>

// Views
#import "ASHButton.h"
#import "ASHUserView.h"

// View Controllers
#import "ASHOtherViewController.h"
#import "ASHThisViewController.h"

不要等到一团乱麻再去收拾——现在就开始整理!以后再往里添加就很轻松了,也让你的代码保持整洁。

戴仓薯
翻译于 2014/02/22 15:28
1

不要用 #define 来定义常量

此话一出,定是一片哗然。但是常量的关键在于,嗯,它们是。名称不变,值也不变。当你 #define 一个常量,它会定义在其他每一个编译器检查到的文件里,直到遇到#undef(如果有的话)。呃。这感觉可一点都不像常量,倒像是全局变量之类的东西。

不要这样做。

取而代之的做法是:将常量在头文件里声明为 extern 变量,同时在相应的 implementation 文件中定义。

在头文件中:

extern const CGFloat ASHHeaderViewHeight;

然后,在 implementation 中,

const CGFloat ASHHeaderViewHeight = 44.0f;

现在你的常量可以对任何需要的人可见(他们只需 #import 头文件),但对程序的其他部分都不可见。它也不可能被 #undef ,在别处再重新 define (明显的代码坏味道)。

注:Michael 指出,严格地说,我们应该用 FOUNDATION_EXPORT 来代替 extern 。

合理地命名常量

我不在乎你给常量加的前缀是 'k' 、是类名还是一个随便什么前缀,但是要确保一致。并且要有描述性。否则,随着你的代码库越来越大,早晚会产生命名冲突的问题。这一点相信我,不会有错。

戴仓薯
翻译于 2014/02/22 15:59
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(10)

leekelby
leekelby
感谢翻译!

但是:
翻译得都不通顺...
并且各种引用、链接、分隔线...就是排版太差,影响阅读
戴仓薯
戴仓薯

引用来自“姜鹏”的评论

直接看《Effective Objective-C 2.0》得啦

马上买了一本,谢谢推荐~
JP311
JP311
直接看《Effective Objective-C 2.0》得啦
Redding
Redding

引用来自“戴仓薯”的评论

引用来自“isaced”的评论

引用来自“戴仓薯”的评论

虽然翻译了本文的大半部分,但是对原作者的部分观点还是不太理解(准确来说比较惊讶)。如有错译、更好的译法或理解不到位的地方,欢迎指出。谢谢。

有什么比较惊讶?

嗯,比如说之前看到很多代码,包括书上的、开源库的例程,甚至一些官方的例程吧,都有用他所说的“实例变量(instance variable)”的。在其他面向对象的语言里,实例变量不是很寻常吗。也没有看出这样写有什么明显的坏处啊。

另外也看到有很多用#define来定义常数的,当然这一点可能确实有更好的写法。

objC的实例变量和其他语言的实例变量不太一样,脱离了具体语言看这个,没有意义
威廉打个
威廉打个
非常好的一篇文章,其中建议不要在dealloc中调用getter/setter方法,我曾用一个上午的时间去debug这个issue。
gzwxn
gzwxn
使用#define来定义常量?C应该也有enum吧。
kenping
kenping
#define那里不太同意,因为#define虽然降低了编译效率,但是提高了执行效率
戴仓薯
戴仓薯

引用来自“isaced”的评论

引用来自“戴仓薯”的评论

虽然翻译了本文的大半部分,但是对原作者的部分观点还是不太理解(准确来说比较惊讶)。如有错译、更好的译法或理解不到位的地方,欢迎指出。谢谢。

有什么比较惊讶?

嗯,比如说之前看到很多代码,包括书上的、开源库的例程,甚至一些官方的例程吧,都有用他所说的“实例变量(instance variable)”的。在其他面向对象的语言里,实例变量不是很寻常吗。也没有看出这样写有什么明显的坏处啊。

另外也看到有很多用#define来定义常数的,当然这一点可能确实有更好的写法。
isaced
isaced

引用来自“戴仓薯”的评论

虽然翻译了本文的大半部分,但是对原作者的部分观点还是不太理解(准确来说比较惊讶)。如有错译、更好的译法或理解不到位的地方,欢迎指出。谢谢。

有什么比较惊讶?
戴仓薯
戴仓薯
虽然翻译了本文的大半部分,但是对原作者的部分观点还是不太理解(准确来说比较惊讶)。如有错译、更好的译法或理解不到位的地方,欢迎指出。谢谢。
返回顶部
顶部