什么时候应该要使用Category
如果想要扩充某个class 功能,增加新的成员变量与method,我们又没有这个class 的代码,正规作法就是继承、建立新的subclass。那,我们需要在不用继承,就直接增加method这种作法的重要理由,就是我们想要扩充的class 很难继承。
我能想到的,大概有几种状况:
- Foundation 对象
- 用Factory Method Pattern 实例化的对象
- Singleton 对象
- 在Project中出现次数已经多不胜数的对象。
Foundation 物件
Foundation 里头的基对象,像是NSString、NSArray、NSDictionary 等Class 的底层实现,除了可以透过Objective-C的介面调用之外,也可以透过另外一个C 的介面,叫做Core Foundation,像NSString 其实会对应到Core Foundation 里头的CFStringRef,NSArray 对应到CFArrayRef,而你甚至可以直接把Foundation 对象cast成Core Foundation 的型别,当你遇到一个需要传入CFStringRef 的function的时候,只要建立NSString 然后cast 成CFStringRef 传入就可以了。
所以,当你使用alloc、init 产生一个Foundation对象的时候,其实会得到一个同时有Foundation 与Core Foundation 实现的subclass,而实际产生出来的对象,往往与你的认知会有很大的差距,例如,我们以为NSMutableString 继承自NSString,但建立NSString ,调用alloc、init 的时候,我们真正拿到的是__NSCFConstantString,而建立NSMutableString ,拿到__NSCFString,而__NSCFConstantString 其实继承自__NSCFString!
我们来写点代码检查Foundation 物件其实属于哪些Class:
#define CLS(x) NSStringFromClass([x class])
NSLog(@"NSString:%@", CLS([NSString string]));
NSLog(@"NSMutableString:%@", CLS([NSMutableString string]));
NSLog(@"NSNumber:%@", CLS([NSNumber numberWithInt:1]));
#undef CLS
执行结果: :
NSString:__NSCFConstantString
NSMutableString:__NSCFString
NSNumber:__NSCFNumber
因此,当我们尝试建立Foundation 对象的subclass 之后,像是继承NSString,建立我们自己的MyString,假如果我们并没有override 原本关于建立instance 的method,我们也不能保证,建立出来的就是MyString 的instance。
用Factory Method Pattern 实现的对象
Wikipedia 上对Factory Method Pattern 的解释是:
...the factory method pattern is a creational pattern which uses factory methods to deal with the problem of creating objects without specifying the exact class of object that will be created.
翻译成中文:Factory Method Pattern是一套用来解决不用特别指定是哪个class,就可以建立对象的方法。比方说,某个class底下,其实有一堆subclass,但对外部来说并不需要确实知道这些subclass而是只要对最上层的class,输入指定的条件,就会从挑选一个符合指定条件的subclass、建立instance回调。
在UIKit中,UIButton就是个好例子。我们在建立UIButton对象的时候,并不是调用init
或是initWithFrame:
,而是调用UIButton的class method:buttonWithType:
,透过传递按钮的type建立按钮物件。在大多数状况下,会回传UIButton对象,但假如我们传入的type是UIButtonTypeRoundedRect
,却会回传继承自UIButton的UIRoundedRectButton
。
检查一下:
#define CLS(x) NSStringFromClass([x class])
NSLog(@"UIButtonTypeCustom %@",
CLS([UIButton buttonWithType:UIButtonTypeCustom]));
NSLog(@"UsIButtonTypeRoundedRect %@",
CLS([UIButton buttonWithType:UIButtonTypeRoundedRect]));
#undef CLS
输出结果: :
UIButtonTypeCustom UIButton
UIButtonTypeRoundedRect UIRoundedRectButton
我们想要扩充UIButton
,但拿到的却是UIRoundedRectButton
,而UIRoundedRectButton
却无法继承,因为这个对象不在公开的header中,我们也不能够保证以后传入UIButtonTypeRoundedRect
就一定会拿到UIRoundedRectButton
。如此一来,就造成我们难以继承UIButton
。
或这么说:假使今天我们的需求是想要改动某个上层的class,让底下所有的subclass也都增加了一个新的method,我们又无法改动这个上层class的程式,就会采用category。比方说,我们今天希望所有的UIViewController
都有一个新method,如此我们整个应用程式中每个UIViewController
的subclass都可以呼叫这个method,但,我们就是无法改动UIViewController
。
单例对象
单例对象是指:某个class只有、也只该有一个instance,每次都只对这个instance操作,而不是建立新的instance。像UIApplication、 NSUserDefault、NSNotificationCenter以及Mac OS X上的NSWorkSpace等,都采用singleton设计。
之所以说singleton 物件很难继承,我们先来看怎么实作singleton:我们会有一个static的对象,然后每次都回传这个对象。定义部分如下:
@interface MyClass : NSObject
+ (MyClass *)sharedInstance;
@end
实现部分:
static MyClass *sharedInstance = nil;
@implementation MyClass
+ (MyClass *)sharedInstance
{
return sharedInstance ?
sharedInstance :
(sharedInstance = [[MyClass alloc] init]);
}
@end
其实现在Singleton大多会使用GCD的dispatch_once
实现,但是在我们还没有提到GCD之前,我们先使用这样的写法。我们会在讨论Threading的时候继续讨论GCD,至于用GCD实现单例Singleton的细节,请参见再谈Singleton这一章。
我们如果继承了MyClass,却没有override掉sharedInstance
,那么,sharedInstance
回传的还是MyClass的singleton instance。而想要override掉sharedInstance
又不见得这么简单,因为这个method里头很可能又做了许多其他事情,很可能会把一些initiailize时该做的事情,反而放在这边做(这不是很好的作法,但就是可能发生)。例如MyClass可能这么写:
+ (MyClass *)sharedInstance
{
if (!sharedInstance) {
sharedInstance = [[MyClass alloc] init];
[sharedInstance doSomething];
[sharedInstance doAnotherThine];
}
return sharedInstance;
}
如果我们并没有MyClass的实现代码,这个class是在其他的library或是framework中,我们直接override了sharedInstance
,就很有可能有事情没做,而产生不符合预期的结果。
在Project中出现次数已经多不胜数
随着Project不断成长,某些class已经频繁使用到了,而我们现在需求改变,必须要增加新的method,我们却也没有力气可以把所有用到的地方统统换成新的subclass。Category 就是解决这种状况的救星。