【靠谱程序员#3】《招聘一个靠谱的程序员》个人解答

290 阅读9分钟

系列:

【靠谱程序员#0】《招聘一个靠谱的程序员》个人解答

【靠谱程序员#1】《招聘一个靠谱的程序员》个人解答

【靠谱程序员#2】《招聘一个靠谱的程序员》个人解答

【靠谱程序员#3】《招聘一个靠谱的程序员》个人解答

【靠谱程序员#4】《招聘一个靠谱的程序员》个人解答

【靠谱程序员#5】《招聘一个靠谱的程序员》个人解答

【靠谱程序员#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 - 指向该方法的具体实现的函数指针。

objc-selector

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是实例对象
实例方法中可以访问成员变量
实例方法中直接调用实例方法
实例方法中也可以调用类方法(通过类名)

下一篇:【靠谱程序员#4】《招聘一个靠谱的程序员》个人解答