iOS底层原理——runtime

作者: xymspace | 来源:发表于2020-07-18 17:09 被阅读0次

    runtime

    runtime 是iOS的运行时,用于实现iOS加载和调用属性和方法。

    函数中load方法没有使用runtime机制,是底层直接调用的函数。load执行顺序是由编译时的文件顺序相同,先编译的先执行load,类优先于分类的顺序调用 +load 方法。

    initialize

    +initialize 方法是在类或类的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。

    也就是说 +initialize 方法是以懒加载的方式被调用的,如果一直没有给一个类或他的子类发送消息,那么这个类的 +initialize 方法是永远不会调用的。

    当我们向某个类发送消息时,runtime 会调用 IMP lookUpImpOrForward(...) 这个函数在类中查找相应方法的实现或进行消息转发,打开 objc-runtime-new.h 找到这个函数:

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        Class curClass;
        IMP imp = nil;
        ...
        if (initialize  &&  !cls->isInitialized()) {
            // 类没有初始化时,对类进行初始化
            _class_initialize (_class_getNonMetaClass(cls, inst));
            // 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
        }
    
        ...
    }
    

    从中可以看到当类没有初始化时,会调用 _class_initialize(Class cls) 对类进行初始化:

    void _class_initialize(Class cls)
    {
        assert(!cls->isMetaClass());
    
        Class supercls;
        BOOL reallyInitialize = NO;
    
        // Make sure super is done initializing BEFORE beginning to initialize cls.
        // See note about deadlock above.
        supercls = cls->superclass;
        // 递归调用,对父类进行_class_initialize调用,确保父类的initialize方法比子类先调用
        if (supercls  &&  !supercls->isInitialized()) {
            _class_initialize(supercls);
        }
        
        ......
        
        if (reallyInitialize) {
            // We successfully set the CLS_INITIALIZING bit. Initialize the class.
            
            // Record that we're initializing this class so we can message it.
            _setThisThreadIsInitializingClass(cls);
            
            // Send the +initialize message.
            // Note that +initialize is sent to the superclass (again) if 
            // this class doesn't implement +initialize. 2157218
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: calling +[%s initialize]",
                             cls->nameForLogging());
            }
            // 发送调用类方法initialize的消息
            ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    
            ......
    }
    

    在这里,先是对入参的父类进行递归调用,以确保父类优先于子类初始化。

    +initialize 方法在 runtime 中是以发送消息的方式调用的,所以子类会覆盖父类的实现,分类会覆盖类的实现,多个分类只会调用一个分类的 +initialize 方法。

    分类

    通过runtime动态将分类的属性和方法合并到类对象,元类对象中。

    • 扩展属性
    #import "ClassName + CategoryName.h"
    #import <objc/runtime.h>
    
    static void *strKey = &strKey;
    
    @implementation ClassName (CategoryName) 
    -(void)setStr:(NSString *)str  
    {  
        objc_setAssociatedObject(self, &strKey, str, OBJC_ASSOCIATION_COPY);  
    }  
    
    -(NSString *)str  
    {  
        return objc_getAssociatedObject(self, &strKey);  
    }
    

    Method Swizzling

    每个类都维护一个方法(Method)列表,Method则包含SEL和其对应IMP的信息,方法交换做的事情就是把SEL和IMP的对应关系断开,并和新的IMP生成对应关系。

    交换前:Asel->AImp Bsel->BImp
    交换后:Asel->BImp Bsel->AImp

    方法交换之后,“方法的实现” 变成了 “你的处理代码” + “方法的实现”

    //获取通过SEL获取一个方法
    class_getInstanceMethod
    
    //获取一个方法的实现
    method_getImplementation
    
    //获取一个OC实现的编码类型
    method_getTypeEncoding
    
    //給方法添加实现
    class_addMethod
    
    //用一个方法的实现替换另一个方法的实现
    class_replaceMethod
    
    //交换两个方法的实现
    method_exchangeImplementations
    
    + (void)swizzleMethods:(Class)class originalSelector:(SEL)origSel swizzledSelector:(SEL)swizSel {
        
        Method origMethod = class_getInstanceMethod(class, origSel);
        Method swizMethod = class_getInstanceMethod(class, swizSel);
        
        BOOL didAddMethod = class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
        if (didAddMethod) {
            class_replaceMethod(class, swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
        } else {
            method_exchangeImplementations(origMethod, swizMethod);
        }
    }
    

    调用过程,涉及到了isa指针

    isa

    • arm64之前,isa是普通指针

    • arm64 isa采用共用体数据结构,使用位域存储更多信息,将64位数据,分开来存储信息

    • isa共用体中的结构体没有实际意义,只是用来描述内存中每一部分的作用

    • isa只有33位用来存放类地址值(shiftcls在4-36位),需要&ISA_MASK才能像之前一样访问class、meta-class

    • isa共用体的好处:

      • 可以存放更多的信息
      • 更好的利用内存空间

    ps:MASK 掩码,一般用来进行按位(与&)运算。

    相关文章

      网友评论

        本文标题:iOS底层原理——runtime

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