OC底层学习-Runtime

1. 概述

  • Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
  • Objective-C的动态性是由Runtime API来支撑的
  • Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写

2.Runtime之 isa指针

  • 要想学习Runtime,首先需要了解底层的一些常用的数据结构,比如isa指针
  • 在arm64架构之前,isa就是 一个普通的指针,储存着Class,Meta-Class对象的内存地址
  • 从arm64架构开始,对isa指针进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息

2.1 示例:使用位来储存值

@interface GYPerson : NSObject
//我们不能声明一个属性,如果声明属性,系统会默认生成set和get方法
//@property (assign, nonatomic, getter=isTall) BOOL tall;
//@property (assign, nonatomic, getter=isRich) BOOL rich;
//@property (assign, nonatomic, getter=isHansome) BOOL handsome;

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;

@end

//
//  GYPerson.m
//  Runtime-isa
//
//  Created by admin on 2020/9/18.
//  Copyright © 2020 admin. All rights reserved.
//

#import "GYPerson.h"

#define GYTallMask (1<<0)
#define GYRichMask (1<<1)
#define GYHansomeMask (1<<2)



@implementation GYPerson
{
    //声明一个char类型的一个字节来储存 3个Bool类型的变量值
    char _tallRichHansome;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        //初始化赋值 , 假设tall、rich、handsome 在_tallRichHansome变量中的储存位置是从右往左开始储存
        _tallRichHansome = 0b00000100;
    }
    return self;
}

/**
    位运算:
    & : 与运算  在二进制下 都为1 得出的值是1, 如果有一个是0 那么值为0 (可以去除特定的位)
     // 0000 0111
     //&0000 0100
     //------
     // 0000 0100
 */
- (void)setRich:(BOOL)rich {
    //1.rich是储存从右往左数第一位,那么我们如果得到右数第一位的值,这里我们需要用位运算
    if (rich) {
        //如果是YES  改变我们需要使用|运算  使用|运算不改边原来的值
        _tallRichHansome |= GYRichMask;
    } else {
        _tallRichHansome &= ~GYRichMask;
    }
}

- (void)setTall:(BOOL)tall {
    if (tall) {
        //如果是YES  改变我们需要使用|运算  使用|运算不改边原来的值
        _tallRichHansome |= GYTallMask;
    } else {
        //如果原本是0 那我们需要去掩码的反码 在和原来的进行 &运算
        /**
         0000 0001 储存的值
         0000 0001 掩码
         1111 1110 掩码的反码
         */
        _tallRichHansome &= ~GYTallMask;
    }
}

- (void)setHandsome:(BOOL)handsome {
    if (handsome) {
        //如果是YES  改变我们需要使用|运算  使用|运算不改边原来的值
        _tallRichHansome |= GYHansomeMask;
    } else {
        //如果原本是0 那我们需要去掩码的反码 在和原来的进行 &运算
        /**
         0000 0001 储存的值
         0000 0001 掩码
         1111 1110 掩码的反码
         */
        _tallRichHansome &= ~GYHansomeMask;
    }
}

- (BOOL)isRich {
    //取值 使用& 运算  不管你&运算出来是多少,首先使用!把值变成0 在使用!变成1  Bool值 0和1
    return !!(_tallRichHansome & GYRichMask);
}

- (BOOL)isTall {
    return !!(_tallRichHansome & GYTallMask);
}

- (BOOL)isHandsome {
    return !!(_tallRichHansome & GYHansomeMask);
}
@end


//测试代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        GYPerson *person = [[GYPerson alloc] init];
        person.tall = NO;
        person.rich = YES;
        person.handsome = NO;
        
        NSLog(@"tall:%d rich:%d hansome:%d", person.isTall, person.isRich, person.isHandsome);
    }
    return 0;
}
  • 位运算:
    • &: 与
    • |: 或
    • !: 非
    • ~: 取反
    • ^: 异或

上述实现过程我们还可以实现,但是管理起来比较麻烦,我们还可以使用结构体和位域来实现上述功能:

/**
 使用位域的技术来存储值
 */
@implementation GYPerson {
    // 定义一个结构体类型的数据来储存数据
    struct {
        //这样表示该字段占据1位  , 储存顺序是从右往左开始 储存
        char tall : 1;
        char rich : 1;
        char handsome : 1;
    } _TallRichHandsome;
}

- (void)setTall:(BOOL)tall {
    _TallRichHandsome.tall = tall;
}

- (void)setRich:(BOOL)rich {
    _TallRichHandsome.rich = rich;
}

- (void)setHandsonme:(BOOL)handsome {
    _TallRichHandsome.handsome = handsome;
}

