美文网首页
iOS runtime

iOS runtime

作者: samstring | 来源:发表于2020-05-24 00:55 被阅读0次

    文章目录

    • OC中类和对象的本质
    • 实例对象,类,元类的关系
    • 类的属性
    • 类的方法
    • 消息发送机制
    • Runtime api的使用
    • Runtime 的应用

    Runtime是什么?

    Runtime是一个运行时库,它提供对Objective-C语言的动态属性的支持。Runtime是一种程序在运行时候的机制,其中最主要的是消息机制。在objective-c中,消息是在程序运行的时候才绑定到方法实现的。

    OC中类和对象的本质

    在程序编译运行的时候,我们用OC编译的代码其实会经历以下流程
    OC代码 -> C++,C语言代码 -> 汇编代码->二进制代码

    OC的实现其实是通过C++和C语言去实现的(苹果官方的源码https://opensource.apple.com/tarballs/objc4/,一般包体积最大的都是最新的)

    在iOS中,几乎所有的类都继承于NSObject类,所以我们先探究一下这个NSObject这个类,NSObject 这个类里面只有Class isa这个变量

    @interface NSObject <NSObject> {
        Class isa  OBJC_ISA_AVAILABILITY;
    }
    

    那Class isa是什么呢?先来看看Class的定义

    typedef struct objc_class *Class;
    /// Represents an instance of a class.
    struct objc_object {
       Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    struct objc_class : objc_object {
       // Class ISA;
       Class superclass;//父类
       cache_t cache;//缓存列表
       uintptr_t data_NEVER_USE;  // class_rw_t * plus custom rr/alloc flags
       //
       class_rw_t *data() { 
           return (class_rw_t *)(data_NEVER_USE & ~CLASS_FAST_FLAG_MASK); 
       }
     
     方法列表...
    };
    

    Class其实就是一个继承于objc_object结构体objc_class。objc_object是一个包含了指向objc_class 指针isa指针的结构体。所以说,其实我们在OC中写的类,其实就是一个结构体。我们创建对象的时候,其实就是在创建结构体实例

    对于objc_object包含一个objc_class类型的isa指针,而objc_class又是继承于
    objc_object这个结构体,这是不是有点绕?
    其实这个很好理解,就是类其实也是一个对象,所以objc_class会继承objc_object这个结构体。


    实例对象,类,元类的关系

    看一张经典的图


    图3-1 实例对象,类和元类.png

    这里有三个角色,实例对象,类,还有元类。对于这三个角色,有以下关系

    • 实例对象是类的实例,类是元类的实例

    • 类是描述实例对象的,包括对象的方法(类声明中的 - 方法),属性,变量等信息。元类是描述类对象的,包括类对象的方法(类声明中的 - 方法)等信息。所以类中的声明的方法,无论是 - 还是 + 方法,都是对象方法,区别在于是说这个对象是实例对象还是类对象。

    • 元类其实也是类,与类的实现都是objc_class结构体

    • 实例对象中的isa指针指向于对应的类,而类对象中的isa指针指向元类

    • objc_class中变量superclass是一个指针,指向父类的objc_class,也就是说指向父类的结构体。类的superclass指向父类的objc_class,根类(NSObject)的superclass是空。

    • 元类的superclass指向父类的元类的。而元类的根类的superclass指向的是类的根类。为什么元类的根类的superclass指向的是类的根类呢?因为元类本来就是类,所以元类的根类是继承于根类NSObject。

    • 关于isa指针
      上面提及到,isa是一个指针指向于类或元类的指针。在arm64架构之前,isa指针是直接指向类或是元类的地址,但是在arm64架构后,对isa指针做了优化,isa指针采用了union的存储方式,用来存储类或元类地址,nonpointer,ha s_assoc,has_cxx_dtor,shiftcls,magic,weakly_referenced,deallocating,extra_rc,has_sidetable_rc等信息(isa指针占8个字节,通过将上述字段的信息存通过位运算存放到isa指针所占的8个字节不同的位中)。


    类的结构

    上面已经给出了objc_class的结构体实现,其中有一个class_rw_t 类型的data,实现如下

    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;//实例对象占据的内存大小
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
        
        const char * name;
        const method_list_t * baseMethods;//类原有的方法
        const protocol_list_t * baseProtocols;//类原有的协议
        const ivar_list_t * ivars;//类的变量
    
        const uint8_t * weakIvarLayout;
        const property_list_t *baseProperties;//类原有的属性
    };
    
    struct class_rw_t {
        uint32_t flags;
        uint32_t version;
    
        const class_ro_t *ro;//类原有的信息,即未融合category的方法和属性的类
    
        union {
    //采用共用体的方式去存储方法列表
            method_list_t **method_lists;  // RW_METHOD_ARRAY == 1 //包含该类所有category的方法列表
            method_list_t *method_list;    // RW_METHOD_ARRAY == 0 //类原本的方法列表
        };
        struct chained_property_list *properties;
        const protocol_list_t ** protocols;//属性列表
    
        Class firstSubclass;
        Class nextSiblingClass;
    };
    

    类中的变量,属性,方法布局如下


    类的结构

    类的结构体中有包含了父类的指针,方法的缓存列表, class_rw_t结构的类信息。

    类的信息中有一个方法的缓存列表,这个缓存列表是一个hash表。在我们调用过的方法中,会把方法的地址缓存到缓存列表中。调用方法的时候,我们会先查看方法方法列表中是否有对应的方法,没有的情况下再去类信息中查找。散列表是一种较为高效的查找方法,比遍历方法列表高效不小,所以整体上加入方法缓存可以提高调用方法的效率。

    类中还包含了一个class_rw_t 类型的data,data中包含了一个class_ro_t 类型只读变量ro,这个ro包含了类的变量,属性,协议,方法等的信息。class_ro_t 既然已经包含了类的这些信息,为什么类的结构体中还要多家一层class_rw_t 类型的da ta层呢?
    原因很简单,因为ro只包含了类在编译后的信息,但是我们在开发过程中会在category给类添加属性和方法等,所以需要给类添加额外的信息,而class_rw_t这一层就是承担着这种功能。

    了解了类的结构后,我们可以通过runtime中的API去获取到类的变量,属性,方法列表等信息,甚至可以通过runtime去动态创建类。关于runtime的使用可以在文章底部看到

    类的方法

    • 方法解析
    • load和initialize
    • class方法
    • super关键词

    方法解析

    在我们编写OC代码的时候,对象和类方法的调用本质是消息发送,调用objc_msgSend方法,向对象发送一个调用方法的消息。

    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wincompatible-library-redeclaration"
    OBJC_EXPORT void
    objc_msgSend(void /* id self, SEL op, ... */ )
        OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    
    OBJC_EXPORT void
    objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
        OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
    #pragma clang diagnostic pop
    #else
    

    从上面的源码可知objc_msgSend存在隐藏的默认两个参数。第一个是self,也就是方法的调用者,第二个是sel,也就是需要调用的方法的名称。省略号...这是我们写的OC方法中传入的参数。OC代码中方法调用在运行过程中最终会转换成objc_msgSend的形式。
    虽然绝大部分OC代码都是通过消息发送机制去完成调用的,但不是所有的OC代码都是通过objc_msgSend去调用,如load方法,是在类加载被系统直接调用的。

    load和initialize的区别

    在日常开发中,会经常在类做一些初始化工作,这时候需要用到类的load和initialize方法。load方法是在把类加载到内存中时候会被调用,而initialize是类第一次接收到消息的时候被调用。

    加载类的时候是按build phase(如下图所示)中的从上往下的顺序去加载。
    对于load方法,当加载类到内存的时候,如果类存在父类的情况下,不管父类的编译顺序先后,会先加载父类的load方法,类中的load这个方法只会由系统调用一次。如果category存在load方法,在调用完类的load方法后,就会按编译顺序调用分类中的load方法。
    对于工程中所有类而言,会按照编译顺序去调用load方法。
    但如果类存在父类,或是category实现了load方法的时候,调用顺序如下
    父类的load(如果存在父类) -> 类的load方法 -> 类的load方法

    而且因为在加载类的时候调用load方法,是通过直接调用类或是category的lo ad方法,所以不存在覆盖问题。

    对于initialize方法,在第一次给类发送消息的时候(如 [类名 allock] init]),会被调用。initialize和load方法一样,如果存在父类的会优先调用父类的initialize方法,如果子类没有实现initialize方法,则会调用父类的initialize方法。所以如果一个类有多个子类,但是子类里面没有实现initialize方法,父类的initialize方法可能会调用多次。

    initialize方法是通过消息发送机制去调用的,如果category实现了initialize方法会存在方法覆盖的问题。为什么覆盖,这里留在另外一篇文章去讨论。

    QQ20200523-135757@2x.png

    class方法

    class 方法作用是获取类的类型,默认实现是在NSObject这个类里面,所以我们即便不实现class方法,也能调用这个方法,但是既然class方法的实现在NSObject中,那怎能保证class方法能准确的返回我们的类呢?看一下下面的源码

    + (Class)class {
        return self;
    }
    
    - (Class)class {
        return object_getClass(self);
    }
    
    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    + (Class)superclass {
        return self->superclass;
    }
    
    - (Class)superclass {
        return [self class]->superclass;
    }
    
    

    对于类对象,返回的是self,这个self不是指NSObject这个类,指得是objc_msgSend(void /* id self, SEL op, ... */ )中传入的self,也就是调用者,所以这就能保证即便是写在NSObject中的类也能返回正确的值。对于实例对象,返回的是实例对象的isa指针。从上图的3-1可知道,isa指针会指向对应的类对象。

    super方法

    super 这个方法是告诉应该从父类的方法开始寻找对应的方法。在转换成最终的调用时,调用的不是objc_msgSend这个函数,而是调用objc_msgSendSuper这个函数。调用这个方法会传入两个隐藏参数,分别是objc_super 和 方法名称,objc_msgSendSuper 会根据objc_super的值去查找对应的父类。objc_super是一个结构体,内容如下

    struct objc_super {
            id  receiver;
            Class   class;
      };
    

    这里包含了两个变量,分别是调用者和调用者class类型。

    由上可知,OC代码的方法调用基本都是由objc_msgSend去实现的,调用一个对象的方法就是给这个对象发送消息。


    消息发送机制

    上面提及到方法的调用本质上是消息发送机制,消息发送机制的执行流程如下


    QQ20200523-154512@2x.png

    如果经历了上面的步骤没找到方法的话,就会转入消息动态解析流程


    QQ20200523-154830@2x.png

    如果动态解析流程不处理消息的话,就会进入消息转发流程


    QQ20200523-154957@2x.png

    测试消息发送流程的代码如下

    #import "ViewController.h"
    #import <objc/runtime.h>
    #import "ForWardHander.h"
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //测试实例方法的消息发送流程
        [self performSelector:@selector(testSendMessage)];
        //测试类方法的消息发送流程
        [ViewController performSelector:@selector(testSendMessage)];
        
    }
    
    
    
    void testResolveClassMethod(id self, SEL _cmd){
        NSLog(@"动态添加方法");
    }
    
    
    //实例对象的消息发送
    
    +(BOOL)resolveInstanceMethod:(SEL)sel{
        //1 动态添加方法
        if (sel == @selector(testSendMessage)) {
            class_addMethod([self class], sel, (IMP)testResolveClassMethod, "v16@0:8:");
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
    
    -(id)forwardingTargetForSelector:(SEL)aSelector{
        //2 消息转发-转发方法给其他对象
        if(aSelector == @selector(testSendMessage)){
            return [[ForWardHander alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    
    
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        //获取方法签名
        if(aSelector == @selector(testSendMessage)){
            NSMethodSignature *signature = [NSMethodSignature methodSignatureForSelector:@selector(forwardingTargetForSelector:)];
            return  signature;
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation{
        //3 消息转发- 转发方法,在这里可以指定方法调用者和用什么方法代替原本调用的方法。forwardingTargetForSelector只能制定代替处理的对象
        anInvocation.target = self;
        anInvocation.selector = @selector(forwariTestMethodSignature);
        [anInvocation invoke];
    }
    
    -(void)forwariTestMethodSignature{
        NSLog(@"测试实例对象的methodSignatureForSelector");
    }
    
    
    //类对象的消息发发送
    +(BOOL)resolveClassMethod:(SEL)sel{
        return NO;
    }
    
    +(id)forwardingTargetForSelector:(SEL)aSelector{
        if(aSelector == @selector(testSendMessage)){
            return [[ForWardHander alloc] init];
        }
        return [super forwardingTargetForSelector:aSelector];
    }
    
    +(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
        if(aSelector == @selector(testSendMessage)){
            NSMethodSignature *signature = [NSMethodSignature methodSignatureForSelector:@selector(forwardingTargetForSelector:)];
            return  signature;
        }
        return [super methodSignatureForSelector:aSelector];
    }
    
    + (void)forwardInvocation:(NSInvocation *)anInvocation{
        anInvocation.target = [self class];
        anInvocation.selector = @selector(forwariTestMethodSignature);
        [anInvocation invoke];
    }
    
    +(void)forwariTestMethodSignature{
        NSLog(@"测试类的methodSignatureForSelector");
    }
    @end
    

    ForWardHander.m的实现如下

    #import "ForWardHander.h"
    
    @implementation ForWardHander
    -(void)testSendMessage{
        NSLog(@"测试消息转发");
    }
    @end
    
    

    这里顺道提一下class_addMethod中最后一个参数types,types传入的是一个字符指针,里面的值构成是

    ‘返回类型+方法参数总共占的字节数+类型+参数1的字节数开始 +类型+ 参数2的字节数开始+ ...类型+ 参数n的字节数开始’

    对于方法,最终转换成objc_msgSend,默认会传入两个参数,objc_msgSend(void /* id self, SEL op, ... */ ),一个是方法调用者,一个是方法名。

    所以对于空方法,如test(),types传的值是'v16@0@8'。对于有一个参数的方法,types串的是‘v20@0@8i16’


    NSProxy

    OC的对象中有两个大的基类,一个是NSObect,一个是NSProxy。NSObject我们在开发中经常用到,但是NSProxy是什么呢?NSProxy是专门用来做消息转发的,相当于一个中介,把你的需求交给其他类去实现。关系如下

    QQ20200523-171531@2x.png

    NSProxy实现消息转发跟上面消息发送流程最后一步大致一样,需要实现下面的两个方法

    - (void)forwardInvocation:(NSInvocation *)invocation;
    - (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
    

    NSProxy和消息发送机制都可以用来做消息转发,但是NSProxy更高效,因为NSProxy不需要经历方法查找,动态方法解析等步骤。

    Runtime Api的使用

    关于类的API

    //动态创建一个类(参数:父类,类名,额外的内存空间)
    Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
    //注册一个类(要在类注册之前添加成员变量)
    void objc_registerClassPair(Class cls) 
    //销毁一个类
    void objc_disposeClassPair(Class cls)
    //获取isa指向的Class
    Class object_getClass(id obj)
    //设置isa指向的Class
    Class object_setClass(id obj, Class cls)
    //判断一个OC对象是否为Class
    BOOL object_isClass(id obj)
    //判断一个Class是否为元类
    BOOL class_isMetaClass(Class cls)
    //获取父类
    Class class_getSuperclass(Class cls)
    

    关于变量的API

    //获取一个实例变量信息
    Ivar class_getInstanceVariable(Class cls, const char *name)
    //拷贝实例变量列表(最后需要调用free释放)
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
    //设置成员变量的值
    void object_setIvar(id obj, Ivar ivar, id value)
    //获取成员变量的值
    id object_getIvar(id obj, Ivar ivar)
    //动态添加成员变量(已经注册的类是不能动态添加成员变量的)
    BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
    //获取成员变量的名称
    const char *ivar_getName(Ivar v)
    //获取成员变量的编码类型
    const char *ivar_getTypeEncoding(Ivar v)
    

    关于属性的API

    //获取一个属性
    objc_property_t class_getProperty(Class cls, const char *name)
    //拷贝属性列表(最后需要调用free释放)
    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
    //动态添加属性
    BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)
    //动态替换属性
    void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                          unsigned int attributeCount)
    //获取属性的一些信息
    const char *property_getName(objc_property_t property)
    const char *property_getAttributes(objc_property_t property)
    

    关于方法的API

    //获得实例方法
    Method class_getInstanceMethod(Class cls, SEL name)
    //获得类方法
    Method class_getClassMethod(Class cls, SEL name)
    //获取方法实现
    IMP class_getMethodImplementation(Class cls, SEL name) 
    //设置方法实现
    IMP method_setImplementation(Method m, IMP imp)
    //替换方法实现
    void method_exchangeImplementations(Method m1, Method m2) 
    //拷贝方法列表(最后需要调用free释放)
    Method *class_copyMethodList(Class cls, unsigned int *outCount)
    //动态添加方法
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    //动态替换方法
    IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
    //获取方法名
    SEL method_getName(Method m)
    //获取方法实现
    IMP method_getImplementation(Method m)
    //获取方法的编码
    const char *method_getTypeEncoding(Method m)
    //获取参数数量
    unsigned int method_getNumberOfArguments(Method m)
    //获取返回类型
    char *method_copyReturnType(Method m)
    //拷贝
    char *method_copyArgumentType(Method m, unsigned int index)
    //获取选择器名称
    const char *sel_getName(SEL sel)
    //注册一个选择器
    SEL sel_registerName(const char *str)
    //设置block作为方法实现
    IMP imp_implementationWithBlock(id block)
    //获取block
    id imp_getBlock(IMP anImp)
    //移除block
    BOOL imp_removeBlock(IMP anImp)
    
    

    Runtime的应用

    • Method Swizzling
    • category中属性关联对象
    • 对象和模型的相互转换

    相关文章

      网友评论

          本文标题:iOS runtime

          本文链接:https://www.haomeiwen.com/subject/gjgmohtx.html