iOS重名category 的调用方式

今天看了一篇文章【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中。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,709评论 0 9
  • 他:“遇见问题了,请帮我分析一下?” 我:“好的,请说。” 他:这个大客户连续拜访了两次(副总),并赠送小礼品,在...
    疯狂的小蜗牛阅读 676评论 0 0
  • 结束完一周的工作,周末终于可以约上小伙伴去探店了,今天要给大家介绍的是这家小而精的吉川堂。说真的,如果真的可以当一...
    喜乐喜乐love阅读 397评论 0 0
  • 从最初到现在,你,换了多少个手机?或许,你一时答不上来,或许,你思考半天还是一无所获。但是,多数人还是会记得自己拥...
    桐妍很无忌阅读 322评论 0 0
  • 文/极客少年 奥古末纪,群王并起,龙武觉醒,诸圣争霸;东方军团喋血长空,冬国之海风起云涌。远古神殿流传着一则不朽的...
    极客少年阅读 545评论 0 4