美文网首页
Runtime运行时机制

Runtime运行时机制

作者: CoderKK | 来源:发表于2019-06-03 14:04 被阅读0次
Runtime简介

Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同,允许很多操作推迟到程序运行时再进行。
Objective-C的动态性是由Runtime API来支撑和实现的,Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写。
学习 Runtime 机制前需要先了解OC 对象本质,否则看不懂以下内容。
平时编写的OC代码,底层都是转换成了Runtime API进行调用。

class的结构
//struct objc_class 结构
struct objc_class{
  Class isa;
  Class superclass;
  Cache_t cache;//方法缓存
  class_data_bit_t bit;//用于获取类的具体信息
}
// bit & FAST_DATA_MASK 得到类的具体信息
struct class_rw_t {  // rw表示可读写
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_array_t methods; //method_t 二维数组
    property_array_t properties; //属性二维数组
    protocol_array_t protocols; //协议二维数组
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
}
//struct class_ro_t 的结构
struct class_ro_t { //ro表示只读
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name; //类名
    method_list_t * baseMethodList; //方法列表
    protocol_list_t * baseProtocols; //协议列表
    const ivar_list_t * ivars; //成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
}

class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

//method_t结构
struct method_t{
  SEL name;//函数名
  const char *type;//编码(包含返回值类型,参数类型)
  IMP imp;//函数指针(指向函数的指针)
}
  • SEL代表方法\函数名,一般叫做选择器,底层结构跟char *(字符串)类似
    不同类中相同名字的方法,所对应的方法选择器是相同的

  • type是包含了函数返回值、参数的编码字符串

方法缓存

Class内部结构中有个方法缓存(cache_t),用散列表(哈希表,空间换取时间增加查找速度)来缓存曾经调用过的方法,可以提高方法的查找速度

//cathe_t结构
struct cahe_t{
  struct bucket_t *buckets;//散列表
  mask_t _mask;//散列表的长度 - 1
  mask_t _occupied;//已经缓存的方法数量
}
//buket_t结构
struct_t buket_t{
  cahe_ket_t _key;//SEL作为key
  IMP _imp;//函数的内存地址
}
objc_msgSend执行流程

OC中的方法调用,其实都是转换为objc_msgSend(receiver,message)函数的调用。
objc_msgSend的执行流程可以分为3大阶段:消息发送;动态方法解析;消息转发。

  • objc_msgSend第一阶段:消息发送


    消息发送流程图
  • objc_msgSend第二阶段:动态方法解析


    动态方法解析流程图

开发者可以实现以下方法,来动态添加方法实现

+ (BOOL)resolveClassMethod:(SEL)sel
+ (BOOL)resolveInstanceMethod:(SEL)sel

动态解析对象方法

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

- (void)other
{
    NSLog(@"%s", __func__);
}

+ (BOOL)resolveClassMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 参数 类对象,sel, 方法实现地址,方法参数编码(包含返回值,参数)
        // 方式1:直接添加方法实现
        // class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
        // 方式2: 获取其他方法添加
        // 获取其他方法
        Method method = class_getInstanceMethod(self, @selector(other));

        // 动态添加test方法的实现
        class_addMethod(self, sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveClassMethod:sel];
}

动态解析类方法

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 获取其他方法
        struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));

        // 动态添加test方法的实现
        class_addMethod(self, sel, method->imp, method->types);

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

动态解析过后,会重新走“消息发送”的流程
“从receiverClass的cache中查找方法”这一步开始执行

  • objc_msgSend第三阶段:消息转发


    image.png

开发者可以实现以下方法实现消息转发

- (id)forwardingTargetForSelector:(SEL)aSelector
//或
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation

