iOS面试知识整理 - Runtime

118 阅读5分钟

Runtime

数据结构

objc_object

id == objc_object

  1. isa_ta
  2. isa操作相关
  3. 弱引用相关
  4. 关联属性相关
  5. 内存管理相关

objc_class

Class = objc_class objc_class继承objc_object这一结构体,所以说class也是一个对象

  1. Class superClass指针
  2. cache_t cache;方法缓存
  3. class_data_bit_t bits;变量、属性、方法等

isa指针

共用体isa_t的结构,一般是64位,分为两种

  1. 指针型isa,isa的值代表的是class的地址
  2. 非指针型isa,isa的值的部分代表是class的地址,33或者44,主要是够用,别的可以放别的。

isa指向

  1. 关于对象,isa指向类对象:instance -> Class
  2. 对于类对象,isa指向元类对象:Class -> MetaClass

cache_t

  1. 用于快速查找方法执行函数
  2. 是可增量扩展的哈希表结构
  3. 是局部性原理的最佳应用

是一个数组结构,里面装的是类似bucket_t的类型

bucket_t -- 缓存方法结构体

  • key -> selector
  • IMP:具体函数实现

class_data_bits_t

class_data_bits_t实际上是对class_rw_t的封装

  • class_rw_t代表是类的相关读写信息,以及class_ro_t的封装
  • class_ro_t代表是类的相关只读信息

method_t

struct method_t {
    SEL _Nonnull method_name; //选择器名称
    char * _Nullable method_types;//返回和参数
    IMP _Nonnull method_imp;//函数实体
}    

class_rw_t 结构图

class_rw_t.jpeg

class_ro_t 结构图

class_ro_t.jpeg

整体结构图

runtime整体结构图.jpeg

类对象与元类对象

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

isa和superClass指向

关键在于根元类对象的superClass指向根类对象

isa指向.jpeg

消息传递

一道题

#import "Mobile.h"
@interface Phone : Mobile
@end
//=========================
#import "Mobile.h"

@implementation Phone
- (instancetype)init
{
    self = [super init];
    if (self) {
        NSLog(@"self.class---->%@",NSStringFromClass([self class]));
        NSLog(@"super.class---->%@",NSStringFromClass([super class]));
    }
    return self;
}
@end
//打印结果是什么?为什么都是 子类的
// 不管是 self还是super,消息发送的接受者都是当前对象,所以打印都是self

2018-12-29 17:26:16.767067+0800 runtimeDemo[65329:11620549] self.class---->Phone
2018-12-29 17:26:16.767263+0800 runtimeDemo[65329:11620549] super.class---->Phone

objc_msgSend、objc_msgSendSuper

//self 调用,
objc_msgSend(void /* id self, SEL op, ... */ )
[self class] == objc_msgSend(self, @selector(class))
//super 调用
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
[super class] == objc_msgSendSuper(self, @selector(class))

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;//指向self
    ///....
};

消息传递.jpeg

方法缓存

第一步 缓存查找

例: 给定值是SEL,目标是是对应bucket_t的IMP cache_key_t -> func(key)->bucket_t func(key) = key&mask 通过哈希函数,提高查找性能

第二步 当前类查找

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

第三步 父类逐级查找

父类逐级查找.jpeg

消息转发

消息转发流程.jpeg

//对于只声明,没有实现的方法的消息转发
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
        NSLog(@"Current method: %@",NSStringFromSelector(_cmd));
        return NO;
    }
    else{
        return [super resolveInstanceMethod:sel];
    }
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"Current method: %@",NSStringFromSelector(_cmd));
    return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if (aSelector == @selector(test)) {
        NSLog(@"Current method: %@",NSStringFromSelector(_cmd));
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    } else {
        return [super methodSignatureForSelector:aSelector];
    }
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"Current method: %@",NSStringFromSelector(_cmd));
}
2018-12-29 11:36:56.217868+0800 runtimeDemo[63226:10823976] Current method: resolveInstanceMethod:
2018-12-29 11:36:56.218062+0800 runtimeDemo[63226:10823976] Current method: forwardingTargetForSelector:
2018-12-29 11:36:56.218204+0800 runtimeDemo[63226:10823976] Current method: methodSignatureForSelector:
2018-12-29 11:36:56.218358+0800 runtimeDemo[63226:10823976] Current method: forwardInvocation:

Method-Swizzling

主要是 selector指向的IMP被交换了

+ (void)load{
    Method test = class_getInstanceMethod(self, @selector(test));
    Method otherTest = class_getInstanceMethod(self, @selector(otherTest));
    method_exchangeImplementations(test, otherTest);
}
- (void)test{
    NSLog(@"test method: %@",NSStringFromSelector(_cmd));
}
- (void)otherTest{
    [self otherTest];//这里其实调用的是[self test],所以不会造成循环调用
    NSLog(@"otherTest method: %@",NSStringFromSelector(_cmd));
}
2018-12-29 18:07:38.670501+0800 runtimeDemo[65733:11765283] test method: otherTest
2018-12-29 18:07:38.670673+0800 runtimeDemo[65733:11765283] otherTest method: test

动态添加方法

主要方法是class_addMethod

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel == @selector(test)) {
        NSLog(@"Current method: %@",NSStringFromSelector(_cmd));
        class_addMethod(self, @selector(test), testTmp, "v@:");
        return YES;
    }
    else{
        return [super resolveInstanceMethod:sel];
    }
}
2018-12-29 18:09:41.268645+0800 runtimeDemo[65770:11767293] Current method: resolveInstanceMethod:
2018-12-29 18:09:41.268802+0800 runtimeDemo[65770:11767293] method invoke

动态方法解析

@dynamic

  • 动态运行时语言将函数决议推迟到运行时
  • 编译时语言在编译期进行函数决议

面试题

  1. [obj foo]和objc_msgSend()函数之间有什么关系?
    • objc_msgSend()是[obj foo]的具体实现。 在runtime中,objc_msgSend()是一个c函数,[obj foo]会被翻译成这样的形式objc_msgSend(obj, foo)。
    • 1 去obj的对应的类中找方法;2 先找缓存,找不到再去找方法列表;3 再找父类,如此向上传递;4 最后再找不到就要转发。
  2. runtime如何通过selector找到对应的IMP地址?
    • 类对象中有类方法和实例方法的列表,列表中记录着方法的名词、参数和实现,而selector本质就是方法名称,runtime通过这个方法名称就可以在列表中找到该方法对应的实现。
  3. 能否向编译后的类中增加实例变量?
    • 不能向编译后得到的类中增加实例变量。
    • 能向运行时创建的类中添加实例变量。
    • 因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小已经确定,同时 runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量。
    • 运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。