iOS开发之Runtime

  在学习Runtime之前,我们了解到,OC是一门动态性比较强的语言,这跟CC++有很大的不同。而OC动态运行时的本质就是RuntimeOC的底层实现都是基于Runtime来实现的,Runtime是开源的可以下载源码,底层由CC++以及汇编语言来实现。知道了这些,我们可以利用Runtime来做很多事情,现在让我们来详细的了解Runtime,以及如何在项目中使用Runtime
  在了解Runtime之前,首先我们要了解一个概念,什么是ISA,了解Runtime在底层常用的数据结构,我们先来看一下什么是ISA

ISA详解

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

在源码中我们可以找到这个共用体:

union isa_t
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
}

这些字段代表的含义:

nonpointer
0、代表普通指针,存储着Class、Meta-Class对象的内存地址
1、代表优化过,使用位域存储更多的信息
has_assoc
是否设置过关联对象,如果没有,释放时会更快
has_cxx_dtor
是否有C++的析构函数(.cxx.destruct),如果没有,释放时更快
shiftcls
存储着Class、Meta-Class对象的内存地址
magic
用于在调试时分辨对象是否未完成初始化
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快
deallocating
对象是否正在释放
has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
extra_rc
里面存储的值是引用计数器减1

想要对共用体以及isa位域等信息有一个详细的了解,需要大家知道位运算的基本知识,在这里不做过多的介绍。大概的思想就是通过位运算,我可以取出特定的位,这样本来需要一个字节(也就是8位)来存储一个信息,现在我用到了位运算,就可以用一位来存储某个信息,这样对计算机的内存有一个优化。想要知道Runtime,我们需要对类的结构有个了解,接下来我们了解一下Class的结构。

struct objc_class {
    Class  isa;
    Class  super_class;
    struct objc_cache * cache;//方法缓存
    struct objc_data_bits_t bits;//用于获取具体的类信息
};
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t methods;//方法列表
    property_list_t properties;//属性列表
    protocol_list_t protocols;//协议列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;//instance对象占用的内存空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;//类名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;//成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};
class_rw_t
  • class_rw_t里面的methods、properities、protocols是二维数组,包含了类的初始内容、分类的内容
class_ro_t
  • class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

baseMethodList里包含的数据类型是method_t

method_t

IMP代表函数的具体实现
SEL代表方法\函数名,一般叫选择器
1、可以通过@selector()和sel_registerName()活的
2、可以通过sel_registerName()和NSStringFromSelector()转成字符串
3、不同类中相同的名字的方法,所对应的方法选择器是相同的
types包含了函数返回值、参数编码字符串

struct method_t {
    SEL name;//函数名
    const char *types;//编码(返回值类型,参数类型)
    IMP imp;//函数地址
    };
};

方法缓存

Class内部有个方法缓存Cache_t,用散列表来缓存曾经调用过的方法,可以提高方法的查找速度

struct cache_t {
    struct bucket_t *_buckets;//散列表
    mask_t _mask;//散列表的长度
    mask_t _occupied;//已经缓存的方法数量
}
struct bucket_t {
    cache_key_t _key;//SEL作为可以
    IMP _imp;//函数的内存地址
}

  现在大家对类的底层实现,有了详细的了解,接下来,我们一起研究一下objc_msgSend方法调用的流程

objc_msgSend执行流程

OC中的方法调用,其实都是转换为objc_msgSend函数调用,objc_msgSend的执行流程可以分为三大阶段
1、消息发送
2、动态方法解析
3、消息转发

objc_msgSend执行流程 - 源码跟读

objc-msg-arm64.s
ENTRY_objc_msgSend
b.le LNilOrTagged
CacheLookup NORMAL
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY _objc_msgSend_uncached
.macro MethodTableLookup
_class_lookupMethodAndLoadCache3
objc-runtime-new.mm
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache

cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache

_class_resolveInstanceMethod
_objc_msgForward_impcache
objc-msg-arm64.s
STATIC_ENTRY_objc_msgForward_impcache
ENTRY_objc_msgForward
Core Foundation

