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 是线程安全的, 如果子类没有实现就会调用父类的方法
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容