- (BOOL)isTall {
	//如果不加!! ,且值为1的话,那么直接取值为-1 ,这是因为
     //因为只占一位  而返回的BOOL类型的值是一个字节  占8位, 那么系统会自动以这个一位的值来填充其他位(如果该位是1,就是拿1来填充其他位->0b1111 1111 -> -1,(255和-1 储存都是0xff,如果是无符号就是255,有符号就是-1)), 这里有两种解决方法,一种是加上!! ,第二种是用两位来存储,这样有一位是0,那么久会以0来填充其他位
    return !!_TallRichHandsome.tall;
}

- (BOOL)isRich {
    return !!_TallRichHandsome.rich;
}
- (BOOL)isHandsome {
    return !!_TallRichHandsome.handsome;
}
@end
  • union共用体: 表示大家共用一块内存,接下来我们可以使用union来是实现上述存储功能, 使用结构体来表示说明
#define GYTallMask (1<<0)
#define GYRichMask (1<<1)
#define GYHandsomeMask (1<<2)
@implementation GYPerson {
    //使用共用体来声明, 实际上和上面 声明 char _TallRichHandsome,的作用是一样的,都是表示一个字节
    union {
          char bits;
        //该结构体仅表示代码说明的作用(增加代码的可读性),实际上不起任何作用的, 可以删除
        //加上和这个结构体,是不影响以前的存储的,因为这个结构只占一个字节,union只占一个字节,所以这个结构体是没有影响union的内存的, 而且由始至终我们一直是在访问bits,没有去访问过结构体
        // 如果结构体的内存超过一个字节, 那么bits的内存也需要相应的放生变化
          struct {
              char tall : 1;
              char rich : 1;
              char handsome : 1;
          };
    } _TallRichHandsome;
}
- (void)setTall:(BOOL)tall
{
    if (tall) {
        _TallRichHandsome.bits |= GYTallMask;
    } else {
        _TallRichHandsome.bits &= ~GYTallMask;
    }
}

- (BOOL)isTall
{
    return !!(_TallRichHandsome.bits & GYTallMask);
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _TallRichHandsome.bits |= GYRichMask;
    } else {
        _TallRichHandsome.bits &= ~GYRichMask;
    }
}

- (BOOL)isRich
{
    return !!(_TallRichHandsome.bits & GYRichMask);
}

- (void)setHandsonme:(BOOL)handsonme
{
    if (handsonme) {
        _TallRichHandsome.bits |= GYHandsomeMask;
    } else {
        _TallRichHandsome.bits &= ~GYHandsomeMask;
    }
}

- (BOOL)isHandsome
{
    return !!(_TallRichHandsome.bits & GYHandsomeMask);
}
@end

  • 存储值的位置是有xxxMask,也就是掩码来决定的

  • isa 的部分源码:


/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
struct objc_object {
private:
    isa_t isa;
 }
union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
}
  • 上述isa中的一些解释:在这里插入图片描述
  • Class、meta-class对象的地址值后三位一定都是0,arm64之后需要&一个mask(掩码)取出储存的地址值,因为在arm64之后,isa中不仅仅是存储地址值,还会储存其他的信息
  • 在arm64之前isa指正是一个普通的指针,存储这class、meta-class的地址值,但是在arm64之后,对isa进行了优化,采用共用体(union)的形式,使用一个64位的内存空间存储这许多信息,其中33位才储存着Class、meta-class对象的地址值,而其他空间还储存着许多其他的信息

3. Runtime-Class

3.1 class

  • 回顾之前学的class的结构,如下图:
    在这里插入图片描述

  • bits & mask(掩码) ,找到 结构他class_tw_t对象,这里面包含着class的一些信息

    • ro: 只读属性,储存着class中定义的基本信息,属性,方法等
    • 程序运行开始,首先或将ro里面的内容赋值到 methods、properties、protocols中 这个三个属性是二维数组,是可以可写的 methods中储存是method_list_t (一维数组),这里储存的是method_t (方法对象)
    • class_rw_t中的methods、properties、protocols 结构如下图
      在这里插入图片描述

3.2 method

  • method_t的结构如下:
    在这里插入图片描述

    • name: SEL类型(方法选择器),代表方法/函数名,一般叫做选择器,底层结构跟char* 类似
      • 可以通过@selector()sel_registerName()(runtimeAPI)获得
      • 可以通过sel_getName()NSStringFormSelector()转成字符串
      • 不同类中相同名字的方法,所对应的方法选择器是相同的
    • IMP:代表函数的具体实现(储存着函数的地址)
      • typedef id _Nullable (* IMP)(id _Nullable, SEL _Nullable, ...);
    • types: 包含了函数的返回值信息,参数编码的字符串
      • 在这里插入图片描述
  • type是如何通过字符来表示,返回值类型,参数的这些信息的,IOS中提供了一个@encode的指令,是通过这个编码来表示的:
    在这里插入图片描述

  • oc中调用方法的实质,就是转换成objc_msgSend(id self, _cmd sel)方法发送,默认传递了两个参数, id类型的self 和 _cmd 类型 @selector()

