iOS中的方法在Runtime时调用的流程大家都很熟悉,通过objc_msgSend方法查找到对应的方法的实现,然后运行。但是,如果一个方法同时在类,类的父类,多个Category中存在,这个方法最后怎么执行的呢?
首先,看看一个方法同时在类及类的父类中存在时的执行顺序。
定一个类Person,类Student是Person的子类,Student中重写Person的study。
@interface Person : NSObject
- (void)speak;
- (void)study;
@end
@implementation Person
- (void)study {
NSLog(@"Method:%s",__PRETTY_FUNCTION__);
}
- (void)speak {
NSLog(@"Method:%s",__PRETTY_FUNCTION__);
}
@end
@interface Student : Person
- (void)study;
@end
@implementation Student
- (void)study {
NSLog(@"Method:%s",__PRETTY_FUNCTION__);
}
@end
执行下面的代码
Student *student = [[Student alloc] init];
[student study];
[student speak];
输出log
2019-11-27 14:40:56.225884+0800 TestMethod[31612:209770] Method:-[Student study]
2019-11-27 14:40:56.226024+0800 TestMethod[31612:209770] Method:-[Person speak]
可以看出study执行的是Student中的方法,speak执行的是Person中的方法。
这其实与objc_msgSend执行有关
1、通过对象的isa指针找到类对象
2、在类对象的objc_cache中查找cache的方法,如果查找到,则直接执行,否则执行下一下
3、在类对象的objc_method_list中查找,如果查找到,则直接执行,并添加到cache,否则执行下一下
4、在类对象的super_class中查找,如果查找到,则直接执行,并添加到cache中
在工程中再添加一个Student的Category Student(pupil)
@interface Student (pupil)
- (void)study;
@end
@implementation Student (pupil)
- (void)study {
NSLog(@"Method:%s",__PRETTY_FUNCTION__);
}
@end
重新执行上面的代码,结果如下
2019-11-27 15:47:59.121235+0800 TestMethod[40834:289455] Method:-[Student(pupil) study]
2019-11-27 15:47:59.121369+0800 TestMethod[40834:289455] Method:-[Person speak]
可以看到study方法执行的是Category中的方法,speak执行的是Person中的方法
这是因为Category在关联到类的时候会执行attachCategories,整理类得方法、属性和协议列表
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[I];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
将Category中的方法放到了类中原方法的前面,所以当我们调用的时候会优先调用Category中的方法。
在工程中再添加一个Student的Category Student(junior)
@interface Student (junior)
- (void)study;
@end
@implementation Student (junior)
- (void)study {
NSLog(@"Method:%s",__PRETTY_FUNCTION__);
}
@end
重新执行上面的代码,结果如下
2019-11-27 16:19:49.194570+0800 TestMethod[45288:330161] Method:-[Student(pupil) study]
2019-11-27 16:19:49.194698+0800 TestMethod[45288:330161] Method:-[Person speak]
在Compile Source中修改Student+pupil和Student+junior的顺序
重新执行上面的代码,结果如下
2019-11-27 16:22:29.941082+0800 TestMethod[45670:333499] Method:-[Student(junior) study]
2019-11-27 16:22:29.941269+0800 TestMethod[45670:333499] Method:-[Person speak]
可以看出Category中相同的方法,最后执行与文件编译的前后有关系,执行后编译的Category中的方法