类(一)-- 底层探索
类(二)-- method归属
类(三)-- cache_t分析
本文内容:
- OC方法中的sel和imp
- 类中method的归属以及一些api的实现
- isKindOfClass & isMemberOfClass
1、OC方法中的sel和imp
OC是对C/C++的超集(此处不是错别字,超级集合的含义),也可以说OC是一种上层封装。OC中方法调用,为了能够找到指定的方法实现,就得对方法做一个“标识”,然后通过这个“标识”去找到具体的实现位置。方法中的sel
和imp
就是为了实现这个“功能”。
- sel:方法编号,也就是“标识”。
- imp:方法实现的指针,通过它来找到具体方法实现的位置。
可以把sel
看做一本书目录中的一条信息,imp
看做信息的页码。通过这条信息,找到指定页码,就可以翻到这页来查看相关内容。
2、类中method的归属
类中的实例方法(-方法)存在当前类中,类方法(+方法)存在元类中。其实是类的isa走位有关系,通过runtime的一些api可以进行确认。
定义一个DZPerson
类,类中分别定义了一个实例方法(sayHi
)和一个类方法(say666
):
@interface DZPerson : NSObject
- (void)sayHi;
+ (void)say666;
@end
@implementation DZPerson
- (void)sayHi{
NSLog(@"%s", __func__);
}
+ (void)say666{
NSLog(@"%s", __func__);
}
@end
⏬⏬⏬
//调用
DZPerson *per = [DZPerson alloc];
Class pCla = object_getClass(per);
Class metaCla = objc_getMetaClass("DZPerson");
- 创建一个
DZPerson
的对象per
- 获取
per
的类pCla
- 再获取
DZPerson
的元类metaCla
2.1 class_getInstanceMethod
class_getInstanceMethod
作用:获取类的实例方法
2.1.1 示例:
Method m1 = class_getInstanceMethod(pCla, @selector(sayHi));
Method m2 = class_getInstanceMethod(metaCla, @selector(sayHi));
Method m3 = class_getInstanceMethod(pCla, @selector(say666));
Method m4 = class_getInstanceMethod(metaCla, @selector(say666));
NSLog(@"m1:%p", m1);
NSLog(@"m2:%p", m2);
NSLog(@"m3:%p", m3);
NSLog(@"m4:%p", m4);
输出结果:
- m1:
DZPerson
类中查找sayHi
方法,sayHi
是实例方法,所以可以找到。 - m2:通过
m1
结果,sayHi
存在类中,所以DZPerson
元类中找不到sayHi
,因此m2返回地址为NULL
- m3&m4:同理,这次找的是类方法
say666
,存在元类中,所以m3
没结果,m4
有结果。
接着看看源码中class_getInstanceMethod
如何实现
2.1.2 class_getInstanceMethod源码分析
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
#warning fixme build and search caches
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
⏬⏬⏬
static Method _class_getMethod(Class cls, SEL sel)
{
mutex_locker_t lock(runtimeLock);
return getMethod_nolock(cls, sel);
}
⏬⏬⏬
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
method_t *m = nil;
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
while (cls && ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
cls = cls->superclass;
}
return m;
}
⏬⏬⏬
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
通过源码推导出的简要流程图:
其中最核心的代码就是getMethodNoSuper_nolock
函数中的auto const methods = cls->data()->methods();
这行代码(图中红色部分)。从当前类中获取方法列表methods()
,找到了直接返回;找不到再去父类中查找。
通过源码分析,很容易清楚class_getInstanceMethod
示例的运行结果。
2.2 class_getClassMethod
class_getClassMethod
作用:获取类方法
2.2.1 示例
Method m5 = class_getClassMethod(pCla, @selector(sayHi));
Method m6 = class_getClassMethod(metaCla, @selector(sayHi));
Method m7 = class_getClassMethod(pCla, @selector(say666));
Method m8 = class_getClassMethod(metaCla, @selector(say666));
NSLog(@"m5:%p", m5);
NSLog(@"m6:%p", m6);
NSLog(@"m7:%p", m7);
NSLog(@"m8:%p", m8);
输出结果:
- m5&m6:
sayHi
是实例方法,因此在类和元类中查找类方法,是肯定找不到。因此输出null
- m7&m8:
say666
是类方法,存储的位置是在元类中。==但是输出结果却是pCla
和metaCla
中都能找到,此时需要源码来帮助我们解惑。==
2.2.2 class_getClassMethod源码分析
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
//注意cls->getMeta()
return class_getInstanceMethod(cls->getMeta(), sel);
}
⏬⏬⏬
Class getMeta() {
if (isMetaClass()) return (Class)this;//元类直接返回
else return this->ISA();
}
- 通过源码,看到
class_getClassMethod
调用的class_getInstanceMethod
,这个函数我们上个示例已经研究过,就是找到类中的示例方法。 - 但是参数有变化,第一个参数是
cls->getMeta()
,获取类的元类。 -
getMeta
实现中,做了一层判断,如果是元类就直接返回;如果不是,获取当前类的元类。
通过源码的查看,我们了解到示例中m7
和m8
都有值的原因。在调用class_getClassMethod
时,分别传入的第一个参数是类和元类。
- m7:传入的是类,
getMeta
走else
分支,返回元类。在元类中查找say666
类方法。 - m8:传入的是元类,
getMeta
走if分支,返回自己。同理也可以找到。
2.3 class_getMethodImplementation
class_getMethodImplementation
作用:查找方法的imp
2.3.1 示例
IMP imp1 = class_getMethodImplementation(pCla, @selector(sayHi));
IMP imp2 = class_getMethodImplementation(metaCla, @selector(sayHi));
IMP imp3 = class_getMethodImplementation(pCla, @selector(say666));
IMP imp4 = class_getMethodImplementation(metaCla, @selector(say666));
NSLog(@"imp1:%p", imp1);
NSLog(@"imp2:%p", imp2);
NSLog(@"imp3:%p", imp3);
NSLog(@"imp4:%p", imp4);
输出结果:
- imp1&imp4:这个很好理解,实例方法
sayHi
和类方法say666
,分别通过类、元类与sel查找到imp - imp2&imp3:在类中查找类方法的imp、元类中查找实例方法的imp。为什么会有值,而且值还相等。同理进入源码看原因。
2.3.2 class_getMethodImplementation源码分析
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
if (!imp) {
return _objc_msgForward;
}
return imp;
}
通过源码看到,如果找不到imp
,就会同意调用_objc_msgForward
这个函数,这就是imp能找到并且返回相同地址原因。
3、isKindOfClass & isMemberOfClass
这两个方法在正常开发流程中,使用率还是挺高的。但是很多人可能不了解,这两个方法也有类方法(+方法)。先来看看示例:
3.1 示例
BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [[DZPerson class] isKindOfClass:[DZPerson class]];
BOOL re4 = [[DZPerson class] isMemberOfClass:[DZPerson class]];
NSLog(@"re1 :%d", re1);
NSLog(@"re2 :%d", re2);
NSLog(@"re3 :%d", re3);
NSLog(@"re4 :%d", re4);
NSLog(@"====================分割线====================");
BOOL re5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [[DZPerson alloc] isKindOfClass:[DZPerson class]];
BOOL re8 = [[DZPerson alloc] isMemberOfClass:[DZPerson class]];
NSLog(@"re5 :%d", re5);
NSLog(@"re6 :%d", re6);
NSLog(@"re7 :%d", re7);
NSLog(@"re8 :%d", re8);
注意:上半部分调用的是类方法,下半部分调用实例方法。
输出结果:
re1 :1
re2 :0
re3 :0
re4 :0
====================分割线====================
re5 :1
re6 :1
re7 :1
re8 :1
- 如果结果与你想的一样,或者你看到结果能够推断出来原因,说明你对isa和superclass的走位图已经很熟悉了。
- 如果不理解,请继续往下看。
3.2 isKindOfClass & isMemberOfClass源码
先把isa走位图放在这里,请再仔细看看
3.2.1 objc_opt_isKindOfClass源码
接着我们看源码:
此处需要注意,isKindOfClass
的imp是objc_opt_isKindOfClass
。使用汇编调试的时候可以确认这点
此处和alloc
方法一样,在llvm编译的时候,苹果底层进行了hook
接下来看看objc_opt_isKindOfClass
实现,解释一下上面的示例
// Calls [obj isKindOfClass]
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);
}
- 实现中有两个参数,
obj
和otherClass
。-
obj
是isKindOfClass
方法的调用者 -
otherClass
是isKindOfClass
的参数
-
- 源码中通过获取
obj
的isa
,通过isa
的继承链进行查找比较-
obj
是对象,那么isa就是对象的类 -
obj
是类,那么isa就是元类
-
了解完实现再来对示例中代码解读一下:
-
BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
- 这句代码第一次循环,获取
NSObject
的元类,也就是根元类,与NSObject
对比。第一次比较失败。 - 第二次循环,根元类找到它的父类,也就是
NSObject
类。再与NSObject
类比较,此时相等,所以打印结果是1
- 这句代码第一次循环,获取
-
BOOL re3 = [[DZPerson class] isKindOfClass:[DZPerson class]];
-
DZPerson
的元类与DZPerson
类进行对比。不相等继续找DZPerson
的元类的父类一直到nil
,都找不到与DZPerson
相等,所以打印结果是0
-
-
BOOL re5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
:NSObject
实例的类就是NSObject
,与NSObject
比较必然相等,打印1
-
BOOL re7 = [[DZPerson alloc] isKindOfClass:[DZPerson class]];
:这个同理,打印1
3.2.2 isMemberOfClass源码
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
-
类方法
isMemberOfClass
,是用self的isa与传入的参数进行对比-
BOOL re2 = [[NSObject class] isMemberOfClass:[NSObject class]];
:NSObject
的元类与NSObject
不相等,所以打印结果是0
-
BOOL re4 = [[DZPerson class] isMemberOfClass:[DZPerson class]];
:同理,DZPerson
的元类与DZPerson
也不相等,打印结果是0
-
实例方法
isMemberOfClass
,获取自己的类与参数比较,re6
和re8
都打印1
3.3 小结
-
isKindOfClass
的实例方法和类方法,它们的的imp都是objc_opt_isKindOfClass
函数。 -
isMemberOfClass
类方法找的是类的isa,实例方法找的的类。 - isKindOfClass会循环用父类和参数比较,最后找到nil
- isMemberOfClass只会比较一次
- 最重要的点,一定要理解isa和superclass的走位图。