系列:
【靠谱程序员#6】《招聘一个靠谱的程序员》个人解答(end)
21. 下面的代码输出什么?
@implementation Son : Father
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
输出结果:
Son
Son
答:
1)、self
self是一个隐藏参数,在实例方法中,self表示当前实例对象,在类方法中,self表示类对象。
- (void)printAge {
}
- (void)showAge {
[self printAge]; //self表示当前实例
}
+ (void)test {
}
+ (void)printMan {
[self test]; //self表示当前类
}
2)、super
super 是一个 Magic Keyword, 它本质是一个编译器标示符,和 self 是指向的同一个消息接受者! 他们两个的不同点在于:super 会告诉编译器,调用方法要去调用父类的方法。 此时调用class 这个方法时调用的不是本类里的。
上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *xxx 这个对象。
当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法。
拓展:
下面代码会输出什么:
//Father
@interface Father : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Father
- (instancetype)init {
if (self = [super init]) {
self.name = @"super class";
NSLog(@"class-----%@", NSStringFromClass([self class]));
}
return self;
}
- (void)setName:(NSString *)name {
_name = [name copy];
NSLog(@"super class");
}
@end
//Son.m
@implementation Son
@synthesize name = _name;
- (instancetype)init {
if (self = [super init]) {
self.name = @"sub class";
}
return self;
}
- (void)setName:(NSString *)name {
_name = [name copy];
NSLog(@"sub class");
}
@end
调用
Son *son = [[Son alloc] init];
会打印什么? 打印结果是:
sub class
sub class
解释:
还是self和super关键字的作用,Son调用init方法,然后会去父类Father执行父类的init方法,然后父类的init方法里面有一个setName,此时调用该setName方法的消息接收者其实还是Son类的self,所以此时还是会去Son类的方法列表里面寻找setName方法,然后打印sub class,后面打印那个sub class应该没有什么问题,所以是打印两次sub class
tips:
不推荐在 init 方法中使用点语法
如果想访问实例变量 iVar 应该使用下划线( _iVar ),而非点语法( self.iVar )。
点语法( self.iVar )的坏处就是子类有可能覆写 setter 。
进阶:
让我们利用 runtime 的相关知识来验证一下 super 关键字的本质,使用clang重写命令:
$ clang -rewrite-objc test.m
将这道题目中给出的代码被转化为:
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));
从上面的代码中,我们可以发现在调用[self class]时,会转化成 objc_msgSend函数。看下函数定义:
id objc_msgSend(id self, SEL op, ...)
我们把 self 做为第一个参数传递进去。
而在调用 [super class]时,会转化成 objc_msgSendSuper函数。看下函数定义:
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一个参数是 objc_super 这样一个结构体,其定义如下:
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};
结构体有两个成员,第一个成员是 receiver, 类似于上面的 objc_msgSend函数第一个参数self 。第二个成员是记录当前类的父类是什么。
所以,当调用 [self class]时,实际先调用的是 objc_msgSend函数,第一个参数是 Son当前的这个实例,然后在 Son 这个类里面去找- (Class)class这个方法,没有,去父类 Father里找,也没有,最后在 NSObject类中发现这个方法。而 - (Class)class的实现就是返回self的类别,故上述输出结果为 Son。
objc Runtime开源代码对- (Class)class方法的实现:
- (Class)class {
return object_getClass(self);
}
而当调用[super class]时,会转换成objc_msgSendSuper函数。第一步先构造 objc_super 结构体,结构体第一个成员就是 self 。 第二个成员是 (id)class_getSuperclass(objc_getClass(“Son”)), 实际该函数输出结果为 Father。
第二步是去 Father这个类里去找- (Class)class,没有,然后去NSObject类去找,找到了。最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用,
此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son。
22. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
答:
每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现。
IMP在objc.h中的定义是:
typedef id (*IMP)(id, SEL, ...);
它就是一个函数指针,这是由编译器生成的。当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法,有空可以说说这个。 你会发现IMP指向的方法与objc_msgSend函数类型相同,参数都包含id和SEL类型。每个方法名都对应一个SEL类型的方法选择器,而每个实例对象中的SEL对应的方法实现肯定是唯一的,通过一组id和SEL参数就能确定唯一的方法实现地址;反之亦然。 当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找。 当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找。
每个对象都存在一个isa指针,它指向该类的类结构,而该类结构有一个指向其父类类结构的指针superclass, 以及自身消息名称(selector)/实现地址(address)的方法链表。
注意这里所说的方法链表里面存储的是Method 类型的。其中selector 就是指 Method的 SEL, address就是指Method的 IMP。 Method 在头文件 objc_class.h中定义如下:
typedef struct objc_method *Method;
typedef struct objc_ method {
SEL method_name;
char *method_types;
IMP method_imp;
};
一个方法 Method,其包含:
- 一个方法选标 SEL – 表示该方法的名称,实际存放的是方法的编号;
- 一个types – 表示该方法参数的类型;
- 一个 IMP - 指向该方法的具体实现的函数指针。
IMP指向的方法与objc_msgSend函数类型相同,参数都包含id和SEL类型。每个方法名都对应一个SEL类型的方法选择器,而每个实例对象中的SEL对应的方法实现肯定是唯一的,通过一组id和SEL参数就能确定唯一的方法实现地址;反之亦然。 当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找。 当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找。
所以使用selector可以通过方法名(方法的index/id)和方法参数匹配到唯一对应实现的IMP。
拓展:
Q1:有什么办法可以知道方法编号呢?
@selector()就是取类方法的编号。
SEL methodId = @selector(func1);
Q2:编号获取后怎么执行对应方法呢?
[self performSelector:methodId withObject:nil];
Q3:有没有办法通过编号获取方法?
NSString *methodName = NSStringFromSelector(methodId);
Q4:IMP怎么获得和使用
IMP methodPoint = [self methodForSelector:methodId];
methodPoint();
23. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
答:
无论在MRC下还是ARC下均不需要。
Associate政策其实是一组枚举值:
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
无论在MRC下还是ARC下均不需要在主对象dealloc的时候释放,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放。
补充:对象的内存销毁时间表,分四个步骤
1、调用 -release :引用计数变为零
* 对象正在被销毁,生命周期即将结束.
* 不能再有新的 __weak 弱引用,否则将指向 nil.
* 调用 [self dealloc]
2、 父类调用 -dealloc
* 继承关系中最直接继承的父类再调用 -dealloc
* 如果是 MRC 代码 则会手动释放实例变量们(iVars)
* 继承关系中每一层的父类 都再调用 -dealloc
3、NSObject 调 -dealloc
* 只做一件事:调用 Objective-C runtime 中object_dispose() 方法
4. 调用 object_dispose()
* 为 C++ 的实例变量们(iVars)调用 destructors
* 为 ARC 状态下的 实例变量们(iVars) 调用 -release
* 解除所有使用 runtime Associate方法关联的对象
* 解除所有 __weak 引用
* 调用 free()
回顾:
static char *propertyKey = "propertyKey";
- (id)property {
return objc_getAssociatedObject(self, &propertyKey);
}
- (void)setProperty:(id)property {
objc_setAssociatedObject(self, &propertyKey, property, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
24. objc中的类方法和实例方法有什么本质区别和联系?
答:
类方法:
类方法是属于类对象的
类方法只能通过类对象调用
类方法中的self是类对象
类方法可以调用其他的类方法
类方法中不能访问成员变量
类方法中不能直接调用对象方法
实例方法:
实例方法是属于实例对象的
实例方法只能通过实例对象调用
实例方法中的self是实例对象
实例方法中可以访问成员变量
实例方法中直接调用实例方法
实例方法中也可以调用类方法(通过类名)