Objective-C 格致余论 2 - Category
本页目录
Objective-C 中的 Category 和 Swift 中的 Extension 还是挺类似的,但是仍有一些区别,所以具体了解一下,也是有必要的。
一般来说,在 C++ 或者 Java 中,如果我们想改变一个已有类型的行为,可以继承之后重写或者添加对应的方法。不过在 Objective-C 中,可以不用继承,就直接添加新的方法或替换已有的方法。
当然,前一篇文章中提到过 class_addMethod 方法,不过还是用 category 更加规范和方便。这里需要注意,正常情况下,我们只能添加新的方法,却不能增加新的变量。
什么时候用
一般来说,继承是最正规的做法,可以很方便地重用。但是也有一些情况,继承不大容易,这时候用 category 就比较合适了,比如:
- Foundation 对象
- 用工厂模式创造的对象
- 单例对象
- 在 app 中已经使用很多次的自定对象(继承的话很容易因为漏改而出错)
下面我们分别介绍这几种情况
Foundation 对象
Foundation 中的类,比如 NSString, NSArray, NSDictionary 除了可以通过 Objective-C 的接口调用外,其实也可以用另一个 C 的接口调用。例如 NSString 会对应到 Core Foundation 中的 CFStringRef。
所以在实际生成的时候,其实具体的类别是难以确定的,也就是说,我们继承了一个 NSString 类创建了一个 MyString, 新建实例的时候,其实并不能保证新建的就是一个 MyString,所以这种情况下,不是很适合用继承,而是 category 直接进行拓展比较合适。
工厂模式对象
工厂模式本身的机制就是,不用关心具体的子类,只要传入所需要的类型,会自动生成我们需要的类。
就拿 UIButton 来说,即使我们继承了,在具体生成的时候,也不能保证得到我们继承后的子类,从这个角度来看,和上一个情境是类似的。
或者说,我们想要改变一个父类的行为,让所有的子类都增加新的方法,当时实际上我们没办法改动这个父类的时候,就应该采用 category。
这种因为具体生成什么类不确定的情况,会导致很多奇奇怪怪的问题,一定要小心使用,或者直接用 Swift,真心的。
单例对象
这也是非常出名的设计模式了,比如 UIApplication, NSUserDefault, NSNotificationCenter 等都是这种设计。因为单例实现机制的问题,使其本身很难被继承,我们先来看看如何声明一个单例。
@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 实现,不过暂时为了理解简单,先用上面的写法)
假如我们继承 MyClass 之后却没有重写覆盖 sharedInstance 方法,首先可能还是会返回原来的对象,另外如果我们覆盖的话,那么可能就会漏过放在原来的实现中的一些操作(因为我们可能看不到源代码),产生与预期不符的结果。
Category 的写法
语法还是很简单的,只要记住括号里是 category 的名称即可,我们用给 NSString 添加一个 strokeCompare: 为例:
@interface NSString (CustomCompare)
- (NSComparisonResult)strokeCompare:(NSString *)anotherString;
@end
@implementation NSString (CustomCompare)
- (NSComparisonResult)strokeCompare:(NSString *)anotherString
{
NSLocale *strokeSortingLocale = [[[NSLocale alloc]
initWithLocaleIdentifier:@"zh@collation=stroke"]
autorelease];
return [self compare:anotherString
options:0
range:NSMakeRange(0, [self length])
locale:strokeSortingLocale];
}
惯例的命名方式是 NSString+CustomCompare.h 和 NSString+CustomCompare.m
除了添加新方法,以下情况也很适合用 category
- 将一个很大的类切分成几个部分,代码组织更清晰,也更易于跨平台
- 替换原来的实现,实现方法的重写,但是这种方式比较危险,推荐不要使用
Extensions
另一个类似的设计是 extensions,可以认为 extensions 是一个没有名字的 category,在 extensions 中定义的方法,需要放在原本的类的实现中,是类名后面跟一对空括号,下面是一个例子:
@interface MyClass : NSObject
@end
@interface MyClass()
- (void)doSomething;
@end
@implementation MyClass
- (void)doSomething
{
}
@end
具体有什么用呢,比方说可以:
- 拆分 header
- 管理私有方法
Swift 中的做法就更加简单粗暴,直接用类名,加上不同的关键字即可,如:
class MyClass{
}
extension MyClass {
}
在 Swift 中,extension 也可以用来扩展 protocol 和 struct,不过这里暂时先不展开。
增加变量与属性
虽然前面提到不能增加变量或者属性,但是其实还是有办法的,既然我们是用 virtual table 来记录相关的方法,同样可以用另一个表格来记录相关的变量和方法嘛,这就是 Associated Objects。具体的使用方式如下:
(原图缺失)
另外,虽然 category 不能增加成员变量,但是 extension 可以,甚至也可以直接在 @implementation 中加入。
参考资料
相关文章
- Python 编程思想:名字、对象与资源边界2026-11-06
- RESTful API 框架 HUG 指南2017-03-09
- Cpp 编码风格指南2017-01-29
- Javascript 编码风格指南2016-11-14