3.3 cache_t cache 方法缓存对象

  • 函数的底层结构就是method_t对象

  • 方法调用

    1. 首先通过isa指针寻找class对象,在方法缓存列表中寻找方法,如果没有则去方法列表中寻找
    2. 如果不存在,通过superclass指针去父类中寻找,执行1操作
    3. 一旦找到方法调用之后, 会把该方法缓存到自己cache (方法缓存列表对象)中,下次该对象在此调用该方法时,是通过isa指针找到class对象,然后直接去class对象中的cache(方法缓存列表对象)中调用,就不用在一层一层的查找方法
  • cache缓存对象的结构
    在这里插入图片描述

    • 散列表(哈希表)数组的长度可能会大已缓存方法数量,每一个bucket_t 对象都是一个方法对象
    • 调用方法是通过@selector(xxx)去寻找方法的
    • 第一次调用方法时 使用@selector(xxx)对象 & mask == 缓存在数组中的索引(通过一套算法和位运算直接算出索引), 数组的其他位置没有缓存就设置为空, 然后第二次调用,直接通过索引 在数组中找到 缓存的bucket_t(方法信息),然后直接调用, 并不是像普通的一样循环遍历,去比较key值(牺牲内存空间,换取执行效率
    • mask是变化的,mask的长度等于数组减1,初始化时,数组有一个默认长度,也就是mask有一个默认数值, 当缓存方法数组容量等于数组长度时(表示快不够缓存),会扩充容量,容量为原来旧数组容量的2倍 ,mask的值也会发生变化,扩容时首先会清空数组,重新开始缓存方法,重新计算索引值,缓存当前调用的方法
    • 扩容源码:
void cache_t::expand()
{
    cacheUpdateLock.assertLocked();
    
    uint32_t oldCapacity = capacity();
    // 旧的数组容量 * 2
    uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;

    if ((uint32_t)(mask_t)newCapacity != newCapacity) {
        // mask overflow - can't grow further
        // fixme this wastes one bit of mask
        newCapacity = oldCapacity;
    }

    reallocate(oldCapacity, newCapacity);
}
  • 源码寻找key ->objc-cache(源码类名)
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
    assert(k != 0);

    bucket_t *b = buckets();
    mask_t m = mask(); //得到mask
    mask_t begin = cache_hash(k, m);
    mask_t i = begin;
    do {
        if (b[i].key() == 0  ||  b[i].key() == k) {
            return &b[i];
        }
    } while ((i = cache_next(i, m)) != begin);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}

//模拟器下
#if __arm__  ||  __x86_64__  ||  __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return (i+1) & mask;
}

//真机下
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}
  • 由上述源码可知,如果计算出来的所以,去除的key值,不相等, 那就去前一个找,知道找到索引为0的可以还不是,那就从索引为mask(最后索引)处开始找,知道找到

  • 哈希表 的核心原理 是首先通过一个函数(通过某个算法),计算出索引,然后直接寻找 ,速度比遍历数组快,牺牲空间,换取执行速度(空间换时间)

4 Runtime- objc_msgSend

4.1 OC方法的调用

  • OC方法的调用:消息机制,给方法调用者发送消息
 GYPerson * person = [[GYPerson alloc] init];
 [person personTest]; 
  • 上述方法调用person对象的personTest方法,底层转换成((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));
  • 消息接受者(receive): person
  • 消息名称: personTest
  • 如果objc_msgSend()找不到合适的方法调用,最后会报错unrecognied selector sent to instance

4.2 objc_msgSend的执行流程

  • OC中的方法调用,其实都是转换成objc_msgSend函数的调用
  • objc_msgSend的执行流程可以分为3大阶段

4.2.1 第一阶段:消息发送

  • 消息发送流程:
    1. 判断消息接受者(receive)是否为空,如果是nil,则直接退出(这也是给一个nil对象发送消息不报错的原因),否则通过isa指针找到receiveClass,然后再cache中查找方法,找到方法调用结束
    2. 第一步没有查找到方法,则从receiveClassclass_rw_t中查找方法,如果方法列表已经排序,则使用二分查找,没有排序,则使用遍历查找, 找到方法,调用方法结束查找,并把该方法缓存到receiveClasscache
    3. 第二步没有查找方法,从superClasscache中查找方法,如果找到,调用方法结束查找,并把该方法缓存到receiveClasscache
    4. 第三步没有找到方法,则从superClassclass_rw_t中查找方法,然后执行第二步的查找方式
    5. 没有找到方法,则判断 上层是否还superClass,如果有,则从第三步开始执行,如果没有找到方法,则进入动态方法解析阶段
    6. 执行流程图:在这里插入图片描述