forwarding(不开源)

   2、如果消息发送不成功,就会进入动态解析方法,开发者可以实现以下方法,来动态添加方法。动态解析过后,会重新走消息发送的流程,从receiverClasscache中查找放方法这一步重新开始执行。

+(BOOL)resolveClassMethod:(SEL)sel
+(BOOL)resolveInstanceMethod:(SEL)sel

   3、如果消息发送,以及动态方法解析都不成功,那么开始走最后一步,消息转发阶段,消息转发调用Runtime的方法:

- (id)forwardingTargetForSelector:(SEL)aSelector{
    return [super forwardingTargetForSelector:aSelector];
}
//方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [super methodSignatureForSelector:aSelector];
}
//封装了一个方法调用,包括:方法调用者,方法名,方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//    anInvocation.target方法调用者
//    anInvocation.selector方法名
//    [anInvocation getArgument:NULL atIndex:0]
}

Runtime的使用

    //动态创建一个类(参数:父类,类名,额外的内存空间)
    objc_allocateClassPair(Class  _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes);
    //注册一个类(要在类注册之前添加成员变量)
    objc_registerClassPair(Class  _Nonnull __unsafe_unretained cls);
    //销毁一个类
    objc_disposeClassPair(Class  _Nonnull __unsafe_unretained cls);
    //获取isa指向的class
    objc_getClass(const char * _Nonnull name);
    //设置isa指向的class
    object_setClass(id  _Nullable obj, Class  _Nonnull __unsafe_unretained cls);
    //判断一个OC对象是否为class
    object_isClass(id  _Nullable obj);
    //判断一个OC对象是否为meta-class
    class_isMetaClass(Class  _Nullable __unsafe_unretained cls);
    //获取父类
    class_getSuperclass(Class  _Nullable __unsafe_unretained cls);
    //获取一个实例变量
    class_getInstanceVariable(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);
    //copy实例变量列表(最后需要调用free释放)
    class_copyIvarList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
    //设置和获取成员变量的值
    object_setIvar(id  _Nullable obj, Ivar  _Nonnull ivar, id  _Nullable value);
    object_getIvar(id  _Nullable obj, Ivar  _Nonnull ivar);
    //动态添加成员变量(已经注册的类是不能动态添加成员变量的)
    class_addIvar(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types);
    //获取成员变量的相关信息
    ivar_getName(Ivar  _Nonnull v);
    ivar_getTypeEncoding(Ivar  _Nonnull v);
    //获取一个属性
    class_getProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);
    //拷贝属性列表
    class_copyPropertyList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
    //动态添加属性
    class_addProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount);
    //动态替换属性
    class_replaceProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount);
    //获取属性的一些信息
    property_getName(objc_property_t  _Nonnull property);
    property_getAttributes(objc_property_t  _Nonnull property);
    //获得一个实例方法、类方法
    class_getInstanceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    class_getClassMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    //方法实现相关操作
    class_getMethodImplementation(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
    method_setImplementation(Method  _Nonnull m, IMP  _Nonnull imp);
    method_exchangeImplementations(Method  _Nonnull m1, Method  _Nonnull m2);
    //拷贝方法发列表(最后需要调用free释放)
    class_copyMethodList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
    //动态添加方法
    class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
    //动态替换方法
    class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
    //获取方法的相关信息(带有copy的需要调用free释放)
    method_getName(Method  _Nonnull m);
    method_getImplementation(Method  _Nonnull m);
    method_getTypeEncoding(Method  _Nonnull m);
    method_copyReturnType(Method  _Nonnull m);
    method_copyArgumentType(Method  _Nonnull m, unsigned int index);

  Runtime还有许多API,在这里就不一一列举了,有兴趣的可以自己搜索,有很多相关的文章可以借鉴。利用这些API,我们可以在项目中利用关联对象,给分类添加属性、也可以遍历所有类的成员变量(修改textfield的站占位文字颜色,字典转模型,自动归结档等)、交换方法实现等一系列操作。

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

推荐阅读更多精彩内容