题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
。
首先我们要知道self
和super
是不同的,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);
然后我们还要知道isKindOfClass
和isMemberOfClass
的区别,前者代表对象是当前类或其子类的实例,后者代表对象是当前类的实例。
第一句:[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 MetaClass
的methodLists
里找foo
的实现,但是找不到,于是就会去NSObjct MetaClass
的父类NSObjct
的methodLists
里找foo
的实现,找到了,于是执行。
这就提醒我们要知道根据@selector(foo)
去对比查找方法的时候是不管你是类方法还是实例方法的,只要这个唯一标识符对上号就行了,到底是类方法还是实例方法只能从方法一开始查找的时候是要在元类里查还是普通类里查来确定,只不过这里NSObjct
和NSObjct 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入院考试
网友评论