分类(category)的使用

本文介绍了Category在iOS开发中的作用,包括为现有类添加或重写方法,管理功能,以及给系统类扩展自定义方法。同时,文章详细阐述了如何利用runtime机制在Category中模拟添加属性,以及如何在分类中重写方法并调用原类方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先可以查看下官方对于Category的介绍:Apple官方文档解释

个人理解:

  1. Category初衷是用来给现有类添加或者重写函数的,不可添加属性,这个区别于Extension;
  2. 将类的一些类似的功能拆分出去,使得每个分类中的函数功能统一,便于管理;
  3. 给系统类(NSString,NSArray等)添加一些自定义的方法;
  4. 重写私有类的函数(公有、私有均可),改变内部的实现代码。

另类使用:

  1. 因为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) 方法,这个方法有四个参数,分别是:

  1. 源对象(self)
  2. 关联时的用来标记是哪一个属性的key (keyName,keyJobName)
  3. 关联的对象(name、jobName)
  4. 一个关联策略(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的方式调用方式,因为真正意思上并没有相关属性

  1. 重写类的方法,并介绍如何调用原类方法:

在分类中重写了原类中的方法,在调用该方法时,程序是触发的分类中重写的方法,但有时我们还是需要用到原类中的方法,接下来就看下怎么用代码实现:

先生成一个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] 该方法由分类实现的,返回的是这个人的介绍信息

代码传送门:

传送门:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值