消息转发

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        // Cat 对象实现了 test 方法
        // objc_msgSend([[MJCat alloc] init], aSelector)
        return [[Cat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
//    anInvocation.target 方法调用者
//    anInvocation.selector 方法名
//    [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//    anInvocation.target = [[Cat alloc] init];
//    [anInvocation invoke];
// 上面两句等同于下面这句
    [anInvocation invokeWithTarget:[[Cat alloc] init]];
}
Category的加载处理过程

我们日常写的分类的底层结构如下

struct category_t {
    const char *name;//类名
    classref_t cls;
    struct method_list_t *instanceMethods;//对象方法列表
    struct method_list_t *classMethods;//类方法列表
    struct protocol_list_t *protocols; //协议列表
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }
    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
  • category的加载处理过程
    1.通过runtime加载某个类的category数据
    2.把所有的category的方法、属性、协议数据合并到一个大数组中
    后面参与编译的category数据,会在数组的前面
    3.将合并后的分类数据(方法、属性、协议),插到类原来数据的前面
Runtime应用

runtime 常用 API 链接

  • 查看对象的私有成员变量,然后用 KVC 赋值改变私有成员变量的值
// 打印查看某个类的所有成员变量
- (void)lookIvarsFromClass:(Class)cls{
    unsigned int count;
    Ivar *ivars = class_copyIvarList(cls, &count);
    for (int i = 0; i < count; i ++) {
        Ivar ivar = ivars[i];
        NSLog(@"%s",ivar_getName(ivar));
    }
    free(ivars);
}
//改变placeholderLabel字体颜色
- (void)changePlaceholderColor{
    [self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    [super touchesBegan:touches withEvent:event];
    // 查看UITextField的所有成员变量
    [self lookIvarsFromClass:[UITextField class]];
}
  • 动态添加属性
    为 Person 对象动态添加属性(间接实现属性效果)
// Person+kj.m 分类 文件
@implementation Person (KJ)

static char nameKey;

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name{
    return  objc_getAssociatedObject(self, &nameKey);
}

@end
  • 字典转模型(MJExtension 框架)利用 Runtime遍历所有的属性或成员变量,然后利用 KVC设值
+ (instancetype)objectWithJson:(NSDictionary *)json{
    id obj = [[self alloc] init];
    
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
    // 遍历 obj 成员变量
    for (int i = 0; i < count; i ++) {
        Ivar ivar = ivars[i];
        NSMutableString *ivarName = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        // 删掉前面下划线
        [ivarName deleteCharactersInRange:NSMakeRange(0,1)];
        // KVC 赋值
        [self setValue:json[ivarName] forKeyPath:ivarName];
    }
    
    free(ivars);
    return obj;
}
  • 利用消息转发解决找不到方法App闪退问题
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    // 本来能调用的方法
    if ([self respondsToSelector:aSelector]) {
        return [super methodSignatureForSelector:aSelector];
    }
    // 找不到方法
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

// 找不到的方法,都会来到这里
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
  • 替换方法实现(Method Swizzling) hook 系统方法,自定义处理自己的逻辑
#import "UIViewController+KJ.h"
#import <objc/runtime.h>

@implementation UIViewController (KJ)

// 类加载时调用,只会调用一次
+ (void)load{
    Method hookMethod = class_getInstanceMethod(self, NSSelectorFromString(@"hookViewDidLoad"));
    Method originalMethod = class_getInstanceMethod(self, @selector(viewDidLoad));
    method_exchangeImplementations(hookMethod, originalMethod);
}

- (void)hookViewDidLoad{
    // 调用原来的方法,咋看是循环调用,其实方法实现被交换了,这样只会调用原来的
    [self hookViewDidLoad];
    NSLog(@"加载了%@",[self class]);
    // 自定义业务,判断 class 统一埋点点处理等等
}

@end
  • ......
以上列举了 runtime 的几种运用场景,合理利用runtime机制可以实现强大的效果,主要还是要看对 runtime 机制的了解程度,api 的灵活使用,毕竟熟能生巧

相关文章

  • iOS - RunTime的简单使用以及说明

    Runtime(消息机制) 都知道runtime就是运行时,OC也是运行时机制的,runtime说简单也简单,说难...

  • Runtime

    1、什么是Runtime(运行时-机制)? Runtime简称运行时,OC就是运行时机制,也就是在程序运行时的一些...

  • [iOS开发]一篇文章带你深入理解runtime

    一. runtime简介 runtime简称运行时,是一套底层的 C 语言 API。OC就是运行时机制,运行时机制...

  • IOS开发谈谈对Runtime 和 Runloop的理解

    Runtime Runtime简称运行时,OC就是运行时机制,也就是在运行时候的一些机制,其中最重要的事消息机制。...

  • IOSRunTime_方法交换

    RunTime_运行时详解 运行时机制: 消息发送机制: RunTime 运行时:苹果提供了一个API,属于C语言...

  • Runtime

    一、Runtime简介   Runtime 简称 运行时机制,也就是在运行时候的一些机制,其中最主要的是 消息机制...

  • iOS开发中的runtime机制知识的简单整理

    一、runtime简介 RunTime简称运行时。OC就是运行时机制,也就是在运行时候的一些机制,其中最主要...

  • RunTime-动态运行时

    RunTime简介: RunTime简称运行时。OC就是运行时机制,也就是运行时候的一些机制,其中最主要的就是消息...

  • Runtime学习与总结

    一 : Runtime简介 Runtime简称运行时,OC就是运行时机制,其中最主要的是消息机制 对于C语言,函数...

  • runtime的简单了解以及使用

    runTime简称运行时。OC就是运行时机制,其中最主要的是消息机制。 1、函数定义: 2、runtime的使用 ...

网友评论

      本文标题:Runtime运行时机制

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