JavaRuntime那么些事

Runtime

前言

从字面意思看,就是运行时。可是这多少个运行时究竟咋样看头?能够把它了然成:不是在编译期也不是在链接期,而是在运行时。这到底在运作期间做了什么样啊?遵照苹果官方的传教,就是把有些裁决(方法的调用,类的充分等)推迟,推迟到运行期间。只要有可能,程序就足以动态的形成任务,而不是我们在编译期已经控制它要做到什么任务。这就象征了OC不仅仅需要编译器,还需要一个周转时的连串来支撑。

目录

接下去就对Runtime做一个体系的牵线,重要内容囊括:

  1. 简介
  2. 涉嫌到的数据结构
  3. runtime.h解析
  4. 哪些可以接触到Run提姆(Tim)e?
  5. 消息
  6. 动态音信分析
  7. 消息转发
  8. Runtime的利用境况

1.简介

基于前言,你早已了解了Runtime大概是个什么样鬼,在OC发展过程中,它重要有五个本子:Legacy和Modern。Legacy版本接纳的是OC1.0版本;Modern版本采纳的OC2.0版本,而且相相比Legacy也添加了有的新特色。最醒目的区分在于:

  • 在legacy版本,假如您转移了类的布局,那么你必须另行编译继承自它的类。
  • 在modern版本,假设你改变了类的布局,你不要再次编写继承自它的类。
平台

vivo的应用程序以及OS X
v10.5版本的64位机器使用的是modern版本的runtime。
此外(OS X桌面应用32位程序)使用的是legacy版本的runtime。

2.关系到的数据结构

这边关键介绍一下在runtime.h里面涉及到的一些数据结构。

Ivar

Ivar从字面意思来讲,它就是意味的实例变量,它也是一个结构体指针,包含了变量的称号、类型、偏移量以及所占空间。

SEL

选取器,每个方法都有谈得来的采用器,其实就是办法的名字,但是不仅仅是措施的名字,在objc.h中,我们可以看到它的概念:

/// An opaque type that represents a method selector.一个不透明类型,用来代表一个方法选择器
typedef struct objc_selector *SEL;

由定义可知它是一个objc_selector的结构体指针,难堪的是在runtime源码中并不曾找到该结构体。推断它里面应该就是一个char
的字符串。
你可以采取:

 NSLog(@"%s",@selector(description));  //%s用来输出一个字符串

打印出来description。
在这里你可以把它了解成一个采纳器,可以标识某个方法。

IMP

它是一个函数指针,指向方法的贯彻,在objc.h里面它的定义是如此的:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif
id

id是一个大家平常利用的序列,可用于作为类型转换的中介者。它相仿于Java里面的Object,可以转换为其他的数据类型。它在objc.h里面是那样定义的:

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

它实质上是一个objc _
object的结构体指针,而在末端将要提到的Class其实是个objc _
class的指针,而objc _ class是延续自objc _o
bject的,由此可以互相转换,这也是怎么id可以转移为此外任何的数据类型的原委。

Method

办法,它实在是一个objc_method的结构体指针,其定义如下:

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}  

本条就比较好通晓了,该结构体包含了措施的名称(SEL),方法的系列以及艺术的IMP。

Class

它是一个objc_class的结构体指针,在runtime.h中的定义如下:

/// An opaque type that represents an Objective-C class.一个不透明类型,代表OC的类
typedef struct 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

} OBJC2_UNAVAILABLE;

该结构体中各部分介绍如下:

  • isa:是一个Class类型的指针,每个对象的实例都有isa指针,他针对性对象的类。而Class里面也有个isa指针,它指向meteClass(元类),元类保存了类措施的列表。
  • name:对象的名字
  • version:类的版本号,必须是0
  • info:供运行期间采纳的位标识
  • instance_size:该类的实例大小
  • ivars:成员变量数组,包含了此类包含的分子变量
  • methodLists:包含方法的数组列表,也是一个结构体,该结构体里面还含有了一个obsolete的指针,表示丢弃的办法的列表
  • cache:缓存。这几个相比复杂,在后边会波及,这里先忽略。
  • protocols:协议列表,也是一个数组

而在objc-runtime-new.h中,你会发现这样的定义(在runtime中并从未完全表露objc_class的实现):

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    //其他的省略

