iOS-底层原理:经典面试题案例分析
面试题一: 类在内存中存储了几份?
答案是
:一份
解析:
可以通过不同的方式来获取同一个类
Class class1 = [LBHPerson class];
Class class2 = [LBHPerson alloc].class;
Class class3 = object_getClass([LBHPerson alloc]);
NSLog(@"\n%p-\n%p-\n%p", class1, class2, class3);
//输出结果
0x10063ac18-
0x10063ac18-
0x10063ac18
面试题二: 元类 中为什么会有 类对象 的 类方法?
在iOS-底层原理8:类 & 类结构分析中的探索中,我们知道了实例方法
存储在类
中,类方法
存储在元类
中
为了探索我们的面试题现象,定义了以下几个方法,来探索方法的归属问题
step1:
定义一个LBHPerson
类并定义一个实例方法和一个类方法
//.h
@interface LBHPerson : NSObject
- (void)instanceMethod;
+ (void)classMethod;
@end
//.m
@implementation LBHPerson
- (void)instanceMethod
{
NSLog(@"%s",__func__);
}
+ (void)classMethod
{
NSLog(@"%s",__func__);
}
@end
@end
step2:
在main
函数中定义几个函数
2.1:
lgInstanceMethod_classToMetaclass
函数:用于获取类的实例方法
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(instanceMethod));
Method method2 = class_getInstanceMethod(metaClass, @selector(instanceMethod));
Method method3 = class_getInstanceMethod(pClass, @selector(classMethod));
Method method4 = class_getInstanceMethod(metaClass, @selector(classMethod));
NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
2.2:
lgClassMethod_classToMetaclass
函数:用于获取类的类方法
void lgClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(instanceMethod));
Method method2 = class_getClassMethod(metaClass, @selector(instanceMethod));
// - (void)sayHello;
// + (void)sayHappy;
Method method3 = class_getClassMethod(pClass, @selector(classMethod));
Method method4 = class_getClassMethod(metaClass, @selector(classMethod));
NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
2.3:
lgIMP_classToMetaclass
函数:用于获取方法的实现
void lgIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHello;
// + (void)sayHappy;
IMP imp1 = class_getMethodImplementation(pClass, @selector(instanceMethod));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(instanceMethod));
IMP imp3 = class_getMethodImplementation(pClass, @selector(classMethod));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(classMethod));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
}
2.4:
lgObjc_copyMethodList
函数:用于获取类的方法列表
void lgObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
LGLog(@"Method, name: %@", key);
}
free(methods);
}
2.5:
main
函数中调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
LBHPerson *person = [LBHPerson alloc];
Class pClass = object_getClass(person);
lgObjc_copyMethodList(pClass);
lgInstanceMethod_classToMetaclass(pClass);
lgClassMethod_classToMetaclass(pClass);
lgIMP_classToMetaclass(pClass);
}
return 0;
}
运行结果
Method, name: instanceMethod
lgInstanceMethod_classToMetaclass - 0x10fa6eae0-0x0-0x0-0x10fa6ea78
lgClassMethod_classToMetaclass-0x0-0x0-0x10fa6ea78-0x10fa6ea78
0x10fa69240-0x7fff201b0ac0-0x7fff201b0ac0-0x10fa69270
对上面的函数进行分析
lgInstanceMethod_classToMetaclass
函数分析
这个函数是获取LBHPerson类
和LBHPerson元类
的实例方法,其中关键函数是class_getInstanceMethod
,我们看下苹果官方是如何定义该函数的:
其大致含义就是:如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL
代码中形参pclass
对应的实参是类LBHPerson
,通过objc_getMetaClass
获取的LBHPerson的元类
是元类LBHPerson
,函数中4个打印结果分别是:
-
method1
地址:0x10fa6eae0
*
传入的pClass
是LBHPerson类
,需要去获取selName = instanceMethod
的实例方法,首先在LBHPerson
中查找,在LBHPerson类
中是有这个实例方法的,所以会返回查找到的实例方法,所以method1
的地址不为0x0
。 -
method2
地址:0x0
*
传入的metaClass
是LBHPerson元类
,需要去获取selName = instanceMethod
的实例方法,其查找的顺序为元类
-->根元类
-->根类
-->nil
,直到最后也没有找到,所以class_getInstanceMethod
返回NULL
,其method2
的地址为0x0
,表示未找到。 -
method3
地址:0x0
*
传入的pClass
是LBHPerson类
,需要去获取selName = classMethod
的实例方法,查找顺序为LBHPerson类
-->根类
-->nil
,也没有找到classMethod
实例方法,返回NULL
,所以method3
的地址为0x0
,表示未找到。 -
method4
地址:0x10fa6ea78
*
传入的metaClass
是LBHPerson元类
,需要去获取selName = classMethod
的实例方法,首先在LBHPerson元类
中查找,发现有classMethod
的实例方法,主要是因为类对象
的类方法
存储在元类
中,类对象
的类方法
是元类
的实例方法,然后返回查找到的实例方法,所以method4
的地址为0x10fa6ea78
,表示找到了指定的实例方法。
lgClassMethod_classToMetaclass
函数分析
这个函数是获取LBHPerson类
和LBHPerson元类
的类方法,其中关键函数是class_getClassMethod
,我们看下苹果官方是如何定义该方法的:
其大致含义就是:如果在传入的类或者类的父类中没有找到指定的类方法,则返回NULL
class_getClassMethod
的实现是获取类
的类方法
,其本质
就是获取元类
的实例方法
,最终还是会走到class_getInstanceMethod
,但是在这里需要注意的一点是:在getMeta
源码中,如果判断出cls是元类,那么就不会再继续往下递归查找,会直接返回this,其目的是为了防止元类的无限递归查找
//获取类方法
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
⬇️
//获取元类
// NOT identical to this->ISA when this is a metaclass 判断是否是元类,是元类就直接返回,反之,继续找isa指向
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
源码流程如图:
对该函数的打印结果有以下分析:
-
method1
地址:0x0
*
传入的pClass
是LBHPerson类
,需要去获取selName = instanceMethod
的类方法
,首先判断LBHPerson类
是否是元类
,如果不是,返回LBHPerson的元类
,然后在元类
中查找instanceMethod实例方法
,并没有找到,然后去它的父类中查找,查找顺序如下:元类
-->根元类
-->根类
-->nil
,最后返回NULL
。 -
method2
地址:0x0
*
传入的metaClass
是LBHPerson元类
,需要去获取selName = instanceMethod
的类方法
,首先判断LBHPerson类
是否是元类
,此时是
,直接返回元类
,然后在元类
中查找instanceMethod实例方法
,并没有找到,然后去它的父类中查找,查找顺序如下:元类
-->根元类
-->根类
-->nil
,最后返回NULL
。 -
method3
地址:0x10fa6ea78
*
传入的pClass
是LBHPerson类
,需要去获取selName = classMethod
的类方法
,首先判断LBHPerson类
是否是元类,如果不是,返回LBHPerson的元类
,然后在元类中查找 classMethod实例方法,发现有这个实例方法,直接返回找到的实例方法。 -
method4
地址:0x10fa6ea78
*
传入的metaClass
是LBHPerson元类
,需要去获取selName = classMethod
的类方法
,首先判断LBHPerson类
是否是元类,此时是
,直接返回元类
,然后在元类
中查找classMethod实例方法
,发现有这个实例方法,直接返回找到的实例方法。
lgIMP_classToMetaclass
函数分析
这个函数主要是获取LBHPerson类
和LBHPerson元类
的方法实现,关键函数class_getMethodImplementation
, 我们看下苹果官方是如何定义该方法的:
其大致含义就是:该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针
。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))
更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。如果类实例无法响应selector
,则返回的函数指针将是运行时消息转发机制
的一部分
来看下它的源码实现
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
//查找方法实现
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
//如果没有找到,则进行消息转发
if (!imp) {
return _objc_msgForward;
}
return imp;
}
分析这个函数中的4个打印结果
-
imp1
函数指针地址:0x10fa69240
*
pClass
是LBHPerson类
,sel
是instanceMethod
,根据LBHPerson
文件,可以得出·LBHPerson类
中可以查找到instanceMethod
的具体实现,所以返回一个imp函数指针
的地址
-
imp2
函数指针地址:0x7fff201b0ac0
*
metaClass
是LBHPerson元类
,sel
是instanceMethod
,根据类方法存储在元类
中可知,instanceMethod
是一个实例方法
,并不存储在元类
中,也没有其任何实现,所以进行了消息转发
-
imp3
函数指针地址:0x7fff201b0ac0
*
pClass
是LBHPerson类
,sel
是classMethod
,根据LBHPerson
文件,classMethod
是一个类方法
,并不存储在类
中,也没有其任何实现,所以进行了消息转发
-
imp4
函数指针地址:0x10fa69270
*
metaClass
是LBHPerson元类
,sel
是classMethod
,classMethod
是一个类方法
,存储在元类
中,返回一个imp函数指针
的地址
lgObjc_copyMethodList
函数分析
这个函数主要是获取LBHPerson类
的方法列表,关键函数class_copyMethodList
,我们看下苹果官方是如何定义该方法的:
其大致含义:获取实例方法的指针数组,如果cls没有实现实例方法,或者cls为Nil,则返回NULL
总结
class_getInstanceMethod
:获取实例方法
,如果指定类找不到对应的方法,则从父类查找直到根类,如果没有则返回NULL。- class_getClassMethod:获取
类方法
,如果指定类找不到对应的方法,则从父类查找直到根类,如果没有则返回NULL。class_getMethodImplementation
:获取方法的具体实现
,如果未查找到,则进行消息转发
面试题三:iskindOfClass & isMemberOfClass 的理解
先看个案例
iskindOfClass
& isMemberOfClass
类方法
调用
//-----使用 iskindOfClass & isMemberOfClass 类方法
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[LBHPerson class] isKindOfClass:[LBHPerson class]];
BOOL re4 = [(id)[LBHPerson class] isMemberOfClass:[LBHPerson class]];
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
//------iskindOfClass & isMemberOfClass 实例方法
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[LBHPerson alloc] isKindOfClass:[LBHPerson class]];
BOOL re8 = [(id)[LBHPerson alloc] isMemberOfClass:[LBHPerson class]];
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
打印结果:
re1 :1
re2 :0
re3 :0
re4 :0
re5 :1
re6 :1
re7 :1
re8 :1
为什么会是这样?
源码分析
isKindOfClass
源码解析 (类方法+实例方法)
//+ isKindOfClass:第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
+ (BOOL)isKindOfClass:(Class)cls {
// 获取类的元类 vs 传入类
// 根元类 vs 传入类
// 根类 vs 传入类
// 举例:LBHPerson vs 元类 (根元类) (NSObject)
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//- isKindOfClass:第一次是获取对象类 与 传入类对比,如果不相等,后续对比是继续获取上次 类的父类 与传入类进行对比
- (BOOL)isKindOfClass:(Class)cls {
/*
获取对象的类 vs 传入的类
父类 vs 传入的类
根类 vs 传入的类
nil vs 传入的类
*/
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
isMemberOfClass
源码解析(实例方法 + 类方法)
//-----类方法
//+ isMemberOfClass : 获取类的元类,与 传入类对比
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
//-----实例方法
//- isMemberOfClass : 获取对象的类,与 传入类对比
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
源码分析总结
isKindOfClass
类方法
:类的isa指向 即元类
-->根元类(父类)
-->根类(父类)
-->nil(父类)
与传入类
的对比
实例方法
:对象的isa指向 即类
-->父类
-->根类
-->nil
与传入类
的对比
isMemberOfClass
类方法
:类的isa指向 即元类
与传入类
对比
实例方法
:对象的isa指向 即类
与传入类
对比
在断点调试时,isMemberOfClass
的类方法
和 实例方法
的流程是正常的,会走到上面分析的源码,但是isKindOfClass
根本不会走到上面分析的源码中,为什么会这样?
通过xcode
工具栏 选择 Debug
--> Debug Workflow
--> Always Show Disassembly
调试进入汇编代码可以发现isKindOfClass
的类方法
和实例方法
都是走到objc_opt_isKindOfClass
方法源码中
objc_opt_isKindOfClass
源码
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
//获取isa,
//如果obj 是对象,则isa是类,
//如果obj是类,则isa是元类
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
// 如果obj 是对象,则在类的继承链进行对比,
// 如果obj是类,则在元类的isa中进行对比
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
为什么会这样呢?主要是因为在llvm
中编译时对其进行了优化处理
调用objc_opt_isKindOfClass
实际走的逻辑如图所示
案例结果解析
[(id)[NSObject class] isKindOfClass:[NSObject class]]
传入类 | 比较类 | 结果 |
---|---|---|
NSObject 根类 |
NSObject 根类 的元类 即根元类
|
不相等 |
NSObject 根类 | 通过继承链 查找比较类NSObject 根元类 的父类 即NSObject根类
|
相等 |
[(id)[NSObject class] isMemberOfClass:[NSObject class]]
传入类 | 比较类 | 结果 |
---|---|---|
NSObject 根类 |
NSObject 根类 的元类 即根元类
|
不相等 |
[(id)[LBHPerson class] isKindOfClass:[LBHPerson class]]
传入类 | 比较类 | 结果 |
---|---|---|
LBHPerson 类 |
LBHPerson 类 的元类 即LBHPerson 元类
|
不相等 |
LBHPerson 类 | 通过继承链 查找比较类LBHPerson 元类 的父类 是NSObject根元类
|
不相等 |
LBHPerson 类 |
NSObject根元类 的父类是NSObject根类
|
不相等 |
LBHPerson 类 |
NSObject根类 的父类是nil
|
不相等 |
[(id)[LBHPerson class] isMemberOfClass:[LBHPerson class]]
传入类 | 比较类 | 结果 |
---|---|---|
LBHPerson 类 |
LBHPerson 类 的元类 即LBHPerson 元类
|
不相等 |
[(id)[NSObject alloc] isKindOfClass:[NSObject class]]
传入类 | 比较对象 | 结果 |
---|---|---|
NSObject 根类 |
NSObject 对象 的isa 指向,即NSObject 根类
|
相等 |
[(id)[NSObject alloc] isMemberOfClass:[NSObject class]]
传入类 | 比较对象 | 结果 |
---|---|---|
NSObject 根类 |
NSObject 对象 的isa 指向,即NSObject 根类
|
相等 |
[(id)[LBHPerson alloc] isKindOfClass:[LBHPerson class]]
传入类 | 比较对象 | 结果 |
---|---|---|
LBHPerson 类 |
LBHPerson 对象 的isa 指向,即LBHPerson 类
|
相等 |
[(id)[LBHPerson alloc] isMemberOfClass:[LBHPerson class]]
传入类 | 比较对象 | 结果 |
---|---|---|
LBHPerson 类 |
LBHPerson 对象 的isa 指向,即LBHPerson 类
|
相等 |