iOS Cateogry的深入理解
首先先看几个面试问题
- Cateogry里面有load方法么? load方法什么时候调用?load方法有继承么?
1. 新建一个项目,并添加TCPerson类,并给TCPerson添加两个分类
2.新建一个TCStudent类继承自TCPerson,并且给TCStudent也添加两个分类
Cateogry里面有load方法么?
答:分类里面肯定有load
#import "TCPerson.h"
@implementation TCPerson
+ (void)load{
}
@end
#import "TCPerson+TCtest1.h"
@implementation TCPerson (TCtest1)
+ (void)load{
}
@end
#import "TCPerson+TCTest2.h"
@implementation TCPerson (TCTest2)
+ (void)load{
}
@end
load方法什么时候调用?
load方法在runtime加载类和分类的时候调用load
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
@implementation TCPerson
+ (void)load{
NSLog(@"TCPerson +load");
}
@end
@implementation TCPerson (TCtest1)
+ (void)load{
NSLog(@"TCPerson (TCtest1) +load");
}
@end
@implementation TCPerson (TCTest2)
+ (void)load{
NSLog(@"TCPerson (TCtest2) +load");
}
@end
可以看到我们在main里面不导入任何的头文件,也不引用任何的类,直接运行,控制台输出结果:
从输出结果我们可以看出,三个load方法都被调用
问题:分类重写方法,真的是覆盖原有类的方法么?如果不是,到底分类的方法是怎么调用的?
首先我们在TCPerson申明一个方法+ (void)test并且在它的两个分类都重写+ (void)test
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TCPerson : NSObject
+ (void)test;
@end
NS_ASSUME_NONNULL_END
#import "TCPerson.h"
@implementation TCPerson
+ (void)load{
NSLog(@"TCPerson +load");
}
+ (void)test{
NSLog(@"TCPerson +test");
}
@end
分类重写test
#import "TCPerson+TCtest1.h"
@implementation TCPerson (TCtest1)
+ (void)load{
NSLog(@"TCPerson (TCtest1) +load");
}
+ (void)test{
NSLog(@"TCPerson (TCtest1) +test1");
}
@end
#import "TCPerson+TCTest2.h"
@implementation TCPerson (TCTest2)
+ (void)load{
NSLog(@"TCPerson (TCtest2) +load");
}
+ (void)test{
NSLog(@"TCPerson (TCtest2) +test2");
}
@end
在main里面我们调用test
#import <Foundation/Foundation.h>
#import "TCPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[TCPerson test];
}
return 0;
}
输出结果:
从输出结果中我们可以看到,只有分类2中的test被调用,为什么只调用分类2中的test了?
因为编译顺序是分类2在后,1在前,这个时候我们改变编译顺序(拖动文件就行了)
其输出结果为:
细心的老铁会看到,为什么load方法一直都在调用,这是为什么了?它和test方法到底有什么不同了?真的是我们理解中的load不覆盖,test覆盖了,所以才出现这种情况么?
我们打印TCPerson的类方法
void printMethodNamesOfClass(Class cls)
{
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);
// 存储方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[I];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 释放
free(methodList);
// 打印方法名
NSLog(@"%@ %@", cls, methodNames);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
[TCPerson test];
printMethodNamesOfClass(object_getClass([TCPerson class]));
}
return 0;
}
输出结果:
可以看到,TCPerson的所有类方法名,并不是覆盖,三个load,三个test,方法都在
load源码分析:查看objc底层源码我们可以看到:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
load方法它是先调用 while (loadable_classes_used > 0) {call_class_loads(); }类的load,再调用more_categories = call_category_loads()分类的load,和编译顺序无关,都会调用
我们查看call_class_loads()方法
我们查看call_class_loads()方法
static void call_class_loads(void)
{
int I;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
其通过的是load_method_t函数指针直接调用
函数指针直接调用
typedef void(*load_method_t)(id, SEL);
其分类load方法调用也是一样
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
为什么test不一样了
因为test是因为消息机制调用的,objc_msgSend([TCPerson class], @selector(test));消息机制就牵扯到了isa方法查找,test在元类方法里面顺序查找的
load只在加载类的时候调用一次,且先调用类的load,再调用分类的
load的继承关系调用
首先我们先看TCStudent
#import "TCStudent.h"
@implementation TCStudent
@end
不写load方法调用
TCStudent写上load
从中可以看出子类不写load的方法,调用父类的load,当子类调用load时,先调用父类的load,再调用子类的load,父类子类load取决于你写load方法没有,如果都写了,先调用父类的,再调用子类的
总结:先调用类的load,如果有子类,则先看子类是否写了load,如果写了,则先调用父类的load,再调用子类的load,当类子类调用完了,再是分类,分类的load取决于编译顺序,先编译,则先调用,test的方法调用走的是消息发送机制,其底层原理和load方法有着本质的区别,消息发送主要取决于isa的方法查找顺序
作者:枫紫_6174
链接:https://www.jianshu.com/p/f66921e24ffe