课程笔记:第五章 Runtime相关面试问题

数据结构

我们主要学习四种数据结构:

  • objc_object

  • objc_class

  • isa指针

  • method_t

    typedef struct objc_class *Class;
    typedef struct objc_object *id;

  1. objc_object

我们平时所使用的对象都是id类型的,id类型的对象对应到 runtime 中实际上代表的就是 objc_object 的结构体,这个结构体主要包含以下几个部分

其中 isa_t 是一个共用体。

  1. objc_class

我们平时用到的 Class 就对应 objc_class 的数据结构,这也是一个结构体,objc_class 继承自 objc_object

superClass 指向它的父类,cache 表示方法缓存,bits 里主要存放定义的属性等相关信息

  1. isa指针
image.png
image.png
image.png
image.png
image.png
image.png
image.png

数据类型总结


image.png

类对象与元类对象
实例对象、类对象、元类对象
类对象存储实例方法列表等信息
元类对象存储类方法列表等信息


image.png

需要注意的地方:
元类对象的isa指针,都是指向根元类对象。包括根元类对象本身,也是指向根元类对象
根元类对象的superclass指针,指向的是根类对象

问:元类对象的isa指针指向哪里?
元类对象的isa指针,都是指向根元类对象。包括根元类对象本身,也是指向根元类对象

问:如果一个类方法没有实现,但是有同名的对象方法实现,会崩溃?还是会调用?
对象方法存储在类对象里面;
类方法存储在元类对象里面;

这个问题应该这样看:
如果一个类方法没有实现,根据superclass指针,会去父类里面找同名类方法,直至到根元类对象里面去查找;
如果中间找到了对应的同名类方法,则会调用。
如果中间没有找到对应的同名类方法,则根元类对象的superclass指向的是根类对象
如果根类对象里面有对应的同名实例方法(类对象(包括根类对象)只能存储实例方法),则会调用。
如果根类对象里面没有对应的同名实例方法,则根据消息传递原则,进入动态方法解析阶段。

举个例子:
@interface YZPerson : NSObject

  • (void)run;
  • (void)run;
    @end

import "YZPerson.h"

@implementation YZPerson
//+ (void)run
//{
// NSLog(@"类方法-run");
//}

  • (void)run
    {
    NSLog(@"实例方法-run");
    }
    @end

  • (void)viewDidLoad {
    [super viewDidLoad];
    [YZPerson run];
    }
    结果:崩溃


    image.png

    也就是说,person里面虽然有同名的对象方法-(void)test;,但是,不好意思,superclass指针最多也就到达根类对象里面,到达不了类对象person里面,所以,还是找不到-(void)test;

换句话说,如果类方法没有实现,只有在根类对象里面有同名实例方法,才能调用。

根类对象一般指的是NSObject,如果需要在根类对象添加,也就是给NSObject添加自定义实例方法,这就需要分类了。分类可以给系统添加方法。

@interface YZPerson : NSObject

  • (void)run;
  • (void)run;
    @end

import "YZPerson.h"

@implementation YZPerson
//+ (void)run
//{
// NSLog(@"类方法-run");
//}

  • (void)run
    {
    NSLog(@"实例方法-run");
    }
    @end

NSObject+test.h文件
@interface NSObject (test)

  • (void)run;
    @end

NSObject+test.m文件

import "NSObject+test.h"

@implementation NSObject (test)

  • (void)run
    {
    NSLog(@"NSObject-实例方法-run");
    }
    @end
  • (void)viewDidLoad {
    [super viewDidLoad];
    [YZPerson run];
    }
    打印结果:
    NSObject-实例方法-run
    消息传递


    image.png

消息传递
void objc_msgSend(void /* id self, SEL op, ... */ )
里面是两个默认参数,self和SEL。
[self class]经过编译器转换为objc_msgSend(self, @selector(class));

void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
里面是两个默认参数,super和SEL。
[super class]经过编译器转换为objc_msgSendSuper(super, @selector(class));

而,其中struct objc_super的数据结构是:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;

if !defined(__cplusplus) && !OBJC2

/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;

else

__unsafe_unretained _Nonnull Class super_class;

endif

/* super_class is the first class to search */

};

里面有句话:super_class is the first class to search
也就是,搜索方法,是从super_class开始的

objc_msgSendSuper({self, super_class}, @selector(class));
receiver是当前对象,也就是self,才是真正的消息接收者

也就是,不论是[self class]还是[super class],消息接收者都是当前对象self。
因此,上面的图片中,打印结果是:
Phone
Phone

可以看出,在调用前,会先进行缓存操作。

缓存操作有两种可能:
1.缓存到receiver的cache_t的bucket_t中
2.缓存到父类的cache_t的bucket_t中

首先,肯定是参数cls的
那么,参数cls是谁?
注释倒是有一句:
从上面代码可以看出,父类中缓存中有或者方法列表中有,则将方法缓存到原类中的cache_t的bucket_t中。

正确流程图:


image.png

1.当前缓存中查找
2.当前类中查找
3.父类缓存中查找
4.父类类中查找

1.当前缓存中查找

根据给定的值key,找到bucket_t里面的IMP


image.png

是一个hash查找

2.当前类中查找

对于已经排序好的列表,采用二分查找算法查找对应的执行函数
对于没有排序好的列表,采用一般遍历查找方法对应的执行函数

3.父类缓存中查找

4.父类中查找

image.png

消息转发


image.png

动态添加方法

动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

Method_Swizzing方法交换

image.png

void method_exchangeImplementations(Method m1, Method m2)

更多学习请参考:
iOS-探究Runtime

+(BOOL)resolveInstanceMethod:(SEL)sel;//为对象方法进行决议
+(BOOL)resolveClassMethod:(SEL)sel;//为类方法进行决议
-(id)forwardingTargetForSelector:(SEL)aSelector;//方法转发目标
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
-(void)forwardInvocation:(NSInvocation *)anInvocation;
那么最后消息未能处理的时候,还会调用到

  • (void)doesNotRecognizeSelector:(SEL)aSelector这个方法,我们也可以在这个方法中做处理,避免掉crash,但是只建议在线上环境的时候做处理,实际开发过程中还要把异常抛出来
image.png

动态添加方法

动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

Method_Swizzing方法交换

image.png

void method_exchangeImplementations(Method m1, Method m2)

更多学习请参考:
iOS-探究Runtime
动态方法解析
@dynamic
这句话写上,编译器将不再生成属性值的setter和getter方法,也不会生成成员变量,而在运行时我们用到的时候再给它动态添加get方法和set方法
然后,我们可以在运行时+ (BOOL)resolveInstanceMethod:(SEL)sel方中,动态的实现setter和getter方法

在MJ课程中,动态方法解析指的是runtime中的第二个流程+ (BOOL)resolveInstanceMethod:(SEL)sel

在于海本节课程中,动态方法解析是一个具体例子@dynamic,动态实现setter和getter方法

都是指的+ (BOOL)resolveInstanceMethod:(SEL)sel的方法调用

动态运行时语言将函数决议推迟到运行时(指在运行时再给对象添加函数)
编译时语言在编译期进行函数决议
问:[obj foo]与objc_msgSend()函数之间有什么关系?
[obj foo]编译后,就转换为了objc_msgSend()类型

问:runtime如何通过selector找到对应的IMP的?
其实就是objc_msgSend()的消息传递流程

问:能否向编译后的类中增加实例变量?
实例变量就是ivar
(成员变量 = 实例变量 + 基本数据类型的变量)

不能

可以向动态添加的类中增加实例变量
只需在注册之前加就可以

你可能感兴趣的:(课程笔记:第五章 Runtime相关面试问题)