唉,实例变量。从何说起呢。一句话,实例变量很糟。如果你会这样写:
@interface MyClass : NSObject { BOOL someVariable; } @end
别这么写了。现在就改。
不要再声明实例变量了,尤其别声明在头文件里。你应该把它们声明为property,然后用消息机制或者“.”来访问它们。
我之前说过为什么通过实例变量来访问property没有明显的益处。事实上,通过getter/setter方法来访问有几个优势。
一致性: 你不需要再去怀疑getter/setter方法有没有副作用了,假如有的话,也肯定早就会被人注意到。
Debug: 你可以简便地在getter/setter方法上设置断点,而不用在运行时针对实例变量的内存地址设置watchpoint。
真的,没有理由再去声明实例变量、再去直接访问这些实例变量形式的属性了——除非是在本身覆盖(override) getter/setter的方法里,或者在initializer/dealloc 方法里,取决于你想要代码有多少防御性(感谢 Bryan 提供链接)。再除非就是习惯了,但你应该改掉这个习惯。我就改了。
更新: 我找到了官方文档的一个链接,建议不要在dealloc中调用getter/setter方法。供参考。
那么只读的属性怎么办呢?既然没有setter方法,你不还是需要访问实例变量吗?好问题。这引出了本文的下一点。
在你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
定义属性时,遵从Apple官方指南总是没错的。但我承认,我也不总是看得那么勤快。要记住,定义BOOL类型的属性时,要同时手动定义一个getter方法。
@property (nonatomic, assign, getter = isSomething) BOOL something;
我经常在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,还可以让你的头文件更轻盈——本该如此。
只有一个例外:继承某个自己实现的类时,需要 #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
这一点我们都时不时遇到过。有多少次你写了一个(也许是)长长的 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"
不要等到一团乱麻再去收拾——现在就开始整理!以后再往里添加就很轻松了,也让你的代码保持整洁。
此话一出,定是一片哗然。但是常量的关键在于,嗯,它们是常量。名称不变,值也不变。当你 #define 一个常量,它会定义在其他每一个编译器检查到的文件里,直到遇到#undef(如果有的话)。呃。这感觉可一点都不像常量,倒像是全局变量之类的东西。
不要这样做。
取而代之的做法是:将常量在头文件里声明为 extern 变量,同时在相应的 implementation 文件中定义。
在头文件中:
extern const CGFloat ASHHeaderViewHeight;
然后,在 implementation 中,
const CGFloat ASHHeaderViewHeight = 44.0f;
现在你的常量可以对任何需要的人可见(他们只需 #import 头文件),但对程序的其他部分都不可见。它也不可能被 #undef ,在别处再重新 define (明显的代码坏味道)。
注:Michael 指出,严格地说,我们应该用 FOUNDATION_EXPORT 来代替 extern 。
我不在乎你给常量加的前缀是 'k' 、是类名还是一个随便什么前缀,但是要确保一致。并且要有描述性。否则,随着你的代码库越来越大,早晚会产生命名冲突的问题。这一点相信我,不会有错。
评论删除后,数据将无法恢复
评论(10)
但是:
翻译得都不通顺...
并且各种引用、链接、分隔线...就是排版太差,影响阅读
引用来自“姜鹏”的评论
直接看《Effective Objective-C 2.0》得啦
引用来自“戴仓薯”的评论
引用来自“isaced”的评论
引用来自“戴仓薯”的评论
虽然翻译了本文的大半部分,但是对原作者的部分观点还是不太理解(准确来说比较惊讶)。如有错译、更好的译法或理解不到位的地方,欢迎指出。谢谢。
另外也看到有很多用#define来定义常数的,当然这一点可能确实有更好的写法。
引用来自“isaced”的评论
引用来自“戴仓薯”的评论
虽然翻译了本文的大半部分,但是对原作者的部分观点还是不太理解(准确来说比较惊讶)。如有错译、更好的译法或理解不到位的地方,欢迎指出。谢谢。
另外也看到有很多用#define来定义常数的,当然这一点可能确实有更好的写法。
引用来自“戴仓薯”的评论
虽然翻译了本文的大半部分,但是对原作者的部分观点还是不太理解(准确来说比较惊讶)。如有错译、更好的译法或理解不到位的地方,欢迎指出。谢谢。