4.2.2 第二阶段:动态方法解析

4.2.2.1 动态方法解析流程
  • 如果消息发送没有找到方法,就会进入动态方法解析阶段
    1. 是否进行动态解析,如果有,直接进入消息转发阶段
    2. 没有,则调用+ (BOOL)resolveInstanceMethod:(SEL)sel或则+ (BOOL)resolveClassMethod:(SEL)sel方法
      • 开发者可以实现上述两个方法,来实现动态方法的添加,前者是针对实力方法实现,后者针对类方法
      • 方法的返回值,没有任何作用,系统要求添加动态方法,返回YES,源码中根据返回值只是做了打印,并没有任何实质性的操作
      • 该方法中添加的动态方法是添加到方法列表中(methods),并在添加完方法之后,在重新走objc_msgSend()流程,也就是从第一个阶段,消息发送阶段流程开始,寻找方法,调用方法
    3. 动态方法解析,不管有没有做操作,都会标记为YES ,下一次就不会再走动态解析方法了,动态方法解析完成,会在次调用到消息发送阶段
    4. 动态消息解析流程图:在这里插入图片描述
4.2.2.2 动态添加方法
  1. 动态添加一个OC方法
- (void)otherMethod{
    NSLog(@"%s",__func__);
}
/// 当类对象调用方法没有实现时,会进入动态解析方法
/// @param sel <#sel description#>
+ (BOOL)resolveInstanceMethod:(SEL)sel {

    if (sel == @selector(personTest)) {
        //获取我们需要添加的方法
        Method method =  class_getInstanceMethod(self, @selector(otherMethod));
        //需要给类对象动态添加方法
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));

        return YES;
    }

    return [super resolveInstanceMethod:sel];
}
- (void)otherMethod{
    NSLog(@"%s",__func__);
}
// 我们知道方法列表中的方法对象是method_t
struct method_t {
    SEL sel;
    char*types;
    IMP imp;
};

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(personTest)) {
        //typedef struct objc_method *Method;
        //method_t == objc_method 这两个实质上的等价的  只不过method_t这个结构体是底层结构没有暴露出来 我们可以自己定义一个结构体method_t
        // Method 可以理解为等价于 struct method_t *
        struct method_t *methd =  (struct method_t *)class_getInstanceMethod(self, @selector(otherMethod));

        //动态增加方法
        class_addMethod(self, sel, methd->imp, methd->types);
    }

    return [super resolveInstanceMethod:sel];
}
  1. 动态添加一个C语言方法:
void c_otherMethod(id self, SEL _cmd) {
    NSLog(@"c_otherMethod=====%@ -----%@",self,NSStringFromSelector(_cmd));
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(personTest)) {
        //C语言中 函数的名称就是函数的地址
        class_addMethod(self, sel, (IMP)c_otherMethod, "v16@0:8");
    }
    
    return [super resolveInstanceMethod:sel];
}
  1. 如果是调用类方法(+ 方法)和上述流程类似

4.2.3 第三阶段:消息转发

  • 消息转发: 将消息转发给别人,自己做不了事情交给别人做
  • 消息发送阶段没有找到方法,动态方法解析也没做,那么会进入第三阶段,消息转发阶段。
  • 消息转发阶段:
    1. 进入消息转发阶段会调用forwardingTargetForSelector:(SEL)aSelector方法,根据调用方法不同,实现实例方法和类方法, 在此方法中我们可以返回一个对象,让该对象来执行对应的方法,就实现了简单的消息转发流程
    2. 如果没有实现forwardingTargetForSelector:(SEL)aSelector方法, 那么系统会调用methodSignatureForSelector:(SEL)aSelector:,如果此方法的返回值不为nil,则会调用forwardInvocation:(NSInvocation *)anInvocation 方法, 前者是返回一个方法签名(我们需要调用方法的方法签名),后者把放回的方法签名封装在一个NSInvocation对象中,让我们来实现调用, 如果返回值为nil则会调用doesNotRecognizeSelector方法打印错误信息,程序会报错unrecognized selector sent to instance
    3. 能走到forwardInvocation:(NSInvocation *)anInvocation方法时,我们可以在这个方法里面做任何事情,也可以空实现,实质上调用方法,只要进入消息转发阶段,该方法的实现内容,取决于forwardInvocation:(NSInvocation *)anInvocation的实现内容
    4. 如果是类方法的调用,也是有消息转发的,和调用实例方法类似,只是调用的方法把 -变成+,然后实现类似,但是在工具中+方法是没有提示的。
