美文网首页
1.Runtime的使用总结

1.Runtime的使用总结

作者: 峰子1994 | 来源:发表于2017-11-04 17:07 被阅读7次

1.前言:

Runtime是想要做好iOS开发,或者说是真正的深刻的掌握OC这门语言所必需理解的东西。最近在学习Runtime,有自己的一些心得,整理如下,
一为 查阅方便
二为 或许能给他人一些启发,
三为 希望得到大家对这篇整理不足之处的一些指点。

2.什么是Runtime?

1.runtime是一套比较底层的纯c语言API,属于C语言库,包含了很多底层的C语言API
,在我们平时编写的OC代码中,程序运行的过程中,其实最终都转成了runtime的c语言代码,runtime算是oc幕后工作者
2.RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
3.对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
4.OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。
5.只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。

3.基本结构及其继承关系

1.要谈Runtime首先必然要先了解oc的对象以及类的结构,这非常能够帮组我们其中你的动态:
2.首先我们要导入<objc/objc.h>文件,可以看到如下定义:

源代码:

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// 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;
#endif
通过上面定义我们可以看出以下几点:
我们常用的id类型实际上一个指向objc_object结构体的指针,id通常指代一个对象,也就是说OC对象其实就一个指向objc_object结构体的指针
我们看objc_object结构体定义,得知其结构体内有一个类型为Class的字段isa,这就是我们常说的isa指针了。
再来看Class的声明,为一个指向objc_class的指针,
实际上isa就是指明当前结构体所属类型,我们可以理解为objc_object为Class类型的

接下来我们继续打开<objc/runtim.h>,来看objc_class声明

struct objc_class {
Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                      OBJC2_UNAVAILABLE;// 父类
    const char *name                       OBJC2_UNAVAILABLE;// 类名
    long version                           OBJC2_UNAVAILABLE;// 类的版本信息,默认为0
    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;

下面说一下我们感兴趣的几个字段:

isa:这里的isa指针同样是一个指向objc_class的指针,表明该Class的类型,这里的isa指针指向的就是我们常说的meta-class了。不难看出,类本身也是一个对象
super_class:这个指针就是指向该class的super class,即指向父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。
cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变。
objc_method_list: 方法链表中存放的是该类的成员方法(-方法),类方法(+方法)存在meta-class的objc_method_list链表中。

4.Runtime的使用方法:

1.我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething));。
2.OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。
3.相关的定义:
/// 描述类中的一个方法
typedef struct objc_method *Method;
/// 实例变量
typedef struct objc_ivar *Ivar;
/// 类别Category
typedef struct objc_category *Category;
/// 类中声明的属性
typedef struct objc_property *objc_property_t;

类在runtime中的表示:

//类在runtime中的表示
struct objc_class {
    Class isa;//指针,顾名思义,表示是一个什么,
    //实例的isa指向类对象,类对象的isa指向元类

#if !__OBJC2__
    Class super_class;  //指向父类
    const char *name;  //类名
    long version;
    long info;
    long instance_size
    struct objc_ivar_list *ivars //成员变量列表
    struct objc_method_list **methodLists; //方法列表
    struct objc_cache *cache;//缓存
    //一种优化,调用过的方法存入缓存列表,下次调用先找缓存
    struct objc_protocol_list *protocols //协议列表
    #endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

5.获取列表:

有时候会有这样的需求,我们需要知道当前类中每个属性的名字(比如字典转模型,字典的Key和模型对象的属性名字不匹配)。
我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)。

调用的方法:

unsigned int count;
    //获取属性列表
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (unsigned int i=0; i<count; i++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
    }

    //获取方法列表
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Method method = methodList[i];
        NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
    }

    //获取成员变量列表
    Ivar *ivarList = class_copyIvarList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Ivar myIvar = ivarList[i];
        const char *ivarName = ivar_getName(myIvar);
        NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
    }

    //获取协议列表
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
    }

在Xcode上跑一下看看输出吧,需要给你当前的类写几个属性,成员变量,方法和协议,不然获取的列表是没有东西的。
注意,调用这些获取列表的方法别忘记导入头文件#import <objc/runtime.h>。

6.方法调用:

1.如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。
2.如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。
``
调用的顺序:

首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。
如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
如果没找到,去父类指针所指向的对象中执行1,2.
以此类推,如果一直到根类还没找到,转向拦截调用。
如果没有重写拦截调用的方法,程序报错。
以上的过程给我带来的启发:

重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了。
如果想调用已经重写过的方法的父类的实现,只需使用super这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。

#7.拦截调用: 

重写了拦截调用的方法并且返回了YES,我们要怎么处理呢?
有一个办法是根据传进来的SEL类型的selector动态添加一个方法。
首先从外部隐式调用一个不存在的方法:
//隐式调用方法
[target performSelector:@selector(resolveAdd:) withObject:@"test"];

8.关联对象

现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。
这种情况的一般解决办法就是继承。
但是,只增加一个属性,就去继承一个类,总是觉得太麻烦类。
这个时候,runtime的关联属性就发挥它的作用了。

实现代码:

//首先定义一个全局变量,用它的地址作为关联对象的key
static char associatedObjectKey;
//设置关联对象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
NSLog(@"AssociatedObject = %@", string);

#9.方法交换:

方法交换,顾名思义,就是将两个方法的实现交换。例如,将A方法和B方法交换,调用A方法的时候,就会执行B方法中的代码,反之亦然。
话不多说,这是参考Mattt大神在NSHipster上的文章自己写的代码。

相关文章

  • 1.Runtime的使用总结

    1.前言: 2.什么是Runtime? 3.基本结构及其继承关系 源代码: 接下来我们继续打开

  • Runtime-原理与应用

    使用运行时前提,必须导入#import 1.runtime使用技巧 谁来发生动作...

  • 1.runtime

    runtime 常见作用 该文件的目的是为读者谁可能有兴趣学习的Objective-C的运行时。因为这不是一个关于...

  • 2017年面试总结

    1.Runtime的理解和项目运用2.TableView的优化 正确使用reuseIdentifier来重用Cel...

  • Runtime 机制

    1.Runtime 消息机制 从xcode6之后苹果不建议直接使用obj_msgSend(),需要自已去xcode...

  • IOS 2020-02-28 一周学习内容总结

    1.runtime的内容复习总结,知道了消息传递和消息转发的机制和内部原理。 2.算法部分看了三个问题:1.两数之...

  • runtime

    1.runtime的gc机制2.runtime的调度机制

  • RunTime源码阅读(十一)之方法添加原理

    1.Runtime交换 问题:method_exchangeImplementations与class_repla...

  • iOS问题汇总

    1.runtime 2.block 3.扩展 4.__week __block

  • 面试总结

    1.Runtime介绍以及实际应用https://blog.csdn.net/TuGeLe/article/det...

网友评论

      本文标题:1.Runtime的使用总结

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