美文网首页ios
iOS重名category 的调用方式

iOS重名category 的调用方式

作者: jokers200 | 来源:发表于2018-01-05 11:18 被阅读835次

今天看了一篇文章【iOS】category 重写方法的调用,介绍了category在重写主类方法,和多个category中方法重名的时候的调用方式。其中提到category加入函数列表中的顺序是反向于文件编译的顺序,即编译是根据buildPhases->Compile Sources里面的顺序从上至下编译的,那么category的执行顺序就是反向于这个顺序的。那么我们来验证一下。

@interface Father : NSObject

- (void)name;

@end

@implementation Father

- (void)name{
    NSLog(@"my name is father");
}
@end
@interface Father (aaa)

@end

@implementation Father (aaa)

- (void)name{
    NSLog(@"my name is father aaa");
}

@end
@interface Father (bbb)

@end

@implementation Father (bbb)

- (void)name{
    NSLog(@"my name is father bbb");
}
@end
    int count;
    Method* list;
    list = class_copyMethodList( [Father class], &count );
    for (int i = 0; i < count; i++) {
        NSLog(@"%@",NSStringFromSelector(method_getName(list[i])));
    }

运行结果:

2018-01-04 23:59:59.904820+0800 test[48010:12449543] name:
2018-01-04 23:59:59.904937+0800 test[48010:12449543] name:
2018-01-04 23:59:59.905057+0800 test[48010:12449543] name:

打印出3个name:,可以看到category和主类的方法都在函数列表中,但是selector的名字都一样啊,那么怎么确定他的顺序呢?哈哈,我们来想想办法,看看method都包括哪些内容

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}

selector是查找函数的key,而selector本身就是一个字符串,那么我们可以定义同样的selector,通过定义不同的method_types加以区分。

@interface Father : NSObject
- (void)name:(int)a;
@end

@implementation Father

- (void)name:(int)a{
    NSLog(@"my name is father");
}
@end
@interface Father (aaa)

@end

@implementation Father (aaa)

- (void)name:(char)a{
    NSLog(@"my name is father aaa");
}

@end
@interface Father (bbb)

@end

@implementation Father (bbb)

- (void)name:(short)b{
    NSLog(@"my name is father bbb");
}
@end
    int a;
    Father* father = [[Father alloc]init];
    [father name:a];

    int count;
    Method* list;
    list = class_copyMethodList( [Father class], &count );
    des = method_getDescription(list);
    for (int i = 0; i < count; i++) {
        NSLog(@"%@ %s",NSStringFromSelector(method_getName(list[i])), method_getTypeEncoding(list[i]));
    }

运行结果:

2018-01-05 00:27:44.160124+0800 test[48427:12478941] my name is father bbb
2018-01-05 00:21:44.170938+0800 test[48427:12478941] name: v20@0:8s16
2018-01-05 00:21:44.171095+0800 test[48427:12478941] name: v20@0:8c16
2018-01-05 00:21:44.200842+0800 test[48427:12478941] name: v20@0:8i16

其中8s中的s代表short,8c中的c代表char,8i中的i是int。

到Compile Sources中看一下,发现编译的顺序是

Father+aaa.m

Father+bbb.m

Father.m

说明是先编译的Father.m,然后编译的Father+aaa.m和Father+bbb.m,所以Father+bbb.m中的函数

-(void)name:(short)b 最后一个载入,也得到了最终执行。

接下来我们在Compile Sources中拖动Father.m交换文件顺序。

Father+bbb.m

Father.m

Father+aaa.m

运行结果:

2018-01-05 00:37:28.714927+0800 test[48698:12497028] my name is father aaa
2018-01-05 00:37:28.715230+0800 test[48698:12497028] name: v20@0:8c16
2018-01-05 00:37:28.727622+0800 test[48698:12497028] name: v20@0:8s16
2018-01-05 00:37:28.727735+0800 test[48698:12497028] name: v20@0:8i16

可以看到,Father+aaa.m和Father+bbb.m中的函数在methodlist中的顺序交换了,最终执行的也成了Father+aaa.m中的函数。但是主类的函数(name: v20@0:8i16)一直是最先放入methodlist中的。这也说明了category的加载是在主类之后,这与Compile Sources中的顺序无关。

再思考2个问题:

  1. 子类和父类都实现了相同的category函数,会执行谁呢?
  2. 子类继承后重写了父类的category函数,会执行谁呢?

其实答案很明显,按照selector的查找顺序,会先在子类中找,再到父类中找。只要子类找到了selector,不管是不是category,都会先执行。

看下这两个问题的代码:

@implementation Father (bbb)

- (void)name:(double)b{
    NSLog(@"my name is father bbb");
}

- (void)age{
    NSLog(@"I am 30 years old");
}
@end
@interface Son : Father
- (void)age;
@end

@implementation Son

- (void)name:(int)a{
    NSLog(@"my name is son");
}

- (void)age{
    NSLog(@"I am 6 years old");
}
@end
@interface Son (bbb)

@end

@implementation Son (bbb)
- (void)name:(double)a{
    NSLog(@"my name is son bbb");
}
@end
Son* son = [[Son alloc]init];
[son name:a];

执行结果:

2018-01-05 10:29:15.625968+0800 test[50457:12615463] my name is son bbb
2018-01-05 10:29:15.626104+0800 test[50457:12615463] I am 6 years old

明显,子类son的category得到了执行,复写的age函数也是先执行的子类的,与分析的结果一致。

category导致的问题

从上面的结果中我们可以看到,category虽然给我们提供了便利,但是最大的问题就是不确定性,当出现重名的函数时,执行的结果很可能和预想的不一样。尤其在一些大的工程中,代码多,很可能出现重名的category。特别是很多工程采用sdk组件化集成,内部的代码都不知道实现了哪些category,当出现一些crash和执行错误的时候,很可能是执行了不同的category导致的。那么怎么控制呢?

  1. 首先是命名,category中要有自己的命名规范,根据category的名字给函数加前缀。由于objective-c没有namespace,只能通过前缀来区分。
@implementation Father (aaa)

- (void)aaa_Name:(char)a{
    NSLog(@"my name is father aaa");
}

@end

@implementation Father (bbb)

- (void)bbb_Name:(char)b{
    NSLog(@"my name is father bbb");
}

@end
  1. 对于一些公用的比较基础的category,放到基础库中,大家引用同一份,不要各自定义。
@interface NSString (Addition)
- (NSString *)urlEncode;
- (NSString *)md5Digest;
@end
  1. category的使用时机。个人认为,有2点:
    • 如果要实现的功能,是对这个类普遍生效的,则使用category。如果是对单独场景的一种扩展,还是使用继承比较好。其实这种讨论类似于一个方法是要加到基类中,还是继承后实现到子类中。比如UIView,添加动画,frame设置这些基础方法,需要放到category中。像UIButton这中针对单独场景的特殊设置,就用的子类。
    • 在大工程多个sdk组件化的工程中,对于其他模块封装的类,还是尽量使用子类化,category还是尽量实现在声明这个类的sdk中。

相关文章

网友评论

    本文标题:iOS重名category 的调用方式

    本文链接:https://www.haomeiwen.com/subject/sdbjnxtx.html