动态运行时(RunTime)

Runtime深度解析以及实用技巧

数据结构

  1. objc_object
objc_object结构体包含内容
  • isa_t (union):
    ① 指针型isaisa的值代表Class的地址;
    ② 非指针型isaisa部分值代表Class地址(在寻址过程中,可能30-40位就能代表Class,为了避免内存浪费,多出来的位就存放了一些内存管理相关的内容)
    isa_t (union)
  • isa指向
    ① 关于对象(objc_object),其指向类对象(objc_class),如果通过对象调用实例方法,是通过objc_object中的isa指针到objc_class对象中去查找;
    ② 关于类对象(objc_class),其指向元类对象(MetaClass),如果通过类调用类方法,实际上是通过objc_class中的isa指针,到MetaClass对象中去查找
  1. objc_class
objc_class
  • Class 是一个类对象,等同于objc_class,继承自objc_object
  • cache_t
    ① 用于快速查找方法执行函数
    ② 是可增量扩展的哈希表结构
    ③ 是局部性原理的最佳应用
  • class_data_bits_t
    class_data_bits_t主要是对class_rw_t的封装;
    class_rw_t 代表了类相关的读写信息(存储分类中的一些内容如 方法method_array_t methods、属性property_array_t properties、协议protocol_array_t protocols等),是对class_ro_t的封装;
    如;
    class_ro_t 代表了类相关的只读信息(储存类本身一些内容如 类名name,变量ivar_list_t * ivars,属性property_list_t,协议protocol_list_t,方法method_list_t
    class_rw_t

    class_ro_t
  1. method_t

method_t结构体代表了一个方法的所有内容

method_t

Type Encodings

  • const char* types;
    v 代表返回值是void类型;
    @ 代表第一个参数类型是id,即消息的接受者,如self([self init]);
    : 代表第二个参数是SEL类型的,即@selector(),如@selector(init)
    types

    type Encodings
  1. 整体数据结构
整体数据结构

对象、类对象、元类对象

  1. 什么是类对象元类对象(类(Class)和对象(id)以及方法(SEL))
    ① 类对象储存实例方法列表等信息
    ② 元类对象存储类方法列表等信息

消息传递 objc_msgSend

  1. 消息传递流程

对象接收到一个消息时,首先从方法缓存列表里寻找,如果找到了,进行函数调用,如果没有找到,则在当前类方法列表中寻找,找到了则进行函数调用,没找到就会逐级到父类的缓存列表、方法列表中寻找,找到了进行函数调用,如果到最上层还没找到则进入消息转发机制


消息传递流程图例
  1. void objc_msgSend(void / id self, SEL op, ... / )
    消息传递转换成函数调用(这一步骤实际上是由编译器上做的)
    objc_msgSend
  2. void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
   struct objc_super{
       //specifies an instance of a class
       __unsafe_unretained id receiver;//实际上就是当前对象
   }
objc_msgSendSuper

下面是一个关于[self class][super class]的例子

@interface ClassA : NSObject
@end
@implementation ClassA
- (instancetype)init
{
   self = [super init];
   if (self) {
       NSLog(@"%@", NSStringFromClass([self class]));
       NSLog(@"%@", NSStringFromClass([super class]));
   }
   return self;
}
@end

输出结果为:

2020-04-10 23:04:46.845874+0800 test[12339:2077774] ClassA
2020-04-10 23:04:46.846015+0800 test[12339:2077774] ClassA

[self class]被编译器转成objc_msgSend(self,@selector(class))的函数调用;
[super class]被编译器转成objc_msgSendSuper(super, @selector(class))的函数调用;
super是一个编译器关键字,在编译器中会转换成struct objc_super *结构体指针;
objc_super中的__unsafe_unretained id receiver 实际上就是当前对象,所以[super class]就相当于[self class]
⑤ 这就是两种调用输出值都为ClassA的原因。

  1. 缓存方法查找(深入理解Objective-C:方法缓存)
    缓存方法的存储使用了散列(hash)表,因为散列表检索起来更快,通过hash算法,查找目标函数指针
  2. 当前类中方法查找
    ① 对于已经排序好的列表,采用二分查找算法查找方法对应的执行函数;
    ② 对于没有排序的列表,采用一般遍历查找方法对应的执行函数
  3. 父类逐级查找
    父类逐级查找流程
  4. 消息转发(iOS消息转发机制实例iOS 消息转发机制)
    消息转发流程简图
  5. Method-Swizzling(方法混淆)
    交换两个方法的实现
+ (void)load{
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"touchesShouldCancelInContentView:"));
       Method method2 = class_getInstanceMethod([self class], @selector(touchesShouldCancelInContentViewSwizzle:));
       method_exchangeImplementations(method1, method2);
   });
}
- (BOOL)touchesShouldCancelInContentViewSwizzle:(UIView *)view{
   [self touchesShouldCancelInContentViewSwizzle:view];
   return YES;
}
Method-Swizzling

