面试题一: 元类 中为什么会有 类对象 的 类方法?
在中的探索中,我们知道了实例方法
存储在类
中,类方法
存储在元类
中
为了探索我们的面试题现象,定义了以下几个方法,来探索方法
的归属问题
首先定义一个Person类,如下代码:
@interface Person : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation Person
- (void)sayHello{
NSLog(@"Person say : Hello!!!");
}
+ (void)sayHappy{
NSLog(@"Person say : Happy!!!");
}
@end
然后在main函数调用一些自定义方法:
//lgInstanceMethod_classToMetaclass 函数:用于获取类的实例方法
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
//lgInstanceMethod_classToMetaclass 函数:用于获取类的类方法
void lgClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
// - (void)sayHello;
// + (void)sayHappy;
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
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(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
Class pClass = object_getClass(person);
lgInstanceMethod_classToMetaclass(pClass);
lgClassMethod_classToMetaclass(pClass);
lgIMP_classToMetaclass(pClass);
NSLog(@"Hello, World!");
}
return 0;
}
打印结果如下:
lgInstanceMethod_classToMetaclass - 0x1000031b0-0x0-0x0-0x100003148
lgClassMethod_classToMetaclass-0x0-0x0-0x100003148-0x100003148
0x100001d10-0x7fff6ee48580-0x7fff6ee48580-0x100001d40
lgInstanceMethod_classToMetaclass函数 分析
在分析前,需要先了解class_getInstanceMethod
这个方法,主要是用于获取实例方法,针对该方法,苹果有如下说明
其大致含义就是:如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL
从上面代码可知,传入的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
,也没有找到sayhello
实例方法,返回NULL
,所以method3
的地址为0x0
,表示未找到
- 传入的
-
method4
地址:0x100003148
- 传入的
pClass
是LGPerson元类
,需要去获取selName = sayHappy
的实例方法,首先在LGPerson元类
中查找,发现有sayHappy
的实例方法,主要是因为类对象的类方法存储在元类
中,类方法在元类中是实例方法
,然后返回查找到的实例方法,所以method3
的地址为0x100003148
,表示找到了指定的实例方法
- 传入的
总结:
实例方法
存在类
中、类方法
存在元类
中
实例方法
就是类的实例
方法
类方法
就是元类的实例
方法
lgClassMethod_classToMetaclass函数 分析
在分析前,需要先了解class_getClassMethod
这个方法,主要是用于获取实例方法,针对该方法,苹果有如下说明
一个指向方法数据结构的指针,它对应于为类指定的类指定的类方法的实现,如果 指定的类或它的父类不包含具有指定的类方法,则为NULL
。
/***********************************************************************
* class_getClassMethod. Return the class method for the specified
* class and selector.
**********************************************************************/
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
//判断isMetaClass是否为元类,如果是元类就返回cls本类,如果不是, 返回本类的isa 。防止递归
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
通过看源码实现,我们发现class_getClassMethod
方法当传入的是类
时返回的是元类
的实例
方法,如果传入的是元类
时就返回元类本身的实例
方法。
所以我们不难分析出
sayHello
是实例
方法存在类
中,而通过上面的总结知当传入的是类
时返回的是元类
的实例
方法,传入的是元类
时就返回元类本身的实例
方法,所以method1
和method2
都是0x0
。都不存在这个实例方法。
sayHappy
是类
方法存在元类
中,而通过上面的总结知当传入的是类
时返回的是元类
的实例
方法,传入的是元类
时就返回元类本身的实例
方法,所以method3
和method4
都是0x100003148
。都存在这个类方法。
此时我们就知道了元类 中为什么会有 类对象 的 类方法?
由于元类获取类方法的时候返回的是元类本身的实例方法,而类方法就是元类中的实例方法。
lgIMP_classToMetaclass函数 分析
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;
}
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}
根据这个分析。不管在类中有没有实现都会返回值,所以都会返回一个地址指针回来。
面试题二: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)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson 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)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
其最终结果打印如下
源码解析
先分析结果之前,首先需要分析下这两个方法的源码
- isKindOfClass 源码解析(实例方法 & 类方法)
//--isKindOfClass---类方法、对象方法
//+ isKindOfClass:第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
+ (BOOL)isKindOfClass:(Class)cls {
// 获取类的元类 vs 传入类
// 根元类 vs 传入类
// 根类 vs 传入类
// 举例:LGPerson 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) --> 根元类(父类) --> 根类NSObject(父类) --> nil(父类)
与传入类
的对比 - 实例方法:
对象的类 --> 父类 --> 根类NSObject --> nil
与传入类
的对比
- 类方法:
-
isMemberOfClass
- 类方法:
类的元类
与传入类
对比 - 实例方法:
对象的父类
与传入类
对比
- 类方法:
然后通过断点
调试,isMemberOfClass
的类方法
和 实例方法
的流程是正常
的,会走到上面分析的源码,而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
中编译时对其进行了优化处理
案例代码执行结果分析
根据源码的分析,来分析代码执行的结果为什么是0
或者1
类方法调用情况分析:
re1:
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
- 第一次循环
NSObject
vsNSObject的元类
即根元类
-- 不相等 - 第二次循环
NSObject
vs根元类的父类
即NSObject根类
-- 相等,返回1
re2:
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
-
NSObject
vsNSObject的元类
即根元类
-- 不相等,返回0
re3:
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
- 第一次循环
LGPerson
vsLGPerson类的父类
即元类
-- 不相等 - 第二次循环
LGPerson
vsLGPerson的元类的父类
即根元类
-- 不相等 - 第二次循环
LGPerson
vs根元类的父类
即根类NSObject
-- 不相等 - 第二次循环
LGPerson
vs根元类的父类
为nil
-- 不相等,返回0
re4:
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
-
LGPerson
vsLGPerson的元类
-- 不相等
类方法调用情况分析:
re5:
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
-
NSObject
vsNSObject的对象的isa
指向父类
即NSObject
-- 相等,返回1
re6:
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
-
NSObject
vsNSObject的对象的类
即NSObject
-- 相等,返回1
re7:
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
-
LGPerson
vsLGPerson的对象的isa
指向父类
即LGPerson
-- 相等,返回1
re8:
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
-
LGPerson
vsLGPerson的对象的类
即NSObject
-- 相等,返回1