/// 进入消息转发阶段 可以指定另外一个类来执行该方法
/// @param aSelector <#aSelector description#>
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(personTest)) {
        //返回这个对象之后,实质是通过objc_msgSend([[GYForwardTest alloc] init], aSelector)  给这个对象发送一个aSelector消息
        return [[GYForwardTest alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
/// 返回一个方法签名, 方法签名中包含返回值类型,参数类型
/// @param aSelector <#aSelector description#>
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(personTest)) {
        //返回一个方法签名,  根据一个编码字符串返回一个方法签名
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

/// 把methodSignatureForSelector:方法返回的方法签名封装成一个NSInvocation对象,
/// @param anInvocation <#anInvocation description#>
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    //指定消息需要转发的对象, 也就是实际执行方法的对象->方法调用者
    anInvocation.target = [[GYForwardTest alloc] init];
    //anInvocation.selector:  封装了 调用方法对象, 也可以修改
    //[anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#>]  获取方法参数
    
    //最后需要调用方法
    [anInvocation invoke];
}
  • 消息转发执行流程:在这里插入图片描述

4.2.4 消息发送的核心源码

objc_msgSend()就是查找方法的过程,下面是查找方法的源码

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.

    runtimeLock.read();

    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // If sel == initialize, _class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

    
 retry:    
    runtimeLock.assertReading();

    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.
	// 没有找到方法,进入第二个阶段->方法动态解析
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

//动态方法解析阶段 调用的方法
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

4.3 @dynamic关键字

  • @dynamic关键字标记的属性,是告诉编译器不用自动生成gettersetter的实现,等到运行时在动态添加方法实现
  • @synthesize :关联属性 给属性生成实例变量、setter方法、getter方法 (久远以前使用, 现在不用 )
  • @properyy以前只会自动声明settergetter方法声明,现在方法的实现也会自动实现,实例变量也会自动生成
@interface GYPerson : NSObject


/** <#des#> */
@property (nonatomic, assign) int age;
@end


#import "GYPerson.h"

#import <objc/runtime.h>
@implementation GYPerson

@dynamic age;

void setAge(id self, SEL _cmd, int age) {
    NSLog(@"age is === %d",age);
}

int age() {
    return 120;
}


+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(setAge:)) {
        
        class_addMethod(self, sel, (IMP)setAge, "v20@0:8i16");
        
        return YES;
    } else if (sel == @selector(age)) {
        class_addMethod(self, sel, (IMP)age, "i16@0:8");
    }
    
    return [super resolveInstanceMethod:sel];
}
@end

//测试代码
 GYPerson *person = [[GYPerson alloc] init];
        person.age = 10;
        
        NSLog(@"age====%d",person.age);

//打印结果:
/**
2020-10-24 10:19:12.132827+0800 Runtime-objc_msgSend2[1710:34347] age is === 10
2020-10-24 10:19:12.133272+0800 Runtime-objc_msgSend2[1710:34347] age====120
Program ended with exit code: 0
*/

4.4 super关键字

  • [super message]:
    • 消息接受者(receive)任然是子类对象
    • 从父类开始查找方法实现
@interface GYPerson : NSObject

@end
@implementation GYPerson

@end

@interface GYStudent : GYPerson

@end
#import "GYStudent.h"

@implementation GYStudent

- (instancetype)init {
    if (self = [super init]) {
        
        NSLog(@"%@----",[self class]);
        NSLog(@"%@----",[super superclass]);
        
        
        NSLog(@"%@----",[super class]);
        NSLog(@"%@----",[super superclass]);
    }
    
    return self;
}

//测试代码;
GYStudent *student = [[GYStudent alloc] init];

//打印结果:
/**
2020-10-24 10:47:51.913618+0800 Runtime-super[2031:42837] GYStudent----
2020-10-24 10:47:51.914187+0800 Runtime-super[2031:42837] GYPerson----

2020-10-24 10:47:51.914240+0800 Runtime-super[2031:42837] GYStudent----
2020-10-24 10:47:51.914274+0800 Runtime-super[2031:42837] GYPerson----
Program ended with exit code: 0
*/
  • 首先我们查看GYStudent.m的OC转换C++代码
struct __rw_objc_super { 
	struct objc_object *object; 
	struct objc_object *superClass; 
	__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};
// @implementation GYStudent


static instancetype _I_GYStudent_init(GYStudent * self, SEL _cmd) {
    if (self = ((GYStudent *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("GYStudent"))}, sel_registerName("init"))) {
    }

    return self;
}
// @end


