美文网首页
iOS开发-8.Runtime

iOS开发-8.Runtime

作者: iOS_ZZ | 来源:发表于2022-02-20 18:35 被阅读0次
    • 1.Objective-C中的Runtime
    a) Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
        1) C、C++都是 编写代码-->编译连接-->运行
        
        2) 而OC则可以在运行的时候动态的去修改,例如动态的去调用自身类或者其他类的方法,或者增加、交换方法的实现
    
    b) Objective-C的动态性是由Runtime API来支撑的
    
    c) Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
    
    • 2.实现用一个字节来存储3个BOLL变量
    a) &(按位与)如果大家都是1结果才是1,其他都是0,用来取出或设置特定的位
    
    b) |(按位或)只要有一个1结果就是1,其他都是,用来取出或设置特定的位
    
    c) ~(位运算取反)
    
    #import <Foundation/Foundation.h>
    @interface MJPerson : NSObject
    //@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
    
    #import "MJPerson.h"
    // &可以用来取出特定的位
    // 0000 0111
    //&0000 0100
    //------
    // 0000 0100
    
    // 掩码,一般用来按位与(&)运算的
    // 宏的替换就是字符串替换所以要注意运算先后次序
    //#define MJTallMask 1
    //#define MJRichMask 2
    //#define MJHandsomeMask 4
    
    //#define MJTallMask 0b00000001
    //#define MJRichMask 0b00000010
    //#define MJHandsomeMask 0b00000100
    
    #define MJTallMask (1<<0)
    #define MJRichMask (1<<1)
    #define MJHandsomeMask (1<<2)
    
    @interface MJPerson()
    {
        char _tallRichHansome;
    }
    @end
    
    @implementation MJPerson
    
    // 0010 1010
    //&1111 1101
    //----------
    // 0010 1000
    
    - (instancetype)init
    {
        if (self = [super init]) {
            _tallRichHansome = 0b00000100;
        }
        return self;
    }
    
    - (void)setTall:(BOOL)tall
    {
        if (tall) {
            _tallRichHansome |= MJTallMask;
        } else {
            _tallRichHansome &= ~MJTallMask;
        }
    }
    
    - (BOOL)isTall
    {
        // 只占1位 返回的也是1位 然而boll类型是一个字节所以会强制变成8位(全部用你当前的结果覆盖其余的位)那么解决办法 !!(2个感叹号) 或者 定义结构体的时候占2位
        // 结果用BOLL强制转 或者 !!(2个感叹号)
        return !!(_tallRichHansome & MJTallMask);
    }
    
    - (void)setRich:(BOOL)rich
    {
        if (rich) {
            _tallRichHansome |= MJRichMask;
        } else {
            _tallRichHansome &= ~MJRichMask;
        }
    }
    
    - (BOOL)isRich
    {
        return !!(_tallRichHansome & MJRichMask);
    }
    
    - (void)setHandsome:(BOOL)handsome
    {
        if (handsome) {
            _tallRichHansome |= MJHandsomeMask;
        } else {
            _tallRichHansome &= ~MJHandsomeMask;
        }
    }
    
    - (BOOL)isHandsome
    {
        return !!(_tallRichHansome & MJHandsomeMask);
    }
    @end
    
    d) 用结构体的方式实现
    e) 结构体是支持位域 字节排布 从低到高 从右到左
    f) 位运算的效率比直接取值效率高
    
    #import "MJPerson.h"
    //#define MJTallMask (1<<0)
    //#define MJRichMask (1<<1)
    //#define MJHandsomeMask (1<<2)
    @interface MJPerson()
    {
        // 位域
        // 结构体是支持位域 字节排布 从低到高 从右到左
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        } _tallRichHandsome;
    }
    @end
    
    @implementation MJPerson
    
    - (void)setTall:(BOOL)tall
    {
        _tallRichHandsome.tall = tall;
    }
    
    - (BOOL)isTall
    {
        return !!_tallRichHandsome.tall;
    }
    
    - (void)setRich:(BOOL)rich
    {
        _tallRichHandsome.rich = rich;
    }
    
    - (BOOL)isRich
    {
        return !!_tallRichHandsome.rich;
    }
    
    - (void)setHandsome:(BOOL)handsome
    {
        _tallRichHandsome.handsome = handsome;
    }
    
    - (BOOL)isHandsome
    {
        return !!_tallRichHandsome.handsome;
    }
    @end
    
    g) 用共用体的方式实现
        1) 共用体:大家共用一块内存
        2) 结构体:结构体内的成员都是单独存在的,占不同的内存
    
    #import "MJPerson.h"
    #define MJTallMask (1<<0)
    #define MJRichMask (1<<1)
    #define MJHandsomeMask (1<<2)
    #define MJThinMask (1<<3)
    @interface MJPerson()
    {
        union {
            int bits;
            
            struct {
                char tall : 4;
                char rich : 4;
                char handsome : 4;
                char thin : 4;
            };
        } _tallRichHandsome;
    }
    @end
    
    @implementation MJPerson
    
    - (void)setTall:(BOOL)tall
    {
        if (tall) {
            _tallRichHandsome.bits |= MJTallMask;
        } else {
            _tallRichHandsome.bits &= ~MJTallMask;
        }
    }
    
    - (BOOL)isTall
    {
        return !!(_tallRichHandsome.bits & MJTallMask);
    }
    
    - (void)setRich:(BOOL)rich
    {
        if (rich) {
            _tallRichHandsome.bits |= MJRichMask;
        } else {
            _tallRichHandsome.bits &= ~MJRichMask;
        }
    }
    
    - (BOOL)isRich
    {
        return !!(_tallRichHandsome.bits & MJRichMask);
    }
    
    - (void)setHandsome:(BOOL)handsome
    {
        if (handsome) {
            _tallRichHandsome.bits |= MJHandsomeMask;
        } else {
            _tallRichHandsome.bits &= ~MJHandsomeMask;
        }
    }
    
    - (BOOL)isHandsome
    {
        return !!(_tallRichHandsome.bits & MJHandsomeMask);
    }
    
    - (void)setThin:(BOOL)thin
    {
        if (thin) {
            _tallRichHandsome.bits |= MJThinMask;
        } else {
            _tallRichHandsome.bits &= ~MJThinMask;
        }
    }
    
    - (BOOL)isThin
    {
        return !!(_tallRichHandsome.bits & MJThinMask);
    }
    @end
    
    h) 例如kvo或者autoresizingMask是怎么知道传入的不同按位或的值呢?
        1) 最终传入的值与自身进行按位与如果成立则包含了自身;
    
    • 3.isa详解
    a) 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
    
    b) 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
    
    c) 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
    
    d) 64位之前isa就是Class类型,是isa_t类型
    
    image
    e) isa详解-位域
    
    image
    • 4.Class的结构
    a) 类对象/元类对象的地址值最后三位都是0,因为isa底层原理
    
    b) 一开始是只有class_ro_t,然后创建class_rw_t并且拷贝class_ro_t的内容,并且将bits指向class_rw_t
    
    image
    c) class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容 
    
    image
    d) class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
    
    在这里插入图片描述
    e) method_t是对方法/函数的封装
    
    image
    f) Type Encoding
    
    在这里插入图片描述
    g) cache(方法缓存)
        1) Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度
        2) 方法缓存cache调用过的方法直接扔到缓存里面,下次再调用直接从cache调用
        3) 缓存源码实现查找
            3.1) objc-cache.mm
            3.2) bucket_t * cache_t::find(cache_key_t k, id receiver)
    
    在这里插入图片描述
    h) 散列表
        1) 存的时候通过一个位运算去存,前面没有的就会设置为NULL(牺牲内存空间来换取执行效率/空间换时间)
        2) 散列表扩容的时候是在原来容量基础上X2,并且清空原来缓存的数据,重新开始缓存
        3) 存或者取的时候有可能是一样的 那么做法是-1或者+1取,重新生成index
    
    在这里插入图片描述
    • 5.objc_msgSend执行流程
    a) OC中的方法调用,其实都是转换为objc_msgSend函数的调用
    
    b) objc_msgSend的执行流程可以分为3大阶段
        1) 消息发送
        2) 动态方法解析
        3) 消息转发
        
    c) 源码跟读
    
    在这里插入图片描述
    d) 消息发送
        1) 查找方式
            1.1) 二分查找(排好序的 从中间开始分然后查找)
            1.2) 线性查找(普通的for循环遍历查找)
    
    在这里插入图片描述
    e) 动态方法解析
        1) 开发者可以实现以下方法,来动态添加方法实现
            1.1) +resolveInstanceMethod:// 对象方法
            1.2)
    +resolveClassMethod:// 类方法
        2) 动态解析过后,会重新走“消息发送”的流程
            2.1) “从receiverClass的cache中查找方法”这一步开始执行
    
    在这里插入图片描述
    f) 消息转发
        1) 这个步骤你就开始找不到源码了
        2) 你自己的类没有能力处理这个方法
        3) 元类对象:是一种特殊的类对象,类对象的类对象
        4) 类方法也是有消息转发机制的
        5) 开发者可以在forwardInvocation:方法中自定义任何逻辑
    
    在这里插入图片描述
        6) 生成NSMethodSignature方式
    
    在这里插入图片描述
    • 6.super的本质
    a) @dynamic (告诉编译器不要自动生成setter&getter方法的实现、不要自动生成成员变量)
    
    b) @synthesize (自动生成setter&getter方法的实现和自动生成成员变量)
    
    c) super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
        1) struct objc_super2
    
    在这里插入图片描述
            1.1) receiver是消息接收者
            1.2) current_class是receiver的Class对象-->superclass找父类对象
        2) SEL
    
    • 7.LLVM的中间代码(IR)
    a) Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)
        1) OC --> 中间代码(.ll文件) --> 汇编、机器代码
        
    b) 可以使用以下命令行指令生成中间代码
        1) clang -emit-llvm -S main.m
        
    c) 语法简介
        1) @ - 全局变量
        2) % - 局部变量
        3) alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
        4) i32 - 32位4字节的整数
        5) align - 对齐
        6) load - 读出
        7) store 写入
        8) icmp - 两个整数值比较,返回布尔值
        9) br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto
        10) label - 代码标签
        11) call - 调用函数
    
    d) 官方文档:  
        1) https://llvm.org/docs/LangRef.html
    
    • 8.Runtime的应用
    a) 查看私有成员变量
        1) 设置UITextField占位文字的颜色
    
    在这里插入图片描述
    b) 字典转模型
        1) 利用Runtime遍历所有的属性或者成员变量
        2) 利用KVC设值
    
    在这里插入图片描述
    c) 替换方法实现
        1) class_replaceMethod
        2) method_exchangeImplementations
    
    在这里插入图片描述 在这里插入图片描述
    • 9.Runtime API
    a) 类
        1) 动态创建一个类(参数:父类,类名,额外的内存空间)
            1.1) Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
    
        2) 注册一个类(要在类注册之前添加成员变量)
            2.1) void objc_registerClassPair(Class cls) 
    
        3) 销毁一个类
            3.1) void objc_disposeClassPair(Class cls)
    
        4) 获取isa指向的Class
            4.1) Class object_getClass(id obj)
    
        5) 设置isa指向的Class
            5.1) Class object_setClass(id obj, Class cls)
    
        6) 判断一个OC对象是否为Class
            6.1) BOOL object_isClass(id obj)
    
        7) 判断一个Class是否为元类
            7.1) BOOL class_isMetaClass(Class cls)
    
        8) 获取父类
            8.1) Class class_getSuperclass(Class cls)
    
    b) 成员变量
        1) 获取一个实例变量信息
            1.1) Ivar class_getInstanceVariable(Class cls, const char *name)
    
        2) 拷贝实例变量列表(最后需要调用free释放)
            2.1) Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
    
        3) 设置和获取成员变量的值
            3.1) void object_setIvar(id obj, Ivar ivar, id value)
            3.2) id object_getIvar(id obj, Ivar ivar)
    
        4) 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
            4.1) BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
    
        5) 获取成员变量的相关信息
            5.1) const char *ivar_getName(Ivar v)
            5.2) const char *ivar_getTypeEncoding(Ivar v)
    
    c) 属性
        1) 获取一个属性
            1.1) objc_property_t class_getProperty(Class cls, const char *name)
    
        2) 拷贝属性列表(最后需要调用free释放)
            2.1) objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
    
        3) 动态添加属性
            3.1) BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                      unsigned int attributeCount)
    
        4) 动态替换属性
            4.1) void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
                          unsigned int attributeCount)
    
        5) 获取属性的一些信息
            5.1) const char *property_getName(objc_property_t property)
            5.2) const char *property_getAttributes(objc_property_t property)
    
    d) 方法
        1) 获得一个实例方法、类方法
            1.1) Method class_getInstanceMethod(Class cls, SEL name)
            1.2) Method class_getClassMethod(Class cls, SEL name)
    
        2) 方法实现相关操作
            2.1) IMP class_getMethodImplementation(Class cls, SEL name) 
            2.2) IMP method_setImplementation(Method m, IMP imp)
            2.3) void method_exchangeImplementations(Method m1, Method m2) 
    
        3) 拷贝方法列表(最后需要调用free释放)
            3.1) Method *class_copyMethodList(Class cls, unsigned int *outCount)
    
        4) 动态添加方法
            4.1) BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
    
        5) 动态替换方法
            5.1) IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
    
        6) 获取方法的相关信息(带有copy的需要调用free去释放)
            6.1) SEL method_getName(Method m)
            6.2) IMP method_getImplementation(Method m)
            6.3) const char *method_getTypeEncoding(Method m)
            6.4) unsigned int method_getNumberOfArguments(Method m)
            6.5) char *method_copyReturnType(Method m)
            6.6) char *method_copyArgumentType(Method m, unsigned int index)
    
        7) 选择器相关
            7.1) const char *sel_getName(SEL sel)
            7.2) SEL sel_registerName(const char *str)
    
        8) 用block作为方法实现
            8.1) IMP imp_implementationWithBlock(id block)
            8.2) id imp_getBlock(IMP anImp)
            8.3) BOOL imp_removeBlock(IMP anImp)
    
    • 10.其它知识点总结
    a) objc_msgSendSuper 和 objc_msgSendSuper2区别
    
    b) 数组可以当成指针来用
    
    c) 分类的方法尽量+属于自己的前缀区分,万一覆盖了呢
    
    d) 方法替换:替换系统的方法 例如拦截整个项目中所有按钮的点击事件
    
    e) hook:钩子函数 拦截系统的方法 塞入自己的实现
    
    f) 类簇(cu):我们看到的类型不一定是他的真实类型 NSString、NSArray、NSDictionary,真实类型是其他类型
    
    g) 转成底层代码的方式
        1) 转成cpp文件(作为参考)
        2) 程序运行起来查看汇编
        3) 转成汇编
    
    在这里插入图片描述
    • 11.面试题1
      在这里插入图片描述
    a) isKindOfClass // 传入的对象的类对象是否==判断的类对象或者他的子类
    b) isMemberOfClass // 传入的对象的类对象是否==判断的类对象
    c) super class的返回类型是谁取决于方法调用者是谁即:消息接收者 消息接收者仍然是子类对象,只不过从父类的方法开始寻找方法
    c) 底层实现原理
    
    在这里插入图片描述
    • 12.面试题2
      在这里插入图片描述
    a) 函数调用堆栈的问题
        1) 局部变量分配在栈空间
        2) 栈空间分配,从高地址到低地址
    b) 寄存器(存储在cpu里面,相当于一个元件,参数都是存储在这里面)比内存的效率更高
    
    在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

    相关文章

      网友评论

          本文标题:iOS开发-8.Runtime

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