其实objc _ class继承自objc _
object。所以这也讲明了干吗id可以转移为另外的品类。

3.runtime.h解析

我们先看一下在usr/include/objc/runtime.h,这多少个是其余一个工程都足以向来找到的,它是SDK的一局部。重要定义了以下内容:

  1. 概念了有些项目,例如Method/Ivar/Category等,还有局部结构体。
  2. 函数。函数里面有分了几大类:
    • 关于目标实例的点子,例如object _ getClass、object _
      setClass以及object _ getIvar等。这个函数大多以object开头
      用来拿到属性或者对目的进行操作。
    • 取得类定义的艺术,例如objc _ getClass/objc _
      getMetaClass等,这多少个艺术更多的是得到Class或者在Class级别上展开操作。
      多以objc开头
    • 和类相关的办法。例如class _ getName/class _
      isMetaClass等,这一个更多的是得到Class的一对属性。比如该类的属性列表、方法列表、协议列表等。传参大多为Class。
      多以class开头
    • 实例化类的有的方法。例如class _
      createInstance方法,就是一对一于通常的alloc init。
    • 添加类的办法。例如你可以采纳那个情势春季的挂号一个类。使用objc
      _ allocateClassPair成立一个新类,使用 objc _
      registerClassPair对类进行登记
    • 等等。。。
  3. 其余就是一对撇下的不二法门和花色。

4. 什么样可以接触到Run提姆e?

有两种不同的办法得以让OC编程和runtime系统相互。

OC源代码

大部分情景下,我们写的OC代码,其实它底层的贯彻就是runtime。runtime系统在幕后自动帮大家处理了操作。例如咱们编译一个类,编译器器会创建一个结构体,然后这多少个布局体会从类中捕获音讯,包括方法、属性、Protocol等。

NSObject的一对艺术

在Foundation框架之中有个NSObject.h,在usr/include/objc里面也有一个NSObject.h。而我辈一贯使用的类的基类是/usr/include/objc里面的这么些NSObject.h,Foundation里面的NSObject.h只是NSObject的一个Category。所以这里我们更珍重一下/usr/include/objc里面的NSObject.h。
鉴于大部分目标都是NSObject的子类,所以在NSObject.h里面定义的法门都得以拔取。
在那多少个点子里面,有部分办法可以查询runtime系统的信息,例如:

- (BOOL)isKindOfClass:(Class)aClass;   //用来检测一个对象是否是某各类的实例对象,aClass也有可能是父类,同样可以检测出来。
- (BOOL)isMemberOfClass:(Class)aClass;   //而该方法只能检测一个对象是否是某各类的实例对象。但如果aClass不能为该类的父类,如果是父类则该方法返回NO

- (BOOL)respondsToSelector:(SEL)aSelector;

- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

- (IMP)methodForSelector:(SEL)aSelector;

此间用代码对isKindOfClass和isMemberOfClass做个简易介绍:

//stu是Student的实例对象,Student的父类为Person,Person的父类为NSObject。
[stu isKindOfClass:[Student class]];    //YES
[stu isKindOfClass:[Person class]];    //YES
[stu isKindOfClass:[NSObject class]];   //YES

[stu isMemberOfClass:[Student class]];    //YES
[stu isMemberOfClass:[Person class]];    //NO
[stu isMemberOfClass:[NSObject class]];   //NO

俺们可以在objc源代码中的NSObject.mm中见到相应的落实:

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

从切实贯彻可知,为何isKindOfClass可以检测出superclass。其余,在NSObject.h中,并没有观看三个点子的类情势讲明,不过在贯彻里面却含有了类措施的贯彻。这里有个问题:怎么一贯不对外宣称的六个类形式还是可以够在表面调用呢?(比如自己得以从来利用[Student
isMemberOfClass:[NSObject class]])

此处还用到了class方法,这多少个法子声明如下:

+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");

- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");

+ (Class)class {   //返回当前的self
    return self;
}

- (Class)class {
    return object_getClass(self);
}

此地最首要的是清楚self究竟代表着怎么着:

  1. 当self为实例对象的时候,[self class] 和
    object_getClass(self)是等价的。object_getClass([self
    class])得到的是元类。
  2. 当self为类对象的时候,[self
    class]重返的是自身,依然self。object_getClass(self)
    与object_getClass([self class])等价。得到的是元类。
