首先可以查看下官方对于Category的介绍:Apple官方文档解释
个人理解:
- Category初衷是用来给现有类添加或者重写函数的,不可添加属性,这个区别于Extension;
- 将类的一些类似的功能拆分出去,使得每个分类中的函数功能统一,便于管理;
- 给系统类(NSString,NSArray等)添加一些自定义的方法;
- 重写私有类的函数(公有、私有均可),改变内部的实现代码。
另类使用:
- 因为Category是不具有属性列表的,所以不能给Category添加属性。那怎么通过@property给定义的Catogory添加属性呢?接下来就详细讲解:
定义一个NSObject的分类Worker,通过@property声明两个属性如下:
@interface NSObject (Worker)
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *jodName;
@end
如果这时直接使用name和jodName如下:
NSObject *worker = [[NSObject alloc] init];
worker.name = @"Jim";
worker.jodName = @"Software Engineer";
command+R运行则会报如下错误:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSObject setName:]: unrecognized selector sent to instance 0x600001a7c570'
这是因为系统不会给Category添加属性的setter/getter方法,所以这时就要用到runtime机制了,写法如下:
// name的key
static NSString *keyName;
// jobName的key
static NSString *keyJobName;
@implementation NSObject (Worker)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, &keyName, name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name {
return objc_getAssociatedObject(self, &keyName);
}
- (void)setJodName:(NSString *)jodName {
objc_setAssociatedObject(self, &keyJobName, jodName, OBJC_ASSOCIATION_COPY);
}
- (NSString *)jodName {
return objc_getAssociatedObject(self, &keyJobName);
}
@end
在 setter 里面使用了一个 objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy) 方法,这个方法有四个参数,分别是:
- 源对象(self)
- 关联时的用来标记是哪一个属性的key (keyName,keyJobName)
- 关联的对象(name、jobName)
- 一个关联策略(OBJC_ASSOCIATION_COPY)
在 getter 里面用到了 objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key) 这个方法,这个方法有两个参数,填写方法参照setter方法。
这时再运行代码如下:
NSObject *worker = [[NSObject alloc] init];
worker.name = @"Jim";
worker.jodName = @"Software Engineer";
NSLog(@"Hello, my name is %@, I am a %@ !",worker.name,worker.jodName);
打印信息如下:
2019-04-30 11:24:43.258099+0800 test[44759:2009573] Hello, my name is Jim, I am a Software Engineer !
这样就实现了在变相的在分类里添加了假属性,注意:这种添加方式并不支持_name和_jobName的方式调用方式,因为真正意思上并没有相关属性
- 重写类的方法,并介绍如何调用原类方法:
在分类中重写了原类中的方法,在调用该方法时,程序是触发的分类中重写的方法,但有时我们还是需要用到原类中的方法,接下来就看下怎么用代码实现:
先生成一个Person类,实现一个方法:
- (NSString *)getWorkerIntroducation {
return @"该方法由分类实现的,返回的是这个人的介绍信息";
}
在生成一个分类Person+Worker.h,重写方法如下:
- (NSString *)getWorkerIntroducation {
if (self.name.length&&self.jodName.length) {
return [NSString stringWithFormat:@"My name is %@, I am a %@!", self.name, self.jodName];
} else {
return nil;
}
}
在调用如下:
Person *worker = [[Person alloc] init];
worker.name = @"Jim";
worker.jodName = @"Software Engineer";
NSLog(@"%@", [worker getWorkerIntroducation]);
打印日志:
2019-04-30 15:59:05.812732+0800 test[46479:2079803] My name is Jim, I am a Software Engineer!
根据分类的原理,结构体中存在一个对象方法列表,主要就是通过获取方法的列表实现调用原类的方法,我们可以先打印一下对象方法列表:
Person *worker = [[Person alloc] init];
worker.name = @"Jim";
worker.jodName = @"Software Engineer";
NSLog(@"%@", [worker getWorkerIntroducation]);
// 打印对象方法列表
u_int count;
Method *methods = class_copyMethodList([worker class], &count);
for (int i=0; i<count; i++) {
SEL sel = method_getName(methods[i]);
NSString *methodName = [NSString stringWithCString:sel_getName(sel) encoding:NSUTF8StringEncoding];
NSLog(@"%d = %@", i, methodName);
}
// 打印结果
// 调用分类重写方法
My name is Jim, I am a Software Engineer!
0 = setJodName:
// 分类的重写方法
1 = getWorkerIntroducation
// 原类的重写方法
2 = getWorkerIntroducation
3 = jodName
4 = name
5 = setName:
从打印信息来看分类的调用优先级是在前面的,所以需要遍历获取最后的方法再通过IMP调用,代码如下:
Person *worker = [[Person alloc] init];
worker.name = @"Jim";
worker.jodName = @"Software Engineer";
NSLog(@"%@", [worker getWorkerIntroducation]);
// 打印对象方法列表
u_int count;
Method *methods = class_copyMethodList([worker class], &count);
int index = -1;
for (int i=0; i<count; i++) {
SEL sel = method_getName(methods[i]);
NSString *methodName = [NSString stringWithCString:sel_getName(sel) encoding:NSUTF8StringEncoding];
if ([methodName isEqualToString:@"getWorkerIntroducation"]) {
index = i;
}
}
if (index>=0) {
// 通过index获取原类的方法
SEL sel = method_getName(methods[index]);
IMP imp = method_getImplementation(methods[index]);
NSString *string = ((id (*)(id, SEL)) imp)(self, sel);
NSLog(@"%@",string);
} else {
NSLog(@"不存在该方法");
}
打印结果:
// 执行的分类方法
2019-04-30 17:21:50.688067+0800 test[49470:2130743] My name is Jim, I am a Software Engineer!
// 执行的原类方法
2019-04-30 17:21:50.688238+0800 test[49470:2130743] 该方法由分类实现的,返回的是这个人的介绍信息
代码传送门:
传送门: