Runtime

什么是运行时

运行时是OC动态性得以实现的一个机制,OC以一个动态语言,把静态语言编译和链接的事情放到了运行时来处理,但是怎么处理呢,运行时机制就是处理这个事情的,它是一套用C和汇编编写的API。
并且苹果开源了API, 其中主要在文件runtime.h 和 message.h中

核心概念

类的本质
objc_class 和 Class

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

}

// Class  结构体指针,  指向某个类实例, 表示这个类
typedef struct objc_class *Class;

类的本质,或者说数据格式就是结构体, 一个结构体变量就是一个类对象,或者说实例。

objc_object 和 id

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;

objc_selector 和 SEL

typedef struct objc_selector *SEL;

IMP 可以理解为函数指针, 指向函数实现首地址

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif
/// 方法    Method  结构体指针,  一个方法包含 方法名SEL,  方法类型 和 方法的实现IMP
struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
} 
typedef struct objc_method *Method;


/// 实例变量
struct objc_ivar {
    char * _Nullable ivar_name         // 变量名                      OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type              // 变量的类型编码                  OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}
typedef struct objc_ivar *Ivar;

/// 分类  包括分类名,  类名,实例方法列表,类方法列表和协议列表
struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}
typedef struct objc_category *Category;

/// 属性
typedef struct objc_property *objc_property_t;

类,父类,元类

/*
        1, 实例的类其实也是对象, 叫做类对象,区别就是类对象在内存中只有一份  类对象的类叫做元类
        2,当调用实例方法时, 会去类对象的方法列表中查找匹配
        3,当调用类方法时,  会去类的元类中查找
        4,观察类存储结构的定义,  发现有isa和  super两个class类型的数据,  其中isa指向所属的类,super指向父类
        5.每个实例对象的类都是类对象,每个类对象的类都是元类对象,每个元类对象的类都是根元类(root meta class的isa指向自身)
      6.类对象的父类最终继承自根类对象NSObject,NSObject的父类为nil
        7.元类对象(包括根元类)的父类最终继承自根类对象NSObject
     */
类和元类结构图.png

方法调用的本质?
方法调用的本质就是向方法调用者发送了一条消息,核心方法是
objc_msgSend(void /* id self, SEL op, ... */ ) , OC中的每一个方法调用会转化成这个c函数调用, 其中第一个参数是方法的调用者, 第二个参数表示方法名, 之后是可变参数列表, 可以传入调用方法需要的参数。那么这个方法底层做了什么呢, 它会去该实例的类对象的方法列表中寻找同名的方法, 如果在本类中找不到就去到父类中寻找, 如果找到同名方法SEL或者Selector后, 拿到对应的IMP,然后根据参数调用对应的函数。如果找不到就会进入消息转发

有什么作用

利用runtime提供的API我们可以获取所有已经注册的类,获取指定类的信息(包括名字,所有实例变量,属性, 方法信息) 动态的创建类, 给类添加方法,属性(assiociateObject)和 交换方法的实现(hook)
1, 获取类的所有实例变量(类型 名字)其中实例变量包括类中定义的实例变量和属性生成的实例变量
2,获取属性
3,获取方法。 获取对象方法是在本类中查找,, 获取类方法需要到元类中查找
4,添加属性。
4.1,对于还没有注册的类 添加属性有相应的函数,但是属性类型编码需要看一下
4.2, 对于已经注册过的类,如果想添加属性的话,只能使用关联对象了。对于还没有注册的类 有相应的函数可以添加属性
5,添加方法 改变方法的实现
6,动态的添加一个类 创建实例
7,获取实例变量 和 属性区别
8, 用运行时配合KVC 改变系统的私有属性
详见demo https://github.com/JTWang4778/RuntimeDemo

在Swift中使用和在OC中使用有什么区别

Swift代码中已经没有了Objective-C的运行时消息机制, 在代码编译时即确定了其实际调用的方法. 所以纯粹的Swift类和对象没有办法使用runtime, 更不存在method swizzling.
为了兼容Objective-C, 凡是继承NSObject的类都会保留其动态性, 依然遵循Objective-C的�运行时消息机制, 因此可以通过runtime获取其属性和方法, 实现method swizzling.

面向切面编程

APO, Aspect Oriented Programming面向切面编程, 可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。利用运行时我们可以在IOS开发中运用面向切面编程的思路给工程统一添加某一项功能。 典型的是给现有的类添加方法和属性,然后hook到原有实现添加处理。 有名的三方有FDFullscreenPopGesture 和 MLeaksFinder

关于runtime的几个问题

1, + class, -class 和 objc_getClass 作用一样吗?
object_getClass(obj)返回的是obj中的isa指针;而[obj class]则分两种情况:一是当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。
https://www.jianshu.com/p/ae5c32708bc6
2,isKindOfClass 和 isMemmberOfClass 区别

//    JTView *subInstance = [JTView new];
//    // 调用者是否为给定类的实例或任何继承自给定类的实例。
//    BOOL asdf = [subInstance isKindOfClass:[JTView class]];
//    if (asdf) {
//        NSLog(@"是子类");
//    }else {
//        NSLog(@"不是子类");
//    }
//    // 是否是给定类的实例
//    if ([subInstance isMemberOfClass:[JTView class]]) {
//        NSLog(@"是该类的子类");
//    }else {
//        NSLog(@"不是该类的子类");
//    }

3, 几个面试题
http://blog.sunnyxx.com/2014/11/06/runtime-nuts/

4,+load 和 +initialize 方法

  • 调用方式不同,load方法是在main方法执行之前,直接调用函数,而initialize走的是消息机制
  • 调用时机不同, load方法是在main方法执行之前, 类加载进内存的时候调用的,并且只要类实现了load方法必定调用。而initialize是惰性调用, 只有第一次使用类的时候才会调用
  • load方法的调用顺序是(如果都实现的话)超类,子类,分类,对于多个分类的调用顺序就不一定了。如果没有实现就不调用,不会调用父类的load方法。也就是说一个类的load方法只调用一次,但是initialize可能调用多次。
  • initialize 是线程安全的, 如果子类没有实现就会调用父类的方法

你可能感兴趣的:(Runtime)