Runtime函数

runtime系统实际就是一个动态共享的Library,它是由在/usr/include/objc目录的公共接口中的函数和数据结构组成。
Java 1

5. 消息

在Objective-C中,信息直到运行时才将其与音讯的落实绑定,编译器会将

[receiver message];

转换成

objc_msgSend(receiver,selector);   //1

objc_msgSend(receiver,selector,arg1,arg2,...);  //2

如若带有参数,那么就会执行2办法。其实不外乎该模式,还有以下多少个格局:

objc_msgSend_stret    
objc_msgSendSuper
objc_msgSendSuper_stret

当想一个对象的父类发送message时,会利用

objc_msgSendSuper

万一措施的重临值是一个结构体,那么就会动用

objc_msgSend_stret
objc_msgSendSuper_stret

此处大家得以打开objc源码,然后你会意识内部有多少个.s文件:
Java 2
这边之所以有objc-msg-类的两样文件,我怀疑应该是对不同的CPU指令集(指令不均等)做了独家处理。因为这个.s文件名称中包含的是不同的arm指令集。而且打开.s文件你会发现其间的贯彻是汇编语言,所以苹果为了效能仍然蛮拼的,直接用汇编语言实现。
里头就能找到objc _ msgSend的实现(objc-msg-i386.s中):
Java 3
尽管如此对汇编精通不是太多,可是这些文件中的注释很详细,从注释可以观察objc_msgSend方法的履行进程:

  1. 先加载receiver和selector到寄存器,然后判断receiver是否为空,假若为空,则函数执行完毕;
  2. 假定receiver不为空,开头找寻缓存,查看方法缓存列表里面是不是有改selector,假诺有则实施;
  3. 比方没有缓存,则搜索方法列表,假设在措施列表中找到,则跳转到具体的imp实现。没有则进行完毕。
应用了藏匿参数

在发送一个消息的时候,会被编译成objc_msgSend,此时该音讯的参数将会传出objc_msgSend方法里面。除此之外,还会蕴藏六个暗藏的参数:

  1. receiver
  2. method的selector

这五个参数在地点也有提到。其中的receiver就是新闻的发送方,而selector就是采用器,也可以直接用
_ cmd来指代( _
cmd用来代表当前所在模式的SEL)。之所以隐蔽是因为在点子表明中并从未被强烈宣称,在源代码中大家仍旧可以引用它们。

得到形式地址

我们每一遍发送消息都会走objc_msgSend()方法,那么有没有措施规避音讯绑定直接获取情势的地点并调用方法吗?答案自然是局部。大家地方简单介绍了IMP,其实大家可以运用NSObject的

- (IMP)methodForSelector:(SEL)aSelector;

主意,通过该办法拿到IMP,然后调用该措施。可是避开信息绑定而一贯调用的采纳并不常见,不过一旦你要再三循环调用的话,直接得到情势地址并调用不失为一个节能操作。看下边的代码:

 void (*setter)(id,SEL,BOOL);
    setter = (void(*)(id,SEL,BOOL))[stu2 methodForSelector:@selector(learning)];
    NSDate *startDate = [NSDate date];
    for (int i = 0;i<100000;i++) {
        setter(stu2,@selector(learning),YES);
    }
    double deltaTime = [[NSDate date] timeIntervalSinceDate:startDate];
    NSLog(@"----%f",deltaTime);

    NSDate *startDate1 = [NSDate date];
    for (int i = 0;i<100000;i++) {
        [stu2 learning];
    }
    double deltaTime1 = [[NSDate date] timeIntervalSinceDate:startDate1];
    NSLog(@"----%f",deltaTime1);

您可以自动跑一下,看一下时刻距离。你会发现:获取格局地址直接调用更省时间,但请留意利用情况。

6. 动态音信分析

这边介绍一下假使动态地提供形式的贯彻。

动态方法分析

在开发进程中,你可能想动态地提供一个方法的落实。比如我们对一个目的注解了一个性质,然后大家利用了
@dynamic 标识符:

@dynamic propertyName;

该标识符的目标就是报告编译器:和这个特性相关的getter和setter方法会动态地提供(当然你也可以一向手动在代码里面实现)。这么些时候你就会用到NSObject.h里面的六个主意

+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

