动态运行时(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;

你可能感兴趣的:(动态运行时(RunTime))