美文网首页程序员
第五篇:runtime的几道面试题

第五篇:runtime的几道面试题

作者: 意一ineyee | 来源:发表于2017-08-16 16:55 被阅读15次
题1(考查super关键字):下面的代码输出什么?

@implementation Son : Father

- (instancetype)init {
    
    self = [super init];
    if (self != nil) {
        
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}

@end

答案:都输出Son

首先我们要知道selfsuper是不同的,self代表当前类或当前对象,可以用来作为消息接受者,而super其实仅仅是一个编译器指示符,作用是告诉当前消息接收者去它的父类里找方法的实现,而不是在当前类中找,super的消息接受者其实还是self

题目中,self调用class方法最终会转化为objc_msgSend(self, @selector(class))的调用,而super调用class方法最终会转化为objc_msgSendSuper(self, @selector(class))的调用,两者的消息接受者是相同的,都是self,只不过前者是直接在当前类中找class方法的实现,后者是去父类中找class方法的实现,然而当前类和父类class方法的实现都是一样的,都是返回当前对象所属的类,所以两者都会打印Son

所以不要把[super class][self superclass]弄混了,后者肯定打印Father的。

题2(考查对两个Class方法的理解和元类):下面的代码输出什么?

BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Person class] isKindOfClass:[Person class]];
BOOL res4 = [(id)[Person class] isMemberOfClass:[Person class]];

答案:YES、NO、NO、NO。

首先我们要知道获取Class的几个方法(runtime源码如是):

// 这是一个类方法,[类 class]时会调用该方法,返回类本身
+ (Class)class {
    
    return self;
}
// 这是一个实例方法,[实例 class]时会调用该方法,返回当前对象所属的类
- (Class)class {
    
    return isa;
}


// 返回当前对象所属的类,无论object是普通对象还是类对象:如果object是一个普通对象,则返回它所属的类;如果object是一个类对象,则返回它的元类
Class object_getClass(id object);


// 通过类名的字符串来获取一个类
Class objc_getClass(const char *className);
// 通过类名的字符串来获取一个类的元类
Class objc_getMetaClass(const char *className);

然后我们还要知道isKindOfClassisMemberOfClass的区别,前者代表对象是当前类或其子类的实例,后者代表对象是当前类的实例。

这个图再贴一遍:红条isa指向,蓝条继承关系

第一句:[NSObject class]调用的是+class方法,所以获取到的是NSObject类本身,所以这句话的意思就是问NSObject类对象是不是NSObject类或其子类的实例,而我们知道NSObject类对象所属的类是NSObject MetaClass,而NSObject MetaClass继承于NSObject,所以NSObject类对象是NSObject类的子类NSObject MetaClass的一个实例,所以返回YES。

第二句:NSObject类对象不是NSObject类的一个实例,所以返回NO,分析同一。

第三句:[Person class]调用的是+class方法,所以获取到的是Person类本身,而Person类对象是Person MetaClass的实例,但是Person MetaClass不继承于Person类,所以Person类对象不是Person类或其子类的实例,所以返回NO。

第四句:Person类对象不是Person类的实例,所以返回NO。

3、(考查方法的调用流程):下面的代码会怎样?Compile Error / Runtime Crash / NSLog…,怎样NSLog…?

@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
    NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 测试代码
[NSObject foo];
[[NSObject new] foo];

答案:不会崩掉,也不会编译报错,而是正常运行打印,并且两段测试代码都会走-foo方法打印IMP: -[NSObject (Sark) foo]

首先我们要知道一个类所有的实例方法都存在自己的methodLists里,一个类所有的类方法都存在它元类的methodLists里。

第二句就不用说了,主要是第一句,NSObjct类是在调用类方法foo,所以首先去它的元类NSObjct MetaClassmethodLists里找foo的实现,但是找不到,于是就会去NSObjct MetaClass的父类NSObjctmethodLists里找foo的实现,找到了,于是执行。

这就提醒我们要知道根据@selector(foo)去对比查找方法的时候是不管你是类方法还是实例方法的,只要这个唯一标识符对上号就行了,到底是类方法还是实例方法只能从方法一开始查找的时候是要在元类里查还是普通类里查来确定,只不过这里NSObjctNSObjct MetaClass的关系比较特殊,刚开始明明是要查一个类方法,结果因为NSObjct MetaClass的父类是NSObjct又绕回来查实例方法去了。

4、(考查OC对象的构成条件):下面的代码会怎样?Compile Error / Runtime Crash / NSLog…,怎样NSLog…?

@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak {
    NSLog(@"my name's %@", self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Sark class];
    void *obj = &cls;
    [(__bridge id)obj speak];
}
@end

答案:不会崩掉,也不会编译报错,而是正常运行打印,并且打印打印当前ViewController。

第一句获取Sark类并赋值给cls;第二句又把obj指针指向Sark类,就使得obj满足了构成一个OC对象的全部条件(首地址指向一个类);所以第三句obj能正常调用speak方法,为什么打印当前ViewController还没搞明白。


参考博客:神经病院objc runtime入院考试


相关文章

网友评论

    本文标题:第五篇:runtime的几道面试题

    本文链接:https://www.haomeiwen.com/subject/nzcmrxtx.html