前言

iOS 中,类簇的使用是比较普遍的,如 NSNumberNSArrayNSString 等,属于 抽象工厂 模式的一种应用,隐藏了具体的实现类,只暴露出简单的接口。

NSNumber的类簇

这里以 NSNumber 为例。

假设我们要把 int, bool, float, double 等数据类型包装成类的形式,一般我们比较容易想到的是新建多个子类,如 NSNumberInt, NSNumberBool, NSNumberFloat, NSNumberDouble 等,加上其他一些数据类型,这样会导致有大量的子类,对开发者来说就得记住很多类名,很不友好。

Foundation 是通过 NSNumber 这个 类簇 来实现的:

1
2
3
4
5
+ (NSNumber *)numberWithInt:(int)value;
+ (NSNumber *)numberWithBool:(BOOL)value;
+ (NSNumber *)numberWithFloat:(float)value;
+ (NSNumber *)numberWithDouble:(double)value;
...

下面以 numberWithInt 为例,我们将 allocinit 拆为两条语句,并跟 numberWithInt 对比:

1
2
3
4
id obj1 = [NSNumber alloc];
id obj2 = [obj1 initWithInt:1];
id obj3 = [NSNumber numberWithInt:1];
id obj4 = [NSNumber alloc];

结果如下:

UIImage

可以看到,alloc 方法返回的是 NSPlaceholderNumber 对象,initnumberWithInt 返回的才是 __NSCFNumber 对象。

这里加多了一句:

1
id obj4 = [NSNumber alloc];

会发现 obj4 的地址跟 obj1 是一样的,说明 alloc 方法实现了 对象复用

对象复用 需要有个地方来存这些之前创建但又已经被“销毁”的对象;

这里的销毁不是真的销毁,是因为只是做记号,标记成销毁,但它实际还在“对象池”里,下次再初始化的时候,看看这个“对象池”里是否有已经创建的对象,有就直接用,没有再创建。

alloc 方法复用了 NSPlaceholderNumber 对象,那么就说明是在调用 initinitWithInt 等方法时才转化为 __NSCFNumber 的。

可以大致推测出如下方法:

1、生成静态 NSPlaceholderNumber 对象,实现对象复用:

1
2
3
4
5
6
7
static NSPlaceholderNumber *BuildPlaceholderNumber() {
static NSPlaceholderNumber *instance;
if (!instance) {
instance = [[NSPlaceholderNumber alloc] init];
}
retrun instance;
}

2、NSNumber 类的大致实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface NSNumber
@end

@implementation

+ (instancetype)alloc {
return BuildPlaceholderNumber();
}

+ (NSNumber *)numberWithInt:(int)value {
return [[NSNumber alloc] initWithInt:value];
}

@end

3、NSPlaceholderNumber 类的大致实现

1
2
3
4
5
6
7
8
9
10
11
12
@interface NSPlaceholderNumber
@end

@implementation NSPlaceholderNumber

- (instancetype)initWithInt:(int)value {
if (self == BuildPlaceholderNumber()) {
return [[__NSCFNumber alloc] initWithInt:value];
}
}

@end

4、__NSCFNumber 类的大致实现

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface __NSCFNumber: NSNumber
@end

@implementation __NSCFNumber

- (instancetype)initWithInt:(int)value {
if (self = [super init]) {
// do sth to init with int...
}
return self;
}

@end

这样就不会将其它类暴露出来,开发者只要关心 NSNumber 提供的类方法就可以愉快地构造一个 NSNumber 对象了。

自己写一个类簇

以上简单分析了 NSNumber 类簇,接下来我们自己写一个简单的类簇。

假设 CatBlackCatWhiteCat 两种,其中黑猫喜欢睡觉,白猫喜欢玩。
当然了,我家的黄猫喜欢在我睡觉的时候找我玩…

1
2
3
4
5
6
7
8
9
10
11
12
13
// Cat.h  
typedef NS_ENUM(NSInteger, CatType) {
CatTypeBlack,
CatTypeWhite
};

@interface Cat : NSObject

+ (instancetype)catWithType:(CatType)type;

- (void)showHobby;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Cat.m

// BlackCat
@interface BlackCat: Cat

@end

@implementation BlackCat

- (void)showHobby {
NSLog(@"%@'s hobby is: sleep", [self class]);
}

@end


// WhiteCat
@interface WhiteCat: Cat

@end

@implementation WhiteCat

- (void)showHobby {
NSLog(@"%@'s hobby is: play", [self class]);
}

@end


// Cat
@implementation Cat
+ (instancetype)catWithType:(CatType)type {
switch (type) {
case CatTypeBlack:
{
return [[BlackCat alloc] init];
break;
}
case CatTypeWhite:
{
return [[WhiteCat alloc] init];
break;
}
default:
break;
}
}

- (void)showHobby {

}

@end

接下来我们就不用关心 BlackCat 或是 WhiteCat 这两个类,只需要传入一个 CatType 即可:

1
2
3
4
5
Cat *cat1 = [Cat catWithType:CatTypeBlack];
[cat1 showHobby]; // print: BlackCat's hobby is: sleep

Cat *cat2 = [Cat catWithType:CatTypeWhite];
[cat2 showHobby]; // print: WhiteCat's hobby is: play

类簇的其它应用

1、假设需要在 iOS6iOS7 系统分别使用不同风格的图片,一般人是直接根据当前系统判断,这样会导致很多个 if else 语句,后期代码会很杂乱。

这时可以利用类簇,写一个基类(如 DemoImageView)及私有子类(DemoImageView_iOS6DemoImageView_iOS7),统一在基类里去根据系统判断生成不同的子类即可(具体可以看这篇文章,这里不再详述)。

2、假设有2个页面,布局都一样,只是传入的数据不一样而已,这时也可以考虑使用类簇来解决。

总结

通过这次实际分析,对类簇这种设计模式有一种更深的了解。

另外也希望以后能在项目中熟练地运用各种设计模式,写出更加优雅的代码。

References From Sunny大神


2016-10-25 21:51
Aevit
深圳南山



摄影:Aevit 2016年8月 茶卡盐湖