类簇实际上是Foundation framework框架下的一种设计模式,它管理了一组隐藏在公共接口下的私有类
。
以NSNumber举例:
因为不同数据类型的变量在使用的时候可以互相转换类型或用字符串标识,所以我们可以用一个简单的类来管理它们。可是无论怎样,它们的存储形式都是不同的,所以使用一个类来管理它们的效率很低下。考虑到这个问题,设计了类Number。
Number是一个抽象的父类,它实现的方法主要是操作它的子类。
Number类不会直接声明一个变量去存储不同类型的数据,而是由它的子类们去创建一个对象去存储然后将实现方法隐藏,将调用接口共享给抽象父类Number。
在Cocoa中,许多类实际上是以类簇的方式实现的,即它们是一群隐藏在通用接口之下的与实现相关的类。
创建NSString对象时,实际上获得的可能是NSLiteralString、NSCFString、NSSimpleCString、NSBallOfString或者其他未写入文档的与实现相关的对象。
所以,请不要尝试去创建NSString、NSArray或NSDictionary的子类。如果必须添加或修改某个方法,可以使用类目的方式。
注:对于类簇使用isMemberOfClass和isKindOfClass是不允许的,因为类簇是由抽象公共类管理的一组私有类,抽象公共类并不是真正的实例的父类,类簇中真正的类从属关系被隐藏了,所以使用isMemberOfClass和isKindOfClass结果可能不准确。
Method Swizzling类簇
项目开发过程中,经常因为NSArray数组越界或者NSDictionary的key或者value值为nil等问题导致的崩溃,对于这些问题苹果并不会报一个警告,而是直接崩溃,感觉苹果这样确实有点“太狠了”。
由此,我们可以根据上面所学,对NSArray、NSMutableArray、NSDictionary、NSMutableDictionary等类进行Method Swizzling,实现方式还是按照上面的例子来做。但是....你发现Method Swizzling根本就不起作用,代码也没写错啊,到底是什么鬼?
这是因为Method Swizzling对NSArray这些的类簇是不起作用的。因为这些类簇类,其实是一种抽象工厂的设计模式。抽象工厂内部有很多其它继承自当前类的子类,抽象工厂类会根据不同情况,创建不同的抽象对象来进行使用。例如我们调用NSArray的objectAtIndex:方法,这个类会在方法内部判断,内部创建不同抽象类进行操作。
所以也就是我们对NSArray类进行操作其实只是对父类进行了操作,在NSArray内部会创建其他子类来执行操作,真正执行操作的并不是NSArray自身,所以我们应该对其“真身”进行操作。
代码示例:
下面我们实现了防止NSArray因为调用objectAtIndex:方法,取下标时数组越界导致的崩溃:
#import "NSArray+LXZArray.h"
#import "objc/runtime.h"
@implementation NSArray (LXZArray)
+ (void)load {
Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(lxz_objectAtIndex:));
method_exchangeImplementations(fromMethod, toMethod);
}
- (id)lxz_objectAtIndex:(NSUInteger)index {
if (self.count-1 < index) {
// 这里做一下异常处理,不然都不知道出错了。
@try {
return [self lxz_objectAtIndex:index];
}
@catch (NSException *exception) {
// 在崩溃后会打印崩溃信息,方便我们调试。
NSLog(@"---------- %s Crash Because Method %s ----------\n", class_getName(self.class), __func__);
NSLog(@"%@", [exception callStackSymbols]);
return nil;
}
@finally {}
} else {
return [self lxz_objectAtIndex:index];
}
}
@end
__NSArrayI才是NSArray真正的类
,而NSMutableArray又不一样。我们可以通过runtime函数获取真正的类:
objc_getClass("__NSArrayI");
一些类簇和它对应的公共抽象父类
类簇 公共抽象父类
NSData NSMutableData
NSArray NSMutableArray
NSDictionary NSMutableDictionary
NSString NSMutableString