/** 
 * Sends a message with a simple return value to the superclass of an instance of a class.
 *  第一个参数是一个结构体,发送一个消息,里面包含的这个实例对象就是消息接受者(self),消息从superclass开始查找消息
 * @param super A pointer to an \c objc_super data structure. Pass values identifying the
 *  context the message was sent to, including the instance of the class that is to receive the
 *  message and the superclass at which to start searching for the method implementation.
 * @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
 * @param ...
 *   A variable argument list containing the arguments to the method.
 * 
 * @return The return value of the method identified by \e op.
 * 
 * @see objc_msgSend
 */
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
  • 由以上代码可以知,super关键字的调用转换成C++代码objc_msgSendSuper函数的调用(结构体中第二个成员直接传递的是父类对象),而最终底层代码是调用objc_msgSendSuper2函数(结构体中第二个成员传递当前class对象,内部用过superClass拿到父类对象,),该函数接受两个参数:
    • struct objc_super2,该结构体中有两个成员,第一个 receive(消息接受者),第二个当前类的Class对象(作用:告诉编译器从哪里开始寻找方法(当前也就是从父类开始寻找方法))
    • SEL,消息名称(方法名称)
  • 该结构体的真正底层代码
struct objc_super2 {
	id receive;
	Class current_class;
};
  • receive: 消息接受者
  • current_classreceiveClass对象

4.5 OC代码如何转成中间代码

  • 我现在产看底层几乎是把OC代码转换成C++ 代码,虽然很接近底层,但是有些细节在真正的底层代码还是有细微的区别,如果想要查看真正的底层,可以查看直接查看汇编代码,或则转换成中间代码来看,下面是转换成中间代码的步骤:
    在这里插入图片描述

5. Runtime-API

5.1 Runtime- API-类

// 动态创建一个类(参数:父类Class对象、类名、额外的储存空间(一般填0))
Class _Nullable objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes) 

//注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class _Nonnull cls) 

//销毁一个类
void objc_disposeClassPair(Class _Nonnull cls) 

//获取isa指向的Class
Class _Nullable object_getClass(id _Nullable obj) 

//设置isa指向的Class
Class _Nullable object_setClass(id _Nullable obj, Class _Nonnull cls) 

//判断一个OC对象是否为Class
BOOL object_isClass(id _Nullable obj)

//判断一个Class是否为元类
BOOL class_isMetaClass(Class _Nullable cls) 

//获取父类Class对象
Class _Nullable class_getSuperclass(Class _Nullable cls) 

5.1.1 Runtime动态创建类

void test(id self, SEL _cmd) {
    NSLog(@"%@----%@",self,NSStringFromSelector(_cmd));
}

//动态创建一个类 参数: 父类class对象、类名、额外内存空间
        Class newClass = objc_allocateClassPair([NSObject class], "GYStudent", 0);
        //添加实例变量 参数: class对象、实例变量名、实力变量所占空间大小,对齐空间大小、实力变量类型
        class_addIvar(newClass, "_age", 4, 1, @encode(int));
        class_addIvar(newClass, "_weight", 4, 1, @encode(int));
        
        //给这个类添加一个方法
        class_addMethod(newClass, @selector(test), (IMP)test, "v@:");
        //注册类
        objc_registerClassPair(newClass);
        //增加成员变量不可以在注册类之后在添加,因为实例变量是储存在ro结构中 是只读属性,不可以修改, ro中储存的是类中定义好的成员变量不可以修改的
        //增加方法是可以在注册类之后修改的,因为方法是添加到rw中的methods方法列表中,这是个可读可写的属性
        
        //创建并调用方法
        id student = [[newClass alloc] init];
        [student setValue:@10 forKey:@"_age"];
        [student setValue:@20 forKey:@"_weight"];
        [student test];
        
        NSLog(@"_age==%@   _weight+====%@",[student valueForKey:@"_age"],[student valueForKey:@"_weight"]);

//打印结果:
/*
2020-10-26 11:26:47.186273+0800 Runtime-API[3814:85734] <GYStudent: 0x100488340>----test
2020-10-26 11:26:47.201017+0800 Runtime-API[3814:85734] _age==10   _weight+====20
*/


//动态设置对象的isa指向的class对象
 GYPerson *person = [[GYPerson alloc] init];
        //设置isa指向的Class对象
        object_setClass(person, newClass);
        [person test];

//打印结果:
/**
2020-10-26 11:55:15.901719+0800 Runtime-API[4013:96984] _age==10   _weight+====20
2020-10-26 11:55:15.902936+0800 Runtime-API[4013:96984] <GYStudent: 0x10071c120>----test
*/

5.1.2 获取Class对象

GYPerson *person = [[GYPerson alloc] init];
        Class class = [person class];
        Class class2 = [GYPerson class];
        Class class3 = object_getClass(person);
        Class class4 = object_getClass(class2);//如果传递的是calss对象,那返回的是meta-class(元类对象)
        Class class5 = [class2 class];
        
