面试题一
在项目中新建Person类继承NSObject,在Person类中打印
NSLog(@"%@--%@--%@--%@",[self class],[super class],[self superclass],[super superclass]);
打印结果分别是什么?
源码分析
回答这个面试题之前我们需要先弄懂两点:
•class与superClass方法
•super关键字
class方法
OC源码下载地址:https://opensource.apple.com/tarballs/
搜索objc4下载最新源码。打开源码,搜索文件NSObject.mm,可以找到class方法与superClass,我们这里看的是实例方法也就是'—'开头的方法
- (Class)class {
return object_getClass(self);
}
- (Class)superclass {
return [self class]->superclass;
}
分析:class方法的返回值是self的类对象,superClass方法的返回值是self的类对象的父类。self是谁呢?也就是方法调用者,用Runtime的说法就是消息接收者。调用class方法编译成c\c++代码是msg_send(self,@selector(class))
,因为我们是在Person类中的方法中写的该语句,所以消息接收者self也就是Person的实例对象。所以调用[self class]返回结果就是Person类,调用[self superClass]返回结果就是Person类的父类NSObject。
super关键字
了解super关键字做了什么我们可以在-(void)viewDidLoad方法中[super viewDidLoad]打断点。然后
1.png我们可以看到 image.png
通过以上操作我们可以知道使用super关键字后,消息发送不是我们常用的
objc_msgSend
了,而是objc_msgSendSuper2
了。
* id objc_msgSendSuper2(struct objc_super *super, SEL op, ...)
*
* struct objc_super {
* id receiver;
* Class cls; // SUBCLASS of the class to search
* }
我们可以得知:使用super后,消息接收者成为了一个结构体。这个结构体内一个是真正的消息接收者receiver,一个是类对象。
内部实现只有汇编代码,所以这里不贴代码直接说出结论:
使用super关键字后,会根据消息接收者结构体中的类对象找到父类对象,从父类对象中开始寻找方法,如果没有找到上一级父类继续寻找。而不是我们一般的方法调用时先从类对象中查找方法,没有的话再去父类。最终找到NSObject中的class方法时,返回的类对象其实还是真正的消息接收者的类对象,也就是结构体中的receiver的类对象。所以,使用[super class]和[self class]输出结果是一样的。[super superClass]与[self superClass]结果一样。
总结
[self class]与[super class]打印结果一样,区别在于,使用self 调用方法是先去本类对象中查找方法,使用super 调用方法时,先去父类对象中查找方法。[self superClass]与[super superClass]同理。
面试题二
已知LZPerson类继承NSObject,一下代码的输出结果是:
BOOL res1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [[LZPerson class] isKindOfClass:[LZPerson class]];
BOOL res4 = [[LZPerson class] isMemberOfClass:[LZPerson class]];
NSLog(@"%d-%d-%d-%d",res1,res2,res3,res4);
源码分析
同上,在源码中搜索文件NSObject可找到以下方法
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); 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;
}
减号开头的实例方法
isMemberOfClass
是看消息接收者的类对象是不是和你传入的类对象一样。
代码为例:
LZPerson *lz = [[LZPerson alloc]init];
BOOL res5 = [lz isMemberOfClass:[LZPerson class]]
就是判断实例对象lz的类对象是不是LZperson类,结果为YES。
isKindOfClass
是看消息接收者的类对象是不是和你传入的类对象一样或者是你传入类对象的子类。
代码为例:
LZPerson *lz = [[LZPerson alloc]init];
BOOL res5 = [lz isKindOfClass:[LZPerson class]]
就是判断实例对象lz的类对象是不是LZperson类或者是LZPerson类对象的子类,结果是YES。
加号开头的类方法
isMemberOfClass
是看消息接收者的元类对象是不是和你传入的类对象一样。
代码为例:
BOOL res5 = [LZPerson isMemberOfClass:[LZPerson class]]
就是判断LZPerson的元类是不是LZPerson类,结构是NO。
isKindOfClass
是看消息接收者的元类对象是不是和你传入的类对象一样或者是传入类对象的子类。
代码为例:
BOOL res5 = [LZPerson isKindOfClass:[LZPerson class]]
就是判断LZPerson的元类是不是LZPerson类或者是LZPerson类的子类,结构是NO。
总结
根据以上的源码分析,我们可以看一下面试题也就相当于
BOOL res1 = [NSObject isKindOfClass:[NSObject class]];
BOOL res2 = [NSObject isMemberOfClass:[NSObject class]];
BOOL res3 = [LZPerson isKindOfClass:[LZPerson class]];
BOOL res4 = [LZPerson isMemberOfClass:[LZPerson class]];
我们可以得知以上全是调用的类方法,所以也就是比较元类。
第一句:NSObject的元类是不是NSObject类或者是NSObject类的子类。答案是YES。原因见OC对象的分类文章中最后一张经典图片。
第二句:NSObject的元类是不是NSObject类,答案是NO。
第三句:LZPerson的元类是不是LZPerson类或者是LZPerson类的子类。答案是NO。
第四句:LZPerson的元类是不是LZPerson类,答案是NO。
结语
希望你能得知面试题答案的同时了解到更多的知识。
网友评论