什么时候应该要使用Category

如果想要扩充某个class 功能,增加新的成员变量与method,我们又没有这个class 的代码,正规作法就是继承、建立新的subclass。那,我们需要在不用继承,就直接增加method这种作法的重要理由,就是我们想要扩充的class 很难继承。

我能想到的,大概有几种状况:

  1. Foundation 对象
  2. 用Factory Method Pattern 实例化的对象
  3. Singleton 对象
  4. 在Project中出现次数已经多不胜数的对象。

Foundation 物件

Foundation 里头的基对象,像是NSString、NSArray、NSDictionary 等Class 的底层实现,除了可以透过Objective-C的介面调用之外,也可以透过另外一个C 的介面,叫做Core Foundation,像NSS​​tring 其实会对应到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 就是解决这种状况的救星。

results matching ""

    No results matching ""