了解Runtime

跟C、C++等语言有着很大的不同,OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时才进行。OC的动态性是由Runtime API来支撑的,Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写。

一、isa

1、什么是isa

isa是一个Class类型的指针。每个实例对象有个isa的指针指向对象的类,而类里也有个isa的指针指向meteClass(元类)。

类保存了实例方法列表,而元类则保存了类方法列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。
同时,元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

实例/类对象的isa指针结构图
2、isa结构

在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。

使用共用体(union)可以利用内存空间去存储更多的信息。

union isa_t
{
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;   //0:代表普通的指针,存储着Class、Meta-Class对象的内存地址; 1:代表优化过,使用位域存储更多的信息
        uintptr_t has_assoc         : 1;   //是否有设置过关联对象,如果没有,释放时会更快
        uintptr_t has_cxx_dtor      : 1;   //是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快。(析构函数:用来释放内存的操作)
        uintptr_t shiftcls          : 33;  //存储着Class、Meta-Class对象的内存地址信息
        uintptr_t magic             : 6;   //用于在调试时分辨对象是否未完成初始化
        uintptr_t weakly_referenced : 1;   //是否有被弱引用指向过,如果没有,释放时会更快
        uintptr_t deallocating      : 1;   //对象是否正在释放
        uintptr_t has_sidetable_rc  : 1;   //引用计数器是否过大无法存储在isa中,如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
        uintptr_t extra_rc          : 19;  //里面存储的值是引用计数器减1
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
}

二、Class

1、Class的结构

类对象(class)是程序员定义并在运行时由编译器创建的。
通过对类对象进行alloc、new操作创建出实例对象(instance)。
元类对象(meta-class)是类对象的类,是类对象中isa指针所指的类。

实例对象、类、元类

在源码中,Class是一个指向objc_class结构体的指针。

typedef struct objc_class *Class;
typedef struct objc_object *id;

他的内部结构如下:

Class的结构.png

class_rw_t
class_rw_t:rw表示可读写
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容。

class_rw_t的结构

class_ro_t
class_ro_t:ro表示只读
class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,只包含了类的初始内容。

class_ro_t的结构

也就是在加载可执行文件的时候,一开始,类的结构里class_rw_t没有内容。在后续过程中,会将class_ro_t里的初始内容拷贝到class_rw_t中,如果有新增分类,分类里的内容也会被写入到class_rw_t中。

2、方法的结构method_t

Method是一种代表类中的某个方法的类型。

typedef struct method_t *Method;

method_t就是对方法的封装,在上面class_rw_t、class_ro_t中的方法列表里,都含有结构体method_t,它存储了方法名、方法类型和方法实现。

struct method_t {
    SEL name;           //方法名、函数名
    const char *types;  //编码(返回值类型、参数类型)
    IMP imp;            //指向函数的指针(函数地址)

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

SEL

typedef struct objc_selector *SEL;

代表方法名,一般叫做选择器,底层结构跟char *类似;
可以通过@selector()和sel_registerName()获得;
可以通过sel_getName()和NSStringFromSelector()转成字符串;
相同名字的方法即使在不同类中定义,它们的方法选择器也相同。

types

types

方法类型 types 是个char指针,存储着方法的参数类型和返回值类型。
types里存贮的内容是encode每个方法的返回值、参数类型,将这些信息保存在一个字符串里,再将这个string与selector关联起来。
(涉及到的知识点:Type Encodings)。

IMP

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

它就是一个函数指针,这是由编译器生成的。
当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的,而 IMP 这个函数指针就指向了这个方法的实现。既然得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法。
IMP 指向的方法与 objc_msgSend 函数类型相同,参数都包含 id和 SEL 类型。每个方法名都对应一个 SEL 类型的方法选择器,而每个实例对象中的 SEL 对应的方法实现肯定是唯一的,通过一组 id和 SEL参数就能确定唯一的方法实现地址。

3、方法缓存

在调用方法时会根据对象的isa指针找到类对象,在类对象的列表(class_rw_t中的methods列表)里找对象方法,如果找不到会通过superClass指针找到父类的类对象,然后在其方法列表中查找。
为了节省时间,苹果在objc_class结构体中设计了一个cache用来缓存调用过的方法。当第一次调用某个方法时,找到该方法后,会将这个方法存放到cache中,下次再调用该方法时,会先在类对象isa中的cache中找方法。

struct cache_t {
    struct bucket_t *_buckets; // 散列表,数组里面放的是bucket_t这个结构
    mask_t _mask;           //散列表的长度-1
    mask_t _occupied;       //已经缓存的方法数量
    //...more code...
};


struct bucket_t {
    cache_key_t _key; //SEL作为Key
    IMP _imp;  //函数的内存地址
    //... more code ...
};

chche_t的结构如上,通过散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度。
具体做法是用@selector() & _mask得到一个索引值,根据这个索引去散列表中找到方法的_imp,如果查找结果不对,会索引值减1,再对比方法。由于_mask的值为散列表长度减1,这样保证查找范围不会超过散列表范围。而且当散列表不够存储时,散列表容量扩大一倍并清除内容再重新缓存方法。

三、总结

  • <1>什么是runtime?平时的项目中有用过吗?
    (1) 、OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时才进行,OC的动态性就是由Runtime来支撑的。
    (2)、Runtime的运用:
    关联对象给分类添加属性
    遍历类的所有成员变量(修改textField的占位文字颜色、字典转模型、自动归档解档)
    交换方法实现,主要是交换系统自带的方法,(友盟埋点,我是在利用了runtime)
    利用消息转发机制解决方法找不到的问题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容