在类的结构分析中对类底层结构进行了分析,我们知道类的属性和实例方法都存储在class_data_bits_t
类型结构体的bits
中,通过地址对类和实例方法进行了查找,但是我们却没有找到类方法,这是为什么呢?
类方法查找
我们大胆猜测类方法是不是在类的元类里面呢?我们不妨根据在类的结构分析的方法再次了查找试试。如果类方法在在元类里面,那么类的isa
指向了它的元类,通过isa
就可以找到元类的地址,从而逐步拿到元类里面的数据。
首先我们还是创建一个类SYPerson
,在SYPerson
类中创建一个类方法
+ (void)goodJob;
通过lldb
进行打印
(lldb) x/4gx SYPerson.class
0x100002218: 0x00000001000021f0 0x0000000100334140
0x100002228: 0x00000001006ad8c0 0x0002801000000003
//通过isa & ISA_MSAK可以查看元类信息
(lldb) p/x 0x00000001000021f0 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x00000001000021f0
//地址偏移32
(lldb) p (class_data_bits_t *)0x0000000100002210
(class_data_bits_t *) $2 = 0x0000000100002210
//获取data数据
(lldb) p $2->data()
(class_rw_t *) $3 = 0x00000001006ad840
(lldb) p *$3
(class_rw_t) $4 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975672
}
firstSubclass = nil
nextSiblingClass = 0x00007fff90cdfcd8
}
//拿到data中的方法
(lldb) p $4.methods()
(const method_array_t) $5 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002100
arrayAndFlag = 4294975744
}
}
}
//拿到方法列表
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100002100
//读取方法,拿到我们申明的类方法goodJob
(lldb) p *$6
(method_list_t) $7 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "goodJob"
types = 0x0000000100000fa6 "v16@0:8"
imp = 0x0000000100000f00 (KCObjc`+[SYPerson goodJob])
}
}
}
通过上述操作我们如愿拿到了类方法。说明类方法确实是可以通过元类查找到的,我们这里直接上结论那就是:
得到一个类的类方法相当于得到一个元类的实例方法
通过经典面试题分析类
1.有一个LGPerson
的类我们通过runtime动态获取其方法,sayHello
是实例方法,sayHappy
是类方法,那么下面代码会打印什么结果呢?
#ifdef DEBUG
#define LGLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGLog(format, ...);
#endif
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);
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));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
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);
打印结果
//class_getInstanceMethod
lgInstanceMethod_classToMetaclass - 0x1000031b0-0x0-0x0-0x100003148
//class_getClassMethod
lgClassMethod_classToMetaclass-0x0-0x0-0x100003148-0x100003148
//class_getMethodImplementation
0x100001d00-0x7fff71c75580-0x7fff71c75580-0x100001d30
分析
class_getInstanceMethod(pClass, @selector(sayHello))
获取类pClass
名为sayHello
的实例方法,显然是可以获取到的所以可以打印出地址class_getInstanceMethod(metaClass, @selector(sayHello))
获取pClass
元类名为sayHello
的实例方法,这里sayHello
我们知道是实例方法不在元类中所以打印地址为0x0
class_getInstanceMethod(pClass, @selector(sayHappy)
获取类pClass
名为sayHappy
的实例方法,sayHappy
是类方法,那么自然找不到打印0x0
class_getInstanceMethod(metaClass, @selector(sayHappy))
获取pClass
元类名为sayHappy
的实例方法,我们前面已经分析过了,类方法在元类中以实例方法形态存在,所以可以打印出其地址class_getClassMethod(pClass, @selector(sayHello))
获取pClass
中名为sayHello
的类方法,显然无法找到,因为这是实例方法,打印0x0
class_getClassMethod(metaClass, @selector(sayHello))
在pClass
元类中查找sayHello
类方法,同理因为是实例方法元类中也无法找到,打印0x0
class_getClassMethod(pClass, @selector(sayHappy))
在类pClass
获取sayHappy
类方法,sayHappy
本身就是类方法,所以自然可以找到其地址class_getClassMethod(metaClass, @selector(sayHappy))
在pClass
元类中查找sayHappy
类方法,这里为什么可以打印出地址呢?因为在查找的过程中因为其方法本身是类方法直接走了cls->getMeta()
并不会在元类中再去查找实例方法,而是直接返回了地址class_getMethodImplementation
中我们看到全部都打印出了地址,那么这个class_getMethodImplementation
是什么意思呢?意思就是如果向一个类的实例发送一条消息,该函数会返回该条消息的IMP。也就是说我们去查找方法的过程中只要找到了符合的名字sel
就会返回其imp
内省
2.第二题如下,打印结果如何?
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; // 1
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; // 0
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; // 0
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; // 0
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]]; // 1
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; // 1
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; // 1
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; // 1
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
- 首先我们要知道什么是内省
在计算机科学中,内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也被称作运行时类型检查,不应该将内省和反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力
- ios中的内省
isMemberOfClass:Class:检查对象是否是那个类但不包括继承类而实例化的对象
isKindOfClass:Class:检查对象是否是那个类或者其继承类实例化的对象
isSubClassOfClass:检查某个类对象是否是另一个类型的子类
respondToSelector:selector:检查对象是否包含这个方法
instancesRespondToSelector::判断类是否有这个方法
conformsToProtocol:是用来检查对象是否实现了指定协议类的方法
我们知道什么是内省后再分析上面这道题,我们在底层源码中找到isKindOfClass
和isMemberOfClass
的实现如下
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (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)isKindOfClass:(Class)cls
中初始将tcls = [self class]
,然后再判断tcls == cls
是否成立,成立返回1,否则继续将tcls
赋值为其父类再做比较,如果都不成立返回0
re5
传入指向NSObject
的self
和为NSObject
的cls
比较直接返回1
,re7
中tcls
指向LGPerson
类,cls
为LGPerson
类比较返回1
-
- (BOOL)isMemberOfClass:(Class)cls
中传入的cls和其self实例的类进行比较,如果相等则返回1
re6
和re8
中分别将NSObject
和LGPerson
的self实例对象,将它们的所属的类和传入的NSObject
和LGPerson
的cls
比较,显然返回1
-
+ (BOOL)isKindOfClass:(Class)cls
将tcls
首先赋值为self的元类,然后和传入的cls
是否成立,成立返回1,否则继续将元类tcls
赋值为父类再做比较,如果都不成立返回0
re1
中首先将NSObject
的元类和NSObject
比较,显然不成立,那么接着走,tcls
变成NSObject
的元类的父类,那就是NSobject
,所以和传入的clsNSObject
是相等的,返回1
re3
中首先将LGPerson
元类和LGPerson
比较,不相等接着走tcls
指向根元类再比较,还是不相等,最后根源类tcls
的父类指向NSObject
类,还是不相等返回0
-
+ (BOOL)isMemberOfClass:(Class)cls
中将self
指向的元类和传入cls
比较,成立返回1
,不成立返回0
re2
和re4
中将NSObject
和LGPerson
的元类与传入的NSObject
类和LGPerson
类比较,显然不成立返回0
总结
isa
的走位图太经典了,看懂isa
的走位图可以解决很多问题,最后再次附上isa
走位图供参考