iOS中两道经典面试题解析
1.方法归属
自定义类一个实例方法和一个类方法
main函数调用
lgObjc_copyMethodList 函数:用于获取类的方法列表
lgInstanceMethod_classToMetaclass 函数:用于获取类的实例方法
lgClassMethod_classToMetaclass 函数:用于获取类的类方法
lgIMP_classToMetaclass 函数:用于获取方法的实现
打印结果
解析:
lgObjc_copyMethodList函数 分析
在这个函数中,主要是获取LGPerson类中的方法列表,从实例方法存储在类中,类方法存储在元类中可以得知,LGPerson的方法列表打印结果只有sayHello方法
lgInstanceMethod_classToMetaclass 函数 分析
从上面代码可知,传入的pclass 是类LGPerson,通过objc_getMetaClass获取的LGPerson的元类 是元类LGPerson,函数中4个打印结果分别是:
method1 地址:0x1000031b0
传入的pClass是LGPerson类,需要去获取selName = sayHello的实例方法
首先在LGPerson中查找,有前面的LGPerson类可知,是有这个实例方法的,所以会返回查找到的实例方法,所以method1的地址不为0x0
method2 地址:0x0
传入的pClass是LGPerson元类,需要去获取selName = sayHello的实例方法
其查找的顺序为元类 --> 根元类 --> 根类 --> nil,直到最后也没有找到,所以class_getInstanceMethod返回NULL,其method2的地址为0x0,表示未找到
method3 地址:0x0
传入的pClass是LGPerson类,需要去获取selName = sayHappy的实例方法
查找顺序为LGPerson类 --> 根类 --> nil,也没有找到sayHappy实例方法,返回NULL,所以method3的地址为0x0,表示未找到
method4 地址:0x100003148
传入的pClass是LGPerson元类,需要去获取selName = sayHappy的实例方法
首先在LGPerson元类中查找,发现有sayHappy的实例方法,主要是因为类对象的类方法存储在元类中,类方法在元类中是实例方法,然后返回查找到的实例方法,所以method3的地址为0x100003148,表示找到了指定的实例方法
lgClassMethod_classToMetaclass函数分析
class_getClassMethod
看该方法的源码实现,可以得出class_getClassMethod的实现是获取类的类方法,其本质就是获取元类的实例方法,最终还是会走到class_getInstanceMethod,但是在这里需要注意的一点是:在getMeta源码中,如果判断出cls是元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找
打印结果有以下分析:
method1 地址:0x0
pClass是LGPerson类,selName是sayHello
首先判断LGPerson类是否是元类,此时不是,返回LGPerson的元类,然后在元类中查找sayhello实例方法。查找顺序如下:元类 --> 根元类 --> 根类 --> nil,最后返回NULL
method2 地址:0x0
metaClass是LGPerson元类,selName是sayHello
首先判断LGPerson元类是否是元类,此时是,直接返回元类,然后在元类中查找sayhello实例方法,发现并没有找到,返回NULL
method3 地址:0x100003148
pClass是LGPerson类,selName是sayHappy
首先判断LGPerson类是否是元类,此时不是,返回LGPerson的元类,然后在元类中查找sayHappy实例方法,发现有这个实例方法,直接返回找到的实例方法。
method4 地址:0x100003148
pClass是LGPerson元类,selName是sayHappy
首先判断LGPerson元类是否是元类,此时是,直接返回元类,然后在元类中查找sayHappy实例方法,发现有这个实例方法,直接返回找到的实例方法
lgIMP_classToMetaclass函数 分析
该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分
class_getMethodImplementation
4个打印结果:
imp1 函数指针地址:0x100001d00
pClass 是 LGPerson类,sel 是 sayHello
根据LGPerson文件,可以得出LGPerson类中可以查找到sayHello的具体实现,所以返回一个imp函数指针的地址
imp2 函数指针地址:0x7fff66238d80
pClass是LGPerson元类,sel是sayHello
根据类方法存储在元类中可知,sayHello是一个实例方法,并不存储在元类中,也没有其任何实现,所以进行了消息转发
mp3 函数指针地址:0x7fff66238d80
pClass是LGPerson类,sel是sayHappy
根据LGPerson文件,sayHappy是一个类方法,并不存储在类中,也没有其任何实现,所以进行了消息转发
imp4 函数指针地址:0x100001d30
pClass是LGPerson元类,sel是sayHappy
根据类方法存储在元类文件,可以在元类中查找到sayHappy的具体实现,所以返回一个imp函数指针的地址
总结
class_getInstanceMethod:获取实例方法,如果指定的类或其父类不包含带有指定选择器的实例方法,则为NULL
class_getClassMethod:获取类方法,如果指定的类或其父类不包含具有指定选择器的类方法,则为NULL。
class_getMethodImplementation:获取方法的具体实现,如果未查找到,则进行消息转发
2.isKindOfClass 和 isMemberOfClass
先上问题和答案~~
先分析方法源码
isKindOfClass
类方法:元类 --> 根元类 --> 根类 --> nil 与 传入类的对比
实例方法:对象的类 --> 父类 --> 根类 --> nil 与 传入类的对比
isMemberOfClass
类方法: 类的元类 与 传入类 对比
实例方法:对象的父类 与 传入类 对比
根据源码的分析,来分析代码执行结果
re1 :1 ,是NSObject与NSObject的对比,使用+isKindOfClass
NSObject(传入类,即根类) vs NSObject的元类即根元类-- 不相等
NSObject(传入类,即根类) vs 根元类的父类即根类-- 相等,返回1
re2 :0 ,是NSObject与NSObject的对比,使用+isMemberOfClass
NSObject根类(传入类) vs NSObject的元类即根元类-- 不相等
re3 :0 ,是LGPerson与LGPerson的对比,使用+isKindOfClass
LGPerson(传入类) vs LGPerson的元类即元类LGPerson -- 不相等
LGPerson(传入类) vs 元类LGPerson的父类即根元类-- 不相等
LGPerson(传入类) vs 根元类的父类即根类-- 不相等
LGPerson(传入类) vs 根类的父类即nil-- 不相等
re4 :0 ,是LGPerson与LGPerson的对比,使用+isMemberOfClass
LGPerson(传入类) vs元类-- 不相等
使用实例方法结果解析
re5 :1 ,是NSObject对象与NSObject的对比,使用-isKindOfClass
NSObject(传入类,即根类) vs 对象的类即NSObject根类 -- 相等
re6 :1 ,是NSObject对象与NSObject的对比,使用-isMemberOfClass
NSObject(传入类,即根类) vs 对象的类即NSObject根类 -- 相等
re7 :1 ,是LGPerson对象与LGPerson的对比,使用-isKindOfClass
LGPerson(传入类) vs 对象的类即LGPerson -- 相等
re8 :1 ,是LGPerson对象与LGPerson的对比,使用-isMemberOfClass
LGPerson(传入类) vs 对象的类即LGPerson -- 相等