了解Cocoa和Objective-C一些特性
了解Cocoa和Objective-C一些特性是本文要介绍的内容,对于Objective-C的一些特性,初学者应该好好的参考一番,文中介绍的夜很详细。不多说,我们来看内容。
1、成员变量应该定义为@private
参考代码:
@interface MyClass : NSObject { @private id myInstanceVariable_; } // public accessors, setter takes ownership - (id)myInstanceVariable; - (void)setMyInstanceVariable:(id)theVar; @end
2、明确指定初始化
注释并说明指定的初始化。
明确指定初始化对想要子类化你的类的时候时很重要的。那样,子类化时只需要做一个或多个初始化去保证初值即可。这也有助于在以后调试你的类时明了初始化流程。
3、重写指定初始化
当重写一个子类并需要init...方法,注意要重写父类的指定初始化方法。
如果你没有正确重写父类的指定初始化方法,你的初始化方法可能不会被调用,这会导致很多微妙而难以排除的错误。
4、重写NSObject的方法
强烈建议在@implementation之后就立即重写NSObject 的方法。建议重写 init...,copyWithZone:和 dealloc 方法。init...相关的方法写在一起, 接下来是 copyWithZone: ,最后是 dealloc。
5、避免调用new方法
不要调用NSObject 的new方法,也不要在子类中重写它,而是应该使用 alloc 和 init 方法来初始化retained的对象。
Objective-C代码显式调用 alloc 和 init 方法来创建和retain一个对象。new 的方法可能会带来内存上调试的麻烦。
6、初始化变量
没必要在初始化方法里把变量初始化为0或者nil,这是多余的。
所有新分配内存的对象内容都初始化为0(除了isa),所以不要在init方法里做无谓的重初始化为0的操作。
7、保持公有API简明
保持你的类简单,如果一个方法没必要公开就不要公开。使用私有类别保证公开头文件的简洁。
和C++不同,Objective-C无法区分公有私有方法,因为它全是公有的。因此,除非就是为了让用户调用所设计,不要把其他的方法放到公有API里。这样可以减少不期调用的可能性。这还包括重写父类的方法。对于那些内部实现的方法,在实现文件里使用类别而不是将方法定义在公有头文件里。
// GTMFoo.m #import "GTMFoo.h" @interface GTMFoo (PrivateDelegateHandling) - (NSString *)doSomethingWithDelegate; // Declare private method @end @implementation GTMFoo(PrivateDelegateHandling) ... - (NSString *)doSomethingWithDelegate { // Implement this method } ... @end
8、#import和#include
用#import导入Objective-C或Objective-C++头文件,用#include导入C或C++头文件
根据头文件的语言去选择合适的导入方式。
当导入的头文件使用Objective-C或Objective-C++语言时,使用#import。
当导入标准C或C++头文件时,使用#include。头文件应该使用自己的#define重加载保护。
有些Objective-C头文件没有#define重加载保护,所以只应该用#import导入。因此Objective-C头文件只应该被Objective-C源文件或其他的Objective-C头文件所导入。这种情况下全部使用#import是合适的。
标准C和C++头文件不包含任何Objective-C元素都可以被一般的C或C++文件导入。因为标准C和C++里根本没有#import,所以也只能用#include导入。在Objective-C代码中使用#include一致的导入这些头文件。
本条款有助于跨平台项目的无意错误。一位Mac开发者引入一份新C或C++头文件时可能会忘记添加#define重加载保护,因为在Mac上用#import导入文件不会引发问题,但在别的使用#include的平台就可能出问题。在所有平台一致的使用#include意味着要么全部成功要么全部失败,避免了那种一些平台上可以运作而另一些不行的情况。
#import <Cocoa/Cocoa.h> #include <CoreFoundation/CoreFoundation.h> #import "GTMFoo.h" #include "base/basictypes.h"
9、使用根框架
导入框架根的头文件而不是分别导入框架头文件
看起来从Cocoa或Foundation这些框架里导入个别的文件很不错,但实际上你直接导入框架根头文件效率更高。框架根已经被预编译故可更快的被加载。还有,记住用#import指令而不是#include导入Objective-C的框架。
#import <Foundation/Foundation.h> // good #import <Foundation/NSArray.h> // avoid #import <Foundation/NSString.h>
10、构建时即设定autorelease
当创建新的临时对象时,在同一行代码里就设定autorelease而不是写到这个方法的后面几行去
即使这样可能会造成一些轻微的延迟,但这样避免了谁不小心把release去掉,或在release之前就return而造成的内存泄露,如下:
// AVOID (unless you have a compelling performance reason) MyController* controller = [[MyController alloc] init]; // ... code here that might return ... [controller release]; // BETTER MyController* controller = [[[MyController alloc] init] autorelease];
11、优先autorelease而非retain
对象赋值时尽量采用autorelease而不是retian模式。
当把一个新创建的对象赋予一个变量的时候,第一件要做的事情就是先释放原来变量指向的对象以防止内存泄露。这里也有很多"正确的"方法去做这件事。我们选择autorelease时因为它更不倾向于出错。小心在密集的循环里可能会很快填满autorelease池,而且它也确实会降低效率,但权衡下来还是可以接受的。
- (void)setFoo:(GMFoo *)aFoo { [foo_ autorelease]; // Won't dealloc if |foo_| == |aFoo| foo_ = [aFoo retain]; }
12、以声明时的顺序dealloc处理实例变量
dealloc应该用在@interface声明时同样的顺序处理实例变量,这也有助于评审者鉴别。
代码评审者检查或修正dealloc的实现要确保所有retain的实例变量都获得了释放。
为了简化评审dealloc,将释放retain的实例变量代码保持和@interface里声明的顺序一致。如果dealloc调用了其他方法去释放实例变量,添加注释说明那些实例变量被这些方法所处理了。
13、Setters copy NSStrings
在NSString上调用Setters方法时,永远使用copy方式。永远不要retain一个字符串,这可以防止调用者在你不知到的情况下修改了字符串。不要以为你可以改变NSString的值,只有NSMutableString才能做到。
- (void)setFoo:(NSString *)aFoo { [foo_ autorelease]; foo_ = [aFoo copy]; }
14、避免抛出异常
不要@throwObjective-C的异常,不过你还是要做好准备捕获第三方以及系统调用抛出的异常。
我们的确在编译时加入了-fobjc-exceptions指令(主要是为了获得@synchronized),但我们并不@throw。当然在使用第三方库的时候是允许使用@try,@catch,以及@finally的。如果你确实使用了,请务必明确到文档中哪个方向你想抛出什么异常。
除非你写的代码想要泡在MacOS10.2或更之前,否则不要使用NS_DURING,NS_HANDLER,NS_ENDHANDLER,NS_VALUERETURNandNS_VOIDRETURN这些宏。
另外你要小心当写Objective-C++代码的时候,如果抛出Objective-C异常,那些栈上的对象不会被清理。示例:
class exceptiontest { public: exceptiontest() { NSLog(@"Created"); } ~exceptiontest() { NSLog(@"Destroyed"); } }; void foo() { exceptiontest a; NSException *exception = [NSException exceptionWithName:@"foo" reason:@"bar" userInfo:nil]; @throw exception; } int main(int argc, char *argv[]) { GMAutoreleasePool pool; @try { foo(); } @catch(NSException *ex) { NSLog(@"exception raised"); } return 0; }
将会有如下输出:
2006-09-28 12:34:29.244 exceptiontest[23661] Created 2006-09-28 12:34:29.244 exceptiontest[23661] exception raised
注意这里的析构函数永远没有机会被调用。这是在你想用栈上的智能指针比如shared_ptr,linked_ptr,还有STL对象的时候不得不关注的一个核心问题。如果你一定要在Objective-C++代码里抛出异常,那就请一定使用C++的异常。永远不要重新抛出一个Objective-C的异常,也不允许在异常块即@try,@catch,@finally里生成栈上的C++对象(比如std::string,std::vector等)。
15、nil检查
仅在校验逻辑流程时做nil检查。
使用nil检查不是为了防止程序崩溃,而是校验逻辑流程。向一个空对象发送一条消息是由Objective-C运行时处理的。方法没有返回结果,你也可以安心走下去。
注意这里和C/C++的空指针检查是完全不同的,在那些环境里,并不处理空指针情况并可能导致你的应用程序崩溃。不过你仍要自己确保提领的指针不为空。
16、 BOOL类型陷阱
整形的转换为BOOL型的时候要小心。不要直接和YES做比较。
BOOL在Objective-C里被定义为unsignedchar,这意味着它不仅仅只有YES(1)和NO(0)两个值。不要直接把整形强制转换为BOOL型。常见的错误发生在把数组大小,指针的值或者逻辑位运算的结果赋值到BOOL型中,而这样就导致BOOL值的仅取决于之前整形值的最后一个字节,有可能出现整形值不为0但被转为NO的情况。应此把整形转为BOOL型的时候请使用ternery操作符,保证返回YES或NO值。
在BOOL,_BOOL以及bool(见C++Std4.7.4,4.12以及C99Std6.3.1.2)之间可以安全的交换值或转型。但BOOL和Boolean之间不可,所以对待Boolean就像上面讲的整形一样就可以了。在Objective-C函数签名里仅使用BOOL。
对BOOL值使用逻辑运算(&&,||,!)都是有效的,返回值也可以安全的转为BOOL型而不需要ternery操作符。
- (BOOL)isBold { return [self fontTraits] & NSFontBoldTrait; } - (BOOL)isValid { return [self stringValue]; } - (BOOL)isBold { return ([self fontTraits] & NSFontBoldTrait) ? YES : NO; } - (BOOL)isValid { return [self stringValue] != nil; } - (BOOL)isEnabled { return [self isValid] && [self isBold]; }
还有,不要把BOOL型变量直接与YES比较。这样不仅对于精通C的人很有难度,而且此条款的第一点也说明了这样做未必能得到你想要的结果。
BOOL great = [foo isGreat]; if (great == YES) // ...be great! BOOL great = [foo isGreat]; if (great) // ...be great!
17、属性
属性遵循如下规则:属性是Objective-C2.0的特性,所以只能跑在iPhone以及MacOSX10.5(leopard)或更高的版本。
一个有属性关联实例变量都要在后面加下划线,而该属性的名称就是实例变量不加尾部的下划线的名字。
使用@synthesize标识以正确的重命名属性。
@interface MyClass : NSObject { @private NSString *name_; } @property(copy, nonatomic) NSString *name; @end @implementation MyClass @synthesize name = name_; @end
属性的声明必须紧接变量申明的括号后。属性的定义应该紧接@implementation模块后面。它和@interface 或者@implementation 的缩进是相同的。
@interface MyClass : NSObject { @private NSString *name_; } @property(copy, nonatomic) NSString *name; @end @implementation MyClass @synthesize name = name_; - (id)init { ... } @end
18、为NSString使用Copy属性
NSString的属性定义为copy。