NSLog(@"class--通过实例获取class对象====%p",class);
        NSLog(@"class--通过类名获取class对象====%p",class2);
        NSLog(@"class--通过Runtime获取class对象====%p",class3);
        NSLog(@"class--通过Runtime获取meta-class对象====%p",class4);
        NSLog(@"class--通过class获取meta-class====%p",class5);//不准,建议通过runtime来获取meta-class对象
  • 打印结果:在这里插入图片描述

5.2 Runtim-API成员变量

//获取一个成员变量
Ivar _Nullable class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)

// 拷贝成员变量列表(最后需要调用free释放)
Ivar _Nonnull * _Nullable class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 

// 设置和获取成员变量的值
void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value) 
id _Nullable object_getIvar(id _Nullable obj, Ivar _Nonnull ivar) 

//动态添加成员变量(已经注册的类是不能动态添加成员变量的)参数: class对象、实例变量名、实力变量所占空间大小,对齐空间大小、实力变量类型
BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types) 

//获取成员变量相关信息
const char * _Nullable ivar_getName(Ivar _Nonnull v) //返回成员变量的名称
const char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v)//返回成员变量的类型信息
  • 测试代码:
//获取成员变量
        Ivar nameVar = class_getInstanceVariable([GYPerson class], "_name");
        NSLog(@"name===%s   type====%s",ivar_getName(nameVar),ivar_getTypeEncoding(nameVar));//返回都是const char * C语言字符串 用%s 来打印
        // 打印结果name===_name   type====@"NSString"
        
        GYPerson *person = [[GYPerson alloc] init];
        //设置成员变量的值
        object_setIvar(person, nameVar, @"test");
        
        Ivar ageIave = class_getInstanceVariable([GYPerson class], "_age");
        //给基本数据类型赋值一个NSNumber类型的数据,会发生一些问题,赋值之后,值并不准确, 我们需要直接设置值
        //object_setIvar(person, ageIave, @10);
        object_setIvar(person, ageIave, (__bridge id)(void *)10);//Runtime的底层函数不用包装,传什么值就直接传, void *则为“无类型指针”,void *可以指向任何类型, 数字是不能直接转换成对象类型的,首先转换成指针类型(为什么转指针类型可以转成功,因为指针存放的就是地址值,存值的, 所以可以认为10就是个地址值),然后再桥接转换成id类型
        //获取得成员变量的值
        id obj = object_getIvar(person, nameVar);
        
        NSLog(@"name=====%@  obj======%@  age=====%d",person.name,obj,person.age);
        //name=====test  obj======test age=====987880663 // (转换类型之后)age=====10
        
        //拷贝成员变量列表 参数: class对象、 int类型的指针
        unsigned int count; //表示成员变量的数量
        //调用完该方法后,会给count复制
        Ivar *copyIvar = class_copyIvarList([GYPerson class], &count);//返回的是一个数组
        for (int i = 0; i< count; i++) {
            Ivar tempVar = copyIvar[i];
            NSLog(@"name===%s   type====%s",ivar_getName(tempVar),ivar_getTypeEncoding(tempVar));
        }
        /**
         name===_age   type====i
         name===_name   type====@"NSString"
         */
        
        //完成操作时 记得释放
        free(copyIvar);

5.3 Runtime-API -属性

//获取一个属性
bjc_property_t _Nullable class_getProperty(Class _Nullable cls, const char * _Nonnull name)

//拷贝属性列表(最后徐亚调用free释放)
objc_property_t _Nonnull * _Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)

//动态添加属性
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,const objc_property_attribute_t * _Nullable attributes,unsigned int attributeCount)

//动态替换属性
void
class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,const objc_property_attribute_t * _Nullable attributes,unsigned int attributeCount)

//获取属性的一些信息
const char * _Nonnull property_getName(objc_property_t _Nonnull property)//获取属性名称
const char * _Nullable property_getAttributes(objc_property_t _Nonnull property) //获取属性中的一些属性

5.4 RunTime - API-方法

  • 获取一个实例方法、类方法
Method _Nullable (Class _Nullable cls, SEL _Nonnull name)
Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
//交换两个方法的实现
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) 
  • 方法实现及相关操作
//参数 class对象, SEL方法对象
IMP _Nullable class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 
// 设置方法的 指向地址
IMP _Nonnullmethod_setImplementation(Method _Nonnull m, IMP _Nonnull imp) 
  • 拷贝方法列表(最后需要用到free释放)
Method _Nonnull * _Nullable class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)
  • 动态添加、替换方法
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,const char * _Nullable types) 
//动态替换
IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,const char * _Nullable types) 
  • 获取方法相关信息(带有copy的需要调用free释放)
