内省和自省是一个概念,下文使用内省替代;
什么是内省(Introspection)?
在计算机科学中,内省是指计算机程序在运行时(Runtime)检查对象(Object)类型的一种能力,通常也可以称作“运行时类型检查”。一些编程语言如C++、Java、Ruby、PHP、Objective-C、Perl等等具有这种特性。
Objective-C 内省
Objective-C
中的内省方法都定义在NSObject
协议中。
除此之外还有些人认为 + instancesRespondToSelector:
或者 + conformsToProtocol:
也是内省方法,个人认为这两个不能算是内省方法,虽然它们可以做到 respondsToSelector
和 conformsToProtocol
一样的效果,但由于它们是类方法,也就是说它们的方法调用者并不是一个实例对象,所以不能将其归到内省方法中。
isKindOfClass
直接来看runtime
源码吧,isKindOfClass
最终调用就是 NSObject.mm
中的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);
}
复制代码
在当前环境下,执行的都是 Objective-C 2.0
的代码。关于 runtime
中的宏 请看这里
先看这一句 if (slowpath(!obj)) return NO;
,表示如果 obj
是个 nil
的话,直接返回 NO
。
Class cls = obj->getIsa();
通过实例对象的 isa
指针获取它的 Class
对象 cls
。fastpath(!cls->hasCustomCore())
表示是否自己实现了 new/self/class/respondsToSelector/isKindOfClass
的实例方法,没有实现的话就会执行 if
内部的代码。
这个 for
循环比较简单,就是从 obj
对应的 Class
对象沿着继承关系开始寻找和 otherClass
相同的 Class
对象。顺手提一句 ==
大家都知道是内存地址的比较,tcls == otherClass
相等,说明他们的内存是相同的,也从侧面说明 Class
对象也是对象,存在于内存中。
isMemberOfClass
稍微修改一下代码
CZPerson *objc = [[CZPerson alloc] init];
[objc isMemberOfClass:[NSObject class]];
复制代码
在来看一下调用过程
这和 isKindOfClass
有点区别,使用的是消息发送,说明它底层是调用了 Objective-C
方法。 在NSObject.mm
中有 isMemberOfClass
的默认实现
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
复制代码
从这里看出 isMemberOfClass
逻辑比较简单,就是比较两个 Class
对象的内存地址是否相同,也就是调用者的Class
和参数Class
必须是同一个。
respondsToSelector
respondsToSelector
指的是对象是否 实现 了某个方法,如果只是声明没有实现的话,返回值为 NO
。看下源码更加直观。
CZPerson *objc = [[CZPerson alloc] init];
[objc respondsToSelector:@selector(testMethod)];
复制代码
再来看一下调用过程
我们来看一下 NSObject.mm
中的 objc_opt_respondsToSelector
实现
// Calls [obj respondsToSelector]
BOOL
objc_opt_respondsToSelector(id obj, SEL sel)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
return class_respondsToSelector_inst(obj, sel, cls);
}
#endif
return ((BOOL(*)(id, SEL, SEL))objc_msgSend)(obj, @selector(respondsToSelector:), sel);
}
复制代码
这里的逻辑和 isKindOfClass
有点类似,就不做赘述了,直接说结果。如果对象没有重写 respondsToSelector
,那么就调用 class_respondsToSelector_inst
返回结果。
// inst is an instance of cls or a subclass thereof, or nil if none is known.
// Non-nil inst is faster in some cases. See lookUpImpOrForward() for details.
NEVER_INLINE __attribute__((flatten)) BOOL
class_respondsToSelector_inst(id inst, SEL sel, Class cls)
{
// Avoids +initialize because it historically did so.
// We're not returning a callable IMP anyway.
return sel && cls && lookUpImpOrNilTryCache(inst, sel, cls, LOOKUP_RESOLVER);
}
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
复制代码