动态添加方法 (iOS performSelector 及相关方法)

  • performSelector:响应了OC语言的动态性:延迟到运行时才绑定方法。
    当我们在使用以下方法时:
[self performSelector:@selector(sureTestMethod)];
[self performSelector:@selector(sureTestMethod) withObject:params];
[self performSelector:@selector(sureTestMethod) withObject:params withObject:params2];

编译阶段并不会去检查方法是否有效存在,只会给出警告:
Undeclared selector ''

  • performSelector :可以同GCD、NSThread和NSOperation一样,在异步线程线程执行
    ① performSelectorInBackground 开启新的线程在后台执行test方法
    ② performSelector:onThread:在指定线程执行

动态方法解析

@dynamic 不需要编译器来为我们生成gettersetter方法,这样声明的属性,即使你没有实现setter与getter方法,编译时,是不会报错的;但是当你使用到setter与getter方法时(运行时),就会报错,所以就需要由我们来在运行时动态添加;

  • 动态运行时语言将函数决议推迟到运行时(运行时来确定方法的具体实现函数)
  • 编译时语言在编译期进行函数决议(编译时来确定方法的具体实现函数)

思考

[obj foo]objc_msgSend()函数之间有什么关系?
[obj foo]编译器处理后就变成了objc_msgSend()的函数调用,然后开始了runtime消息传递过程
runtime如何通过Selector找到对应IMP的地址的?
先查找当前类方法缓存,再查找当前类的方法列表,左后逐级查找父类的缓存及方法列表
能否向编译后的类中增加实例变量?(注意与关联对象的区分)
不可以的class_addIvar()方法只能为动态添加的类添加实例方法,因为编译后的类已经注册在runtime中,类结构体中的ivar_list_t实例变量的链表和instance_size实例变量的内存大小已经确定
能否向动态添加的类中增加实例变量?(动态添加类的示例)
可以的

   Class MyClass = objc_allocateClassPair([NSObject class], "MyClass", 0);
   BOOL isSuccess = class_addIvar(MyClass, "test", sizeof(NSString *), 0, "@");

编译时语言与OC这种运行时语言有什么区别?

  • 动态运行时语言将函数决议推迟到运行时(运行时来确定方法的具体实现函数)
  • 编译时语言在编译期进行函数决议(编译时来确定方法的具体实现函数)

消息传递与函数调用有什么区别?
C语言的函数调用在编译的时候决定调用那个函数。编译完之后直接顺序执行。
OC的消息发送是属于动态调用过程。在编译的时候决不能决定真正调用那个函数(在编译阶段,oc可以调用任何函数,而c语言在编译阶段会报错)

当我们调用一个没有实现的方法的时候,系统如何为我们实现消息转发过程的?

//首先调用:
+ (BOOL)resolveInstanceMethod:(SEL)sel;//实例方法
+ (BOOL)resolveClassMethod:(SEL)sel;//类方法
#pragma mark -
//上述返回NO后调用:
- (id)forwardingTargetForSelector:(SEL)aSelector;
#pragma mark -
//上述方法返回nil继续调用:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
//上诉方法返回NSMethodSignature *后调用下面方法
- (void)forwardInvocation:(NSInvocation *)anInvocation;

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

推荐阅读更多精彩内容