SEL _Nonnull method_getName(Method _Nonnull m)//根据名称获取SEL对象
IMP _Nonnull method_getImplementation(Method _Nonnull m) //根据一个method对象得到方法函数地址IMP对象
const char * _Nullable method_getTypeEncoding(Method _Nonnull m)//获取method的类型(参数,返回值)
unsigned int method_getNumberOfArguments(Method _Nonnull m) //获取method对象的参数个数
char * _Nonnull method_copyReturnType(Method _Nonnull m)//拷贝method的返回值
char * _Nullable method_copyArgumentType(Method _Nonnull m, unsigned int index)//拷贝method对象的参数类型


//根据选择器,返回选择器的名称
const char * _Nonnull sel_getName(SEL _Nonnull sel)

// 根据一个字符串得到一个选择器
SEL _Nonnull sel_registerName(const char * _Nonnull str)
  • 用block作为实现方法:
IMP _Nonnull imp_implementationWithBlock(id _Nonnull block)

id _Nullable imp_getBlock(IMP _Nonnull anImp)

BOOL imp_removeBlock(IMP _Nonnull anImp)
  • method_exchangeImplementations本质:方法交换的是把Class对象中的class_rw_t中的methods中的method_t(方法对象)中的IMP(指向函数的指针(函数地址))进行一个交换,一但调用这个方法,就回清空方法缓存列表,重新缓存;需要注意的是,Founction框架中有些类并不是看到的类名并不是真实的类型,例如NSMutableArray的Class对象是__NSArrayM

6. 面试题

6.1 OC的消息机制

  • OC中的方法调用都是转成了objc_msgSend函数调用,给receive(方法调用者)发送一条消息(selector方法名)
  • objc_msgSend底层有三大阶段,消息发送(当前类,父类查找)、动态方法解析、消息转发(消息发送的三大阶段请看上述第4章节)。

6.2 消息转发机制

  • 当消息发送没有找到方法,并且在动态解析方法阶段没有做任何处理,就会进入消息转发阶段
  • 具体消息转发流程,请参考本文4.2.3章节

6.3 isMemberOfClass和isKindOfClass

  • 如果是实例方法,那么传递的参数是class对象,底层比较的是class是否相等,如果是类(+)方法传递的参数应该是meta-class对象,底层比较的是元类对象是否相等,
  • isMemberOfClass: 是判断调用者是否是传递参数的类型
  • isKindOfClass: 是判断调用者是否是传递参数的类型,或则子类类型; 在类(+)方法情况下,有一点需要注意的是,如果传递的参数是[NSObject class],那么不管是谁调用此方法返回都是YES,因为我们知道NSObject对象的meta-class的isa指针是指向class对象的
  • 两个方法源码:
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

6.4 看代码回答问题(零散记载-自己还未完全明白)

在这里插入图片描述

  • 代码能执行成功,打印结果是 my name's <viewController:0xxxxxxx>(可以参考super关键字)

  • 方法为什么可以调用成功, 因为 在内存中的结构,和普通对象在内存中的结构是一样的,普通对象调用方法, 通过指针找到对象的那块内存,取出最前面的8个字节,因为最前面的8个字节就是isa指针,就能找到类对象,在去缓存和方法列表中寻找方法

  • 如下图在这里插入图片描述

  • 栈控件内存地址分配,是从高地质到低地址的顺序开始分配 而结构体成员越往后地址值越高 ,结构体的地址值就是第一个成员的地址值
    在这里插入图片描述

  • 寻找成员变量是找到对象的那快内存,然后跳过最前面那8个字节,然后开始往下寻找 所以打印结果会多变,可能是viewcontoller

  • 访问成员变量的本质,找到那个对象的那个内存,然后找到对象内存中的成员变量,成员变量就是结构体中的成员变量,每一个实例对象本质都是一个结构体,找实例对象的成员变量就是结构体中的成员,找结构体中的成员,无非就是跳过前面那些成员,然后找下一个成员,比如跳过isa指针

6.5 什么是Runtime?平时项目中有用过么?

  1. 什么是Runtime:
    1. OC是一门动态性比较强的编程语言,允许很多操作推迟到程勋运行时在进行
    2. OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
    3. 平时编写的OC代码,底层都是转换成了RuntimeAPI运行调用
  2. 具体应用:
    1. 利用关联对象(objc_xxxAssociatedObject),给分类添加属性
    2. 遍历是有成员变量(修改UITextFiled站位文字颜色、字典转模型、自动归档解档)
    3. 交换方法实现(交换系统方法)
    4. 利用消息转发机制解决方法找不到的异常问题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值