Runtime

289 阅读5分钟

Runtime

1、Runtime 介绍

Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。就是一个动态的消息转发机制。

2、消息结构

//对象
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};
//类
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list {
    struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    int method_count                                         OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
//方法
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

类对象 objc_class

由上面代码可以看出他是一个结构体,并包含很多变量。有实例变量列表、方法列表、缓存、遵守的协议列表等 类对象也有一个isa指针,说明他也是一个对象。他的isa指向元类。

实例isa指向类对象,类对象isa指向元类。元类的isa指向父类的元类

元类 Meta Class

元类(Meta Class)是一个类对象的类。 在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。 为了调用类方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就引出了meta-class的概念,元类中保存了创建类对象以及类方法所需的所有信息。 任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。

Method

就是我们平时使用的方法。可以独立完成一段功能

他有三个属性

  • SEL method_name 方法名
  • char *method_types 方法类型
  • IMP method_imp 方法实现

selector

就是个方法选择器,他是SEL类型的。但是是className+method 的组合。是一个字符串。所以同一个类里面的方法名不能重复。

IMP

方法实现的指针

Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。

objc_cache

上面已经提到过。 就是当你搜索方法的时候,他会先搜搜类缓存。

objc_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;
};
name:是指 class_name 而不是 category_name。
cls:要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象。
instanceMethods:category中所有给类添加的实例方法的列表。
classMethods:category中所有添加的类方法的列表。
protocols:category实现的所有协议的列表。
instanceProperties:表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的。

从上面的category_t的结构体中可以看出,分类中可以添加实例方法,类方法,甚至可以实现协议,添加属性,不可以添加成员变量。

3、Runtime消息传递

  • 查找方法
  1. 当你对一个对象进行消息转发的时候,会通过对象的isa指针找到他的class
  2. 在class里面有一个 method list 里面找方法
  3. 如果找不到则找父类,如果找到了,加入objc_cache提高函数查询的效率(method_name是key,imp是value)。避免每次调用都需要便利method list。
  4. 如果在class继承树里面都没有找到method,那么进入消息转发
  • 消息转发
  1. 动态方法解析
/**  1、动态方法解析, 就是动态的添加一个方法  **/
void watchTV(id self, SEL _cmd, NSString *name) {
    NSLog(@"我在看  %@", name);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
//    NSString *method = NSStringFromSelector(sel);
//    if ([method isEqualToString:@"watchTV:"]) {
//        /**
//         1、第一个返回值 void - v
//         2、@ 是第一个参数 id
//         3、方法传:
//         4、NSString 传 @
//         **/
//        return class_addMethod(self, sel, (IMP)watchTV, "v@:@");
//    }
    return NO;
}
  1. 快速转发
// 动态转发机制, 找一个新的对象来接收方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
//    NSString *method = NSStringFromSelector(aSelector);
//    if ([method isEqualToString:@"watchTV:"]) {
//        return [Dog new];
//    }
    return [super forwardingTargetForSelector:aSelector];
}
  1. 慢速转发
/**  快速转发找不到
 进入慢速转发
 1、方法签名 保存方法的具体信息
 2、慢速转发
 **/

// 签名 帮方法信息保存的NSInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSString *method = NSStringFromSelector(aSelector);
    if ([method isEqualToString:@"watchTV:"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL selector = [anInvocation selector];
    Dog *dog = [Dog new];
    if ([dog respondsToSelector:selector]) {
        [anInvocation invokeWithTarget:dog];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

4、runtime应用

demo下载

关联对象给分类增加属性

实现对象的set get 方法

在set方法里面调用

objc_setAssociatedObject

在get方法里面调用

objc_getAssociatedObject

添加方法和替换和KVO实现

例如我们上面所说的,动态方法解析的时候可以给一个类动态添加方法。 这里我们可以用于一些失败的处理,面向切面等。

方法替换

1、例如我们需要给每一个ViewController 的 viewdidload 添加埋点,那就可以使用交换方法来实现,而且不影响原项目

2、例如例如我们要给一个tableView为空数据的时候添加一张默认展示图片,上面的demo 有具体代码实现。

核心思想就是利用 class_getInstanceMethod 获取方法 并用 method_exchangeImplementations 来交换方法。

KVO实现

通过上面demo实现自定义KVO可以帮助我们更好的理解KVO和runtime 核心思想

  • 监听对象创建他的子类。

    · objc_allocateClassPair // 创建新的class

    · objc_registerClassPair // 注册新的class

  • 修改isa 指针 object_setClass 让isa指针指向新的class 。实现set方法并发送消息

  • 重新setter方法

  • class_addMethod 添加setter方法
objc_msgSendSuper 先执行父类方法
关联 Observer。然后在执行父类方法之后获取 Observer 发送消息
    objc_setAssociatedObject
    objc_getAssociatedObject
    objc_msgSend

热更新解决Bug 例如 wax

使用脚本语言利用消息转发的特性,进行动态的替换,添加等方式。实现热更新

实现字典和模型的自动转换(MJExtension)

就是利用runtime来访问model的所有属性来取值和赋值。具体看demo

参考链接