盒子
导航
文章目录
  1. 一、基本语法
  2. 二、进阶解释
  3. 三、避免冲突
  4. 四、其他作用
  5. 五、举栗子🌰
  6. 六、总结
  7. 七、参考资料

Objective-C基础:Category

Learn Objective-C with 《Programming with Objective-C》。如果你需要向已有的类当中增加方法,那么Category将是你最好的选择。Test IDE: Xcode7。

一、基本语法

Category的基本语法如下:

//————-ClassName.h———————
@interface ClassName (CategoryName) //Category声明
//方法声明
@end

//————-ClassName.m———————
@implementation ClassName (CategoryName) //Category实现
//方法实现
@end

二、进阶解释

  1. 可以使用Category来为任何类“插入”新的方法,通过Category添加的方法与直接在原生类实现中的方法在运行时效果是一样的。
  2. Category一旦声明,便可对类以及该类的所有子类的实例产生影响,即这些类中都将“拥有”该Category中的方法(通过#import该头文件来使用,不导入就使用将导致编译警告和错误),在该类及其子类中使用Category中的方法与使用原生方法是一样的。
  3. 使用Category来新增类方法和实例方法都是可行的,但是无法新增属性。事实上从语法上来说可以声明属性,但是无法在其中增添实例变量,即编译器不会为属性合成实例变量、getter和setter访问器方法,你可以自己实现这些方法,但是它的值不会被运行时环境所追踪,总结起来就是不支持通过Category向已有类中添加属性。
  4. 向已有类中新增属性的唯一方法是使用类扩展(Class Extension)。

三、避免冲突

由于新增的方法会添加到原先的类当中,所以在方法命名上必须严格避免与原有方法产生冲突。

  1. 一种情况是新增加的方法名与SDK库中已有的方法名产生了冲突,另一种是后来升级的SDK库中新增的方法与你在旧SDK库中使用Category新增的方法产生冲突。
  2. 如果新方法名称与原生方法或者已经添加到该类的其他Category方法产生冲突,那么调用该方法在运行时的行为将会是未知(运行时环境不知道选用哪个方法,但将会有一个方法“胜出”,该胜出的方法才会被添加到这个类中。: 文档说运行时行为是未知,但在Xcode7下测试发现好像Category的方法优先级更高,也有书籍提到Category新增的方法优先级更高),事实上在Xcode(7)当中会警告你Category实现的这个方法在原生类当中也将会有一个实现,忽略警告继续测试,发现运行时调用的是Category方法。
  3. 为了避免冲突,在向SDK库中的类新增方法时最好使用前缀来标识方法名称,就像类的取名一样。

四、其他作用

  • 可以利用分类分散实现代码

例如AppKit中的NSWindow类,该类有数百个方法,但它通过逻辑划分,将不同实现放置到不同类别里面,如:

@interface NSWindow : NSResponder
@interface NSWindow(NSKeyBoardUI)
@interface NSWindow(NSToolbarSupport)
@interface NSWindow(NSDrag)
@interface NSWindow(NSCarbonExtensions)

  • 使用类别来创建前向引用,以使用私有方法

类当中通过类扩展在实现文件中新增的属性和方法被认为是私有的,外部类没法在导入头文件时使用这些方法,但可以通过为该类创建一个类别,在类别中声明与私有方法同名的方法但不做实现,这样就可以调用私有方法了。Apple禁止开发者调用私有方法,如果这么做了,发布到App Store的程序将不会被通过(除非没被发现)。

五、举栗子🌰

  1. 使用Category来向NSObject当中添加一个打印Hello World的方法。
  2. 声明一个类里面有个打印消息方法,然后通过Category来新增同名方法,看看效果。

使用Xcode新建Command Line工程。下面是h头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//CatTest.h
#import <Foundation/Foundation.h>

@interface NSObject (CatTest)
- (void)printHelloWorld;
@end

@interface MyClass : NSObject
- (void)printMsg;
@end

@interface MyClass(Cat)
- (void)printMsg;
@end;

下面是m实现文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//CatTest.m
#import "CatTest.h"

@implementation NSObject (CatTest) //这里除关键字外与头文件声明是一样的
- (void)printHelloWorld{
NSLog(@"Hello World! This function is added to NSObject using Category!");
}
@end

@implementation MyClass
- (void)printMsg
{
NSLog(@"This message is in the primary MyClass");
}
@end

@implementation MyClass (Cat)
- (void)printMsg
{
NSLog(@"This message is in the Category of MyClass");
}
@end

以下是main.m测试文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//需要导入声明Category的头文件,不然就报错了
#import <Foundation/Foundation.h>
#import "CatTest.h"
int main(int argc, char *argv[]){
@autoreleasepool{
//注意,工程默认启用了ARC,所以后面不需要自己release

//测试01
NSObject *obj = [[NSObject alloc] init];
[obj printHelloWorld];

//测试02
MyClass *cls = [[MyClass alloc] init];
[cls printMsg];
}
return 0;
}

我在Xcode7下面测试,结果是输出MyClass Category中的方法。

六、总结

不过既然官方文档说使用Category声明实现同名方法的话运行时行为是未知,编译器不知道选用那个,那么就有理由相信这个“行为未知”的结论要比那个“Category中的优先级更高”的说法来得靠谱一些。Anyway,尽量避免重名的情况发生,如果需要覆盖,就使用继承。

七、参考资料

Programming with Objective-C》- Customizing Existing ClassesCategories Add Methods to Existing Classes