iOS设计模式之类族(class cluster)
类族模式在UIKit(user interface framework)使用的范围已经远远超过我们的想象,比如,UIButton,NSArray,NSString,NSNumber等,
例如NSNumber类
做iOS开发的朋友们一定用过NSNumber的numberWith…方法。但大家有可能都不知道NSNumber这样的方法调用返回的不是NSNumber类本身的对象,这正是Objective-C类族的微妙之处。
如上图所示,Number的概念很大。而实际上NSNumber实际上是有很多隐藏的子类的,而我们通过NSNumber的numberWith…方法得到的对象正是其子类的对象,但对于使用者几乎可以不必知道这一点,只要知道它是一个NSNumber对象就OK了。
“Simple Concept and Simple Interface”,这正是苹果设计类族的初衷,也是类族的优点所在。可以想象我们要用整数作为参数拿到一个NSNumber对象和一个布尔值参数拿到的NSNumber对象是不同的,这略微有些类似于switch逻辑(虽然是通过不同的方法),根据不同的条件提供不同的子类对象,而这一切都集中声明在公共接口类NSNumber中。通过[NSNumber numberWithInt:2]和[NSNumber numberWithBool:YES]得到的对象对应的类,一个是__NSCFNumber,另一个是__NSCFBoolean。
例如创建按钮时调用的
+ (instancetype)buttonWithType:(UIButtonType)buttonType;
这个方法创建的对象的类型取决于传入的按钮类型buttonType
。返回的对象都继承于同一个基类UIButton
。
当各个子类在外部表现相同,内部实现不同时,我们就可以使用类簇模式去实现它。这样做我们无需使用对应的子类,只需要使用基类即可。
首先我们先来看这两个函数:
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
第一个函数的意思是:接收者是否是aClass类的实例或者从这个类继承的任何类的实例。如果是返回yes。
第二个函数的意思是:接收者是否是aClass的实例,如果是返回yes。
按照上面的解释我们执行下面两行代码输出结果应该是:isKindOfClass
,isMemberOfClass
NSArray*array = [NSArray new];
if ([array isKindOfClass:[NSArray class]]) {
NSLog(@"isKindOfClass");
}
if ([array isMemberOfClass:[NSArray class]]) {
NSLog(@"isMemberOfClass");
}
但是上面代码执行的输出结果却是:isKindOfClass。
为什么会出现这种情况?isMemberOfClass为什么没有执行?array明明是NSArray的一个实例呀!难道不是吗?为了验证我们的怀疑我们在执行下面一段代码看看array到底是什么
NSLog(@"%@",[[array class] debugDescription]);
NSLog(@"%@", [[[NSArray arrayWithObject:@"a,b"] class] debugDescription]);
输出:NSArray0,NSArrayI
好吧,array果然不是NSArray的一个实例,但是上面输出了“isKindOfClass,这说明array是NSArray子类的一个实例,到底这种推断正不正确?我们再来验证一下:
NSLog(@"%@",[[array superclass] debugDescription]);
输出:NSArray
好吧,果然是这样,写到这儿我们已经大致明白了,通过不同方法实例化的array都是NSArray子类的实例,NSArray是一个抽象的基类。这种模式就是类族模式。
但是如果把NSArray换成UIButton又回出现截然相反的情况,出现这种情况的原因在于NSArray基类又对子类进行了重构,生成了多个子类。
举个例子,我们要写个刷新控件,实现下拉刷新,上拉加载功能。
定义抽象基类
typedef enum {
RefreshStateBeginRefreshing,//开始刷新
RefreshStateEndRefreshing//结束刷新
} RefreshState;
/**
控件类型
*/
typedef enum {
RefreshHeaderType,
RefreshFooterType
} RefreshViewType;
@interface XZHRefreshView : UIView
- (void)addRefreshTarget:(id)target action:(SEL)action forRefreshState:(RefreshState)state;
+ (XZHRefreshView *)refreshViewWithType:(RefreshViewType)type;
@end
@implementation XZHRefreshView
+ (XZHRefreshView *)refreshViewWithType:(RefreshViewType)type {
if (type == RefreshHeaderType) {
return [XZHRefreshHeaderView header];
} else if (type == RefreshFooterType) {
return [XZHRefreshFooterView footer];
} else {
return nil;
}
}
- (void)addRefreshTarget:(id)target action:(SEL)action forRefreshState:(RefreshState)state {
//子类实现
}
@end
每个实体子类,都是继承于基类:
@interface XZHRefreshHeaderView : XZHRefreshView
@end
@implementation XZHRefreshView
- (void)addRefreshTarget:(id)target action:(SEL)action forRefreshState:(RefreshState)state {
//code
}
@end
举个例子我们自己写一个类族,定义一个继承与NSObject的抽象基类LULEmployee,基类中定义一个枚举类型
LULEmployee.h 代码
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger,LULEmployeeType) {
WJUEmployeeTypeDevlopers,
WJUEmployeeTypeProducters,
};
@interface WJUEmployee : NSObject
+(WJUEmployee*)employeeWithType: (WJUEmployeeType)type;
-(void)doADayWork;
@end
WJUEmployee.m代码
#import "WJUEmployee.h"
#import "WJUEmployeeTypeDevloper.h"
#import "WJUEmployeeTypeProducter.h"
@implementation WJUEmployee
+(WJUEmployee*)employeeWithType: (WJUEmployeeType)type{
switch (type) {
case 0:
return [WJUEmployeeTypeDevloper new];
break;
case 1:
return [WJUEmployeeTypeProducter new];
break;
default:
break;
}
}
-(void)doADayWork{
}
@end
WJUEmployeeTypeDevloper.h代码
#import "WJUEmployee.h"
@interface WJUEmployeeTypeDevloper : WJUEmployee
@end
WJUEmployeeTypeDevloper.m代码
#import "WJUEmployeeTypeDevloper.h"
@implementation WJUEmployeeTypeDevloper
-(void)doADayWork{
[super doADayWork];
NSLog(@"%@",[[self class] description]);
}
@end
同理WJUEmployeeTypeProducter一样
然后创建3个继承与基类的子类,并在子类中覆写-(void)doADayWork方法;
类族模式最大的好处:
- 可以隐藏抽象基类背后的复杂细节,
- 程序员不需要记住各种类型的创建对象所需要的具体类,简化开发人员开发成本,提高开发效率.
- 使用者只需调用基类简单的方法就可以返回不同的子类实例。
类族缺点
- 类族的一个缺点也显现出来,那就是已有的类族不好扩展(比如你想NSNumber再多支持一种情况,这个恐怕很难。好在这些系统的类库已经将大部分可能都做进去了,考虑得比较完善,通常你只是去用就可以了。)
注意事项
1.所有子类应该继承类簇的抽象基类
2.子类应该覆写父类需要覆写的方法。
3.使用类簇时要完善开发文档,避免团队成员错误使用。