【面试题】元类中为什么会有类对象的类方法?
我先来解释一下题目的意思:
首先创建一个Person类,在类中创建一个成员变量,一个属性,一个实例方法,一个类方法,那么在我们创建这个对象之后,查找对象中的属性和方法,结果在类对象中并没有找到该对象的类方法,请看代码:
@interface Person : NSObject{
NSString *hobby;
}
// 影响的因素的 对象: 属性 : 8 + 8 + 8 + 8 = 32
// 内存的布局 属性
// isa
@property (nonatomic,strong) NSString *name;
-(void)sayNB;
-(void)say666;
@end
在main函数中创建Person对象,并打上断点,进行调试:
(lldb) p/x Person.class
(Class) $0 = 0x0000000100002278 Person
(lldb) p (class_data_bits_t *)0x0000000100002298
(class_data_bits_t *) $1 = 0x0000000100002298
(lldb) p $1->data()
(class_rw_t *) $2 = 0x000000010069b0d0
(lldb) p $2->methods()
(const method_array_t) $3 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020d8
arrayAndFlag = 4294975704
}
}
}
(lldb) p $3.list
(method_list_t *const) $4 = 0x00000001000020d8
(lldb) p *$4
(method_list_t) $5 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "sayNB"
types = 0x0000000100000f84 "v16@0:8"
imp = 0x0000000100000e00 (KCObjc`-[Person sayNB])
}
}
}
(lldb) p $5.get(0)
(method_t) $6 = {
name = "sayNB"
types = 0x0000000100000f84 "v16@0:8"
imp = 0x0000000100000e00 (KCObjc`-[Person sayNB])
}
(lldb) p $5.get(1)
(method_t) $7 = {
name = ".cxx_destruct"
types = 0x0000000100000f84 "v16@0:8"
imp = 0x0000000100000e60 (KCObjc`-[Person .cxx_destruct])
}
(lldb) p $5.get(2)
(method_t) $8 = {
name = "name"
types = 0x0000000100000f98 "@16@0:8"
imp = 0x0000000100000e10 (KCObjc`-[Person name])
}
(lldb) p $5.get(3)
(method_t) $9 = {
name = "setName:"
types = 0x0000000100000fa0 "v24@0:8@16"
imp = 0x0000000100000e30 (KCObjc`-[Person setName:])
}
(lldb) p $5.get(4)
Assertion failed: (i < count), function get, file /Users/pengwenxi/Desktop/objc4-781源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
在上面调试过程中,我们可以看到当我们使用p $5.get()
函数打印类对象的方法时,并没有类方法;在经过一翻推测后,得知类对象的类方法在元类中,那么我们如何证明呢?
那么我们先通过上面的方法去获取元类的方法:
(lldb) p/x Person.class
(Class) $3 = 0x0000000100002278 Person
(lldb) p/x 0x0000000100002278 & 0x0000000ffffffff8ULL
(unsigned long long) $4 = 0x0000000100002278
(lldb) p 0x0000000100002278
(long) $5 = 4294976120
(lldb) x/4gx 0x0000000100002278
0x100002278: 0x0000000100002250 0x0000000100333140
0x100002288: 0x0000000101870020 0x0001802400000003
(lldb) p/x 0x0000000100002250 & 0x0000000ffffffff8ULL
(unsigned long long) $6 = 0x0000000100002250
(lldb) po 0x0000000100002250
Person
(lldb) p (class_data_bits_t *)0x0000000100002270
(class_data_bits_t *) $8 = 0x0000000100002270
(lldb) p $8->data()
(class_rw_t *) $9 = 0x000000010186ff80
(lldb) p $9->methods()
(const method_array_t) $10 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002070
arrayAndFlag = 4294975600
}
}
}
(lldb) p $10.list
(method_list_t *const) $11 = 0x0000000100002070
(lldb) p *$11
(method_list_t) $12 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "say666"
types = 0x0000000100000f84 "v16@0:8"
imp = 0x0000000100000df0 (KCObjc`+[Person say666])
}
}
}
通过获取元类的方法,我们看到了类对象中的类方法。
下面我们通过另一种方式去验证他的存在:
void Objc_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));
NSLog(@"Method, name: %@", key);
}
free(methods);
}
Objc_copyMethodList 函数:用于获取类的方法列表
上面的函数打印结果为:Method, name: sayHello
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);
}
InstanceMethod_classToMetaclass 函数:用于获取类的实例方法
方法打印结果:InstanceMethod_classToMetaclass - 0x1000031b0-0x0-0x0-0x100003148
void ClassMethod_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));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
// 元类 为什么有 sayHappy 类方法 0 1
//
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
ClassMethod_classToMetaclass 函数:用于获取类的类方法
方法打印结果:ClassMethod_classToMetaclass-0x0-0x0-0x100003148-0x100003148
void IMP_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);
NSLog(@"%s",__func__);
}
IMP_classToMetaclass 函数:用于获取方法的实现
方法打印结果为:
0x100001d00-0x7fff66ea8580-0x7fff66ea8580-0x100001d30 lgIMP_classToMetaclass
在上述的方法中,pClass
为类,metaClass
为元类;
第一个方法获取类的方法列表,主要获取Person类的方法列表,但是结果就只获得了一个sayHello
方法;
第二个方法中,获取类的实例方法,分别对Person的类和元类分别获取他们的实例方法,从结果可以得到:在类中,只获得了一个sayHello
方法,而在元类中也只获得了一个sayHappy
方法。
第三个方法中,我们首先需要了解class_getClassMethod
这个方法的作用,该方法主要是获取类的类方法,通过下面的源码中可以看出,当cls是元类时,就不会继续递归查询下去,反之则继续递归查询。
通过打印的结果可以得知:在类对象中,会递归查询到该对象的元类中去查找类方法,在元类中则直接查询类方法。
//获取类方法
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
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
第四个方法中,我们首先需要了解class_getMethodImplementation
方法的作用:
`如果向一个类的实例发送一条消息,该函数会返回该条消息的IMP。class_getMethodImplementation可能比method_getImplementation更高效。返回的指针可能会是一个方法的IMP,也可能是runtime内部的一个函数。比如说,如果一个类的对象不能响应一个selector,这个函数指针返回的就会是runtime里面消息转发机制的一部分。`
下面是这个方法的源代码:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
//没有找到imp,就进行消息转发
if (!imp) {
return _objc_msgForward;
}
return imp;
}
从方法的打印结果可以明白,imp2和imp3结果是一样的,由于我们证明过在类对象中没有类方法,在元类中,存在实例方法;因此,可以证明这两个imp是进行了消息转发,而imp1和imp4则返回了函数指针地址。
【面试题】iskindOfClass 和 isMemberOfClass 的理解:
下面附上一张经典的图,来区分isa和类对象:
isa流程图.png下面请看代码:
//类方法调用
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]]; //
BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
//实例方法调用
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]]; //
BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
我们来先来看一下isKindOfClass
和isMemberOfClass
的源码,他们都有两个方法,一个是实例方法,一个是类方法:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
从isKindOfClass
源码可以分析得出:当传递过来的class是否等于当前的类,不是的话就找父类,进行递归查询。如果存在则返回YES,不存在返回NO。
在类方法中,拿当前类的元类与传入的类进行对比,
在实例方法中,拿当前的类与传入的类进行对比;
坑点:当我们通过在几个方法处打断点进行调试时,他却不会执行我们打断点的地方,那这是什么原因呢?
答案:因为在llvm
中编译时对其进行了优化处理,他执行的是下面的这个方法:
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
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);
}
通过Class cls = obj->getIsa();
这一行代码分别获取类的isa(元类,类方法)和对象的isa(类,实例方法)。
下面我们来解释一下运行程序中的re1-re8:
在re1中,使用NSObject对象与NSObject进行对比:
拿到NSObject(传入的类)的根元类与NSObject对比,结果不相等,
而找NSObject的根元类的父类为NSObject,因此相等:
在re3中,拿Person对象与Person对比:
Person(传入的类) VS Person的元类,不相等;
继续拿Person与Person的根元类(NSObject)对比,不相等;
在re5中,NSObject对象与NSObject对比,在实例方法中执行程序:
拿NSObject(传入的类)(根类)VS 对象的类NSObject跟类对比,相等;
在re7中,Person对象与Person对比:
Person(传入的类) VS 对象的类即Person,相等;
在re2中,将NSObject与NSObject对比,使用类方法:
NSObject根类 VS NSObject的元类即根元类,不相等(注意,不会再进行递归查找父类)
在re4中,拿Person与Person对比:
Person类与Person的元类对比,不相等。
在re6中,拿NSObject对象与NSObject对比,使用实例方法:
拿Person类与Person对象的类对比,相等。
re8中同样如此。
来看一下打印结果:
re1 :1
re2 :0
re3 :0
re4 :0
re5 :1
re6 :1
re7 :1
re8 :1
第二道面试题我们要理解类对象、元类,根元类之间的关系,在上面的图中具体的表现出来了。
网友评论