instancesRespondToSelector:与respondsToSelector:
由于iOS是一门运行时语言,所以就不可避免地需要用到自省方法来判断当前消息是否可以被处理.其中用于判断方法是否是可以被响应的一个重要方法组就是 instancesRespondToSelector:和 respondsToSelector:,这两个方法在使用上是否相同呢?
查看源码实现
在objc源码中,找到了关于instancesRespondToSelector:的方法入口:
+ (BOOL)instancesRespondToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector(self, sel);
}
可以看出当sel为空时,会直接返回false;否则的话就会调用class_respondsToSelector方法:
BOOL class_respondsToSelector(Class cls, SEL sel)
{
return class_respondsToSelector_inst(cls, sel, nil);
}
继续调用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.
bool class_respondsToSelector_inst(Class cls, SEL sel, id inst)
{
IMP imp;
if (!sel || !cls) return NO;
// Avoids +initialize because it historically did so.
// We're not returning a callable IMP anyway.
imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, YES/*resolver*/);
return bool(imp);
}
/***********************************************************************
* lookUpImpOrNil.
* Like lookUpImpOrForward, but returns nil instead of _objc_msgForward_impcache
**********************************************************************/
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
最后方法进入了lookUpImpOrForward这个标准的方法查找流程中。
而respondsToSelector的入口实现:
+ (BOOL)respondsToSelector:(SEL)sel {
if (!sel) return NO;
return class_respondsToSelector_inst(object_getClass(self), sel, self);
}
发现该方法最终也是调用了class_respondsToSelector_inst这个底层的方法实现不同的是,这里传入方法的第一个参数是object_getClass(self)而不是self.
所以两个方法实现在这里产生了分歧。
object_getClass(id obj)
而object_getClass()方法的实现如下:
/***********************************************************************
+ object_getClass.
+ Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
可以看出,该方法的主要是实现了获取当前对象isa结构中指向的类:
- 如果当前对象是实例对象,那么object_getClass方法的返回值指向的就是该实例对象的的类对象;
- 如果当前对象是累对象,那么object_getClass方法返回值指向的就是该累对象的元类对象.
所以上述两个方法的区别就非常明显了:
- 使用instancesRespondToSelector:方法返回的结果表明的是当前类是否可以响应sel方法;
- 而使用responseToSelector:方法返回的结果表明的是当前类的isa结构中指向的类是否能够响应sel方法。
结论
instancesRespondToSelector:
- 这个方法的调用者只能是类,不能是实例对象;
- 其返回结果表明该类结构中是否存在能够响应sel对应的方法,也就是该类的对象是否能响应方法。
respondToSelector:
- 这个方法的调用者可以是类对象也可以是实例对象;
- 如果方法调用者是实例对象,那么返回结果表明的是当前实例对象对应的类中是否包含了sel对应的方法实现;如果是类对象,那么返回结果表明的是当前类对象对应的元类中是否包含了sel对应的方法实现。
上边的描述可能听起来比较拗口,用例子说明一下:
@interface Man: NSObject
+ (void)eatFood;
+ (void)writeCode;
@end
@implementation Man
+ (void)eatFood {
}
+ (void)writeCode {
}
@end
由于eatFood是类方法,所以如果需要判断方法eatFood是否实现,可以使用以下两种方法:
- 使用instancesRespondToSelector:需要注意的是,这个方法的调用者应该是instance对应的类,所以既然是类方法,那么调用者就应该是类对应的元类,所以应该这么干:
class cls = object_getClass([Man class]);
SEL sel = SEL_registerName("eatFood");
if ([cls instancesRespondToSelector: sel]) {
NSLog(@"实现了eatFood方法");
} else {
NSLog(@"未实现eatFood方法");
}
- 也可以使用respondToSelector:进行方法判断,但是这时候使用的调用对象就应该是Man本身
Class cls = [Man class];
SEL sel = sel_registerName("eatFood");
if ([cls respondsToSelector:sel]) {
NSLog(@"实现了eatFood方法");
} else {
NSLog(@"没有实现eatFood方法");
}
而对于writeCode是一个实例方法,所以如果需要判断方法writeCode是否实现,可以使用以下两种方法:
- 可以直接使用实例对象调用respondToSelector:
id obj = [[Man alloc] init];
SEL sel = sel_registerName("writeCode");
if ([obj respondsToSelector:sel]) {
NSLog(@"实现了writeCode方法");
} else {
NSLog(@"没有实现writeCode方法");
}
- 或者使用Man这个类来调用instancesRespondToSelector
Class cls = [Man class];
SEL sel = sel_registerName("writeCode");
if ([cls instancesRespondToSelector:sel]) {
NSLog(@"实现了writeCode方法");
} else {
NSLog(@"没有实现writeCode方法");
}
同样用法的还有,methodSignatureForSelector:,instanceMethodSignatureForSelector:等,差别就在与是否查询了传入对象的isa指向。