来提供格局的贯彻。
实际上OC方法就是一个大概的C函数,它至少含有了多少个参数self和 _
cmd,你可以协调讲明一个办法:

void dynamicMethodIMP(id self, SEL _cmd) {
    //这里是方法的具体实现
}

这儿我们可以在宣称属性的类中落实地点提到的多少个形式(一个是解析类方法,一个是分析实例方法),例如我在Person里面这么写:

@dynamic address;   //也就意味着我们需要手动/动态实现该属性的getter和setter方法。

您会发觉当我们运行下边的代码时,程序会crash:

   Person *zhangsan = [[Person alloc] init];
    zhangsan.address = @"he nan xinxiang ";

    NSLog(@"%@",zhangsan.address);

//    crash reason
// -[Person setAddress:]: unrecognized selector sent to instance 0x1d4449630

此地大概的做一个动态方法分析:

void setter(id self,SEL _cmd) {
    NSLog(@"set address");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *selStr = NSStringFromSelector(sel);
    if ([selStr hasPrefix:@"set"]) {
        class_addMethod([self class], sel, (IMP)setter, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

故此我们需要自己去落实setAddress:
方法。(这里判断用hasPrefix不太规范,开发者可以活动按照需求调整)。转发音信(上面会讲到)和动态解析是正交的。也就是说一个class有机会再信息转发机制前去动态解析此措施,也得以将动态解析方法重回NO,然后将操作转发给信息转发。

动态加载

OC编程也同意大家在程序运行的时候动态去创造和链接一个类依旧分类。这么些创设的类如故分类将会和运行app前创办的类一样,没有差别。
动态加载在开发的过程中得以做过多工作,例如系统安装中的不同模块就是动态加载的。
在Cocoa环境中,最经典的就是Xcode,它可以设置不同的插件,这么些也是动态加载的措施实现的。

7. 信息转发

发送一个信息给目的,假若目标无法处理,那么就会时有发生错误。但是,在发生错误从前,runtime
系统会给目的第二次机遇去处理该音讯。这里详细已经在通俗掌握消息的传递和转化作品中做了介绍,这里就不再介绍了。

8. Runtime的应用情状

Runtime的施用几乎无处不在,OC本身就是一门运行时语言,Class的转移、方法的调用等等,都是Runtime。此外,我们得以用Runtime做一些任何的事情。

字典转换Model

平生我们从服务端得到的数目是json字符串,我们得以将其转换成成NSDictionary,然后经过runtime中的一些艺术做一个转换:
先得到model的保有属性或者成员变量,然后将其和字典中的key做映射,然后经过KVC对性能赋值即可。更多可参见class_copyIvarList方法得到实例变量问题抓住的思辨中的例子。

热更新(JSPatch的实现)

JSPatch能到位JS调用和改写OC方法的根本原因就是OC是动态语言,OC上的拥有办法的调用/类的生科隆通过OC
Runtime在运行时开展,大家得以依据名称/方法名反射得到相应的类和措施。例如

Class class = NSClassFromString("UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString("viewDidLoad");
[viewController performSelector:selector];

也正是鉴于此,才实现了热更新。

给Category添加属性

大家得以行使runtime在Category中给类添加属性,这些重大行使了六个runtime钟的法门:

OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy);

OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);

现实应用可参见:给分类(Category)添加属性

Method Swizzling

它是改变一个已存在的selector的贯彻的技巧,比如您想将viewDidload方法替换为我们自定义的艺术,给系统的不二法门添加一些亟需的功力,来兑现某些需求。比如你想跟踪每个ViewController呈现的次数,你可以采纳该技能重写ViewDidAppear方法,然后做一些温馨的处理。能够参见Method
Swizzling
里头的任课。

总结

Objective-c本身就是一门夏天语言,所以了然runtime有助于我们进一步尖锐地打听其里面的实现原理。也会把有些接近很难的问题经过runtime很快解决。

参考链接:

1.Objective-C Runtime Programming
Guide

2.Objective-C
Runtime

3.objc4
4.浅显通晓音讯的传递和转发
5.class_copyIvarList方法拿到实例变量问题引发的思考
6.JSPatch
实现原理详解

7.给分类(Category)添加属性
8.Method Swizzling

转载请表明来源:http://www.cnblogs.com/zhanggui/p/8243316.html

相关文章