iOS isa理解测试:实例方法,类方法,iskindof,is
面试题一:考察实例方法和类方法
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#ifdef DEBUG
#define LGLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define LGLog(format, ...);
#endif
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
@implementation LGPerson
- (void)sayHello{
LGLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
LGLog(@"LGPerson say : Happy!!!");
}
@end
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello)); // 1
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello)); // 0
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy)); // 0
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));// 1
LGLog(@"%s===%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgClassMethod_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));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
LGLog(@"%s===%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgIMP_classToMetaclass(Class pClass){
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));
LGLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
LGLog(@"%s",__func__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
Class pClass = object_getClass(person);
lgInstanceMethod_classToMetaclass(pClass);
lgClassMethod_classToMetaclass(pClass);
LGLog(@"Hello, World!");
}
return 0;
}
对于这道题首先一点我们要知道OC里面方法的存储是跟isa的走位图紧密相连的;
isa流程图.png1、OC对象的
实例方法
存储类对象
2、OC对象的类方法
存储在元类对象
这的两个方法为
- (void)sayHello: 实例方法
+ (void)sayHappy:类方法
为了不误导大家我先直接打印答案看看
结合isa走位图,根据答案分析,
-
1、 在函数
lgInstanceMethod_classToMetaclass(Class pClass)
中-
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
能找到方法method1
,符合预期 -
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
不能找到方法method2
,符合预期 -
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
不能找到方法method3
,符合预期 -
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
能找到方法method4
符合预期
-
-
2、在函数
lgClassMethod_classToMetaclass(Class pClass)
中-
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
不能找到方法method1
,符合预期 -
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
不能找到方法method2
,符合预期 -
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
能找到方法method3
, 符合预期 -
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
在这里我需要先指出正确答案: 能找到方法;奇了怪,但是按照我们 isa的走位分析,这里应该去元类的元类(根元类)里面找sayHappy
了啊,应该找不到啊,我们不妨看看class_getClassMethod
源码
-
//objc-class.mm
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
//.....
//objc_rumtime-new.h
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
我们可以清晰的看到class_getClassMethod
方法其实实质上是调用的class_getInstanceMethod
,并且是找Class
的元类
;而元类的搜索逻辑是:如果当前类已经是元类
,则返回当前类。这就很好解释了为什么在元类里面找类方法
能找到了。
- 3、 在函数
lgIMP_classToMetaclass(Class pClass)
中-
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
能找到方法的实现imp1
,符合预期 -
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
居然找到的方法的实现,我们前面都在函数lgInstanceMethod_classToMetaclass
中分析打印了Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
的结果:不能找到方法。现在怎么找到了这里方法的实现imp2
呢?我们看看class_getMethodImplementation
源码
-
//objc_class.mm 文件
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;
}
// ...
// message.h 文件
OBJC_EXPORT void
_objc_msgForward(void /* id receiver, SEL sel, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
// ...
//objc-msg-arm.s 文件
STATIC_ENTRY __objc_msgForward_impcache
// Method cache version
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band Z is 0 (EQ) for normal, 1 (NE) for stret
beq __objc_msgForward
b __objc_msgForward_stret
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// Non-stret version
在这里我们可以很清晰的看见:***当lookUpImpOrNil
找不到方法实现的时候,直接返回_objc_msgForward
的函数指针,而这个·_objc_msgForward`的实现是在汇编里面的,所以必定存在;
-
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
,根据前面的分析,能找到方法imp3
, 应该为_objc_msgForward
的实现 -
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
能找到方法实现imp4
;
面试题二:考察iskindof,isMemberof理解
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface LGPerson : NSObject
@end
@implementation LGPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@"\n 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)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
首先我们来看看结果
首先我们需要对
isKindOfClass
和isMemberOfClass
这两个方法有所了解我们先来看看因为解释
-
- (BOOL)isKindOfClass:(Class)aClass;
Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class.大意: 判断当前
接收者
(这里可以理解为调用者)是否是给出类
(这里可以理解为传入的aClass
参数)或者其子类的实例。 -
- (BOOL)isMemberOfClass:(Class)aClass;
Returns a Boolean value that indicates whether the receiver is an instance of a given class.
大意:判断当前接收者
是否是给出类
的实例。
对于类方法
- 【1】、
[(id)[NSObject class] isKindOfClass:[NSObject class]];
我们知道[NSObject class]
会调用类方法+class
;我们查看源码得知
- (Class)class {
return object_getClass(self);
}
+ (Class)class {
return self;
}
返回的是当前类自己
Note:其实在真正运行的时候,
llvm
针对id
类型会做一层处理调用的真正的实现函数是如下逻辑,当发现cls->hasCustomCore()
为false
时,直接返回当前obj
或者obj->isa
指向的Class
// 源码文件NSObject.mm文件中可以找到
// Calls [obj class]
Class
objc_opt_class(id obj)
{
#if __OBJC2__
if (slowpath(!obj)) return nil;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
return cls->isMetaClass() ? obj : cls;
}
#endif
return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));
}
所以当我们调用实例方法
或类方法
class
时返回的都是当前的类对象
;
接下到我们的isKindof
这个实例方法
;为什么是实例方法呢?因为我们将它强转成了id类型,所以我们看看源码
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
Note:
1、我们在源码文件NSObject.mm
发现了+isKindOfClass
的类方法,这个NSObject
类方法是存贮在根元类
里面的, 在我们面试题中,其本质是Class
类型,只是强转成了id
类型,当调用isKindOfClass
方法时,其实是会去Class
指向的元类
里面查找方法, 即会调用+isKindOfClass
;不过在正式环境中我们是看不到这个类方法
的,仅能看到实例方法
。
2、注意重点:其实这个方法也是忽悠人的,经过查看llvm
源码发现iskindOfclass
经过了优化处理,其真实会调用的方法是
// 源码文件NSObject.mm文件中可以找到
// 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
是NSObject
的类对象
,cls
和tcls
是根元类
,很明显当前是tcls == otherClass
不成立,当我们第二次来的时候tcls
去取他的父类赋值到tcls
,根据isa走位图
,我们知道根元类的父类是NSObject的类对象;所以此时tcls == otherClass
成立返回为true
。
- 【2】、
[(id)[NSObject class] isMemberOfClass:[NSObject class]];
对于isMemberOf
这个方法源码去看看;
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
这里就相当简单了:直接判断当前的实例的类对象是否等于传入的cls
。
当我们调用isMemberOfClass
方法时,self
就是[NSObject class]
经过强转之后的id
类型(其本质就是NSObject类对象),当经过[self class]
取class操作之后,会变为NSObject元类对象
和NSObject类对象
比较,所以为false
;
- 【3】、[(id)[LGPerson class] isKindOfClass:[LGPerson class]]; 这里最终是
LGPerson类对象
和LGPerson元类对象
或者其父类
的比较,为false
; - 【4】 、
[(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
【3】都是false,这里还是它的子集,更加为false
; - 【5】、
[(id)[NSObject alloc] isKindOfClass:[NSObject class]];
这里简单分析true
; - 【6】、
[(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
这里简单分析true
; - 【7】、
[(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
这里简单分析true
; - 【8】、
[(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
这里简单分析true
;
两道面试题的总结:
需要充分理解isa的走位图:方法的存储和类型的继承。