网站管理员权限设置权限设置,查房价的官方网站,wordpress微信登陆插件下载失败,怎么给自己做个网站Runtime
概念#xff1a;
Runtime是一套底层纯C语言API#xff0c;OC代码最终都会被编译器转化为运行时代码#xff0c;通过消息机制决定函数调用方式#xff0c;这也是OC作为动态语言使用的基础。Runtime的最大特征就是实现了OC语言的动态特性。
消息机制原理
在Objec…Runtime
概念
Runtime是一套底层纯C语言APIOC代码最终都会被编译器转化为运行时代码通过消息机制决定函数调用方式这也是OC作为动态语言使用的基础。Runtime的最大特征就是实现了OC语言的动态特性。
消息机制原理
在Object-C的语言中对象方法调用都是类似[receiver selector] 的形式其本质就是让对象在运行时发送消息的过程。
而方法调用[receiver selector] 分为两个过程:
编译阶段
[receiver selector] 方法被编译器转化分为两种情况:
不带参数的方法被编译为objc_msgSend(receiverselector)带参数的方法被编译为objc_msgSend(recevierselectororg1org2…)
运行时阶段
消息接收者recever寻找对应的selector,也分为两种情况:
接收者能找到对应的selector直接执行接收receiver对象的selector方法。接收者找不到对应的selector消息被转发或者临时向接收者添加这个selector对应的实现内容否则崩溃
总而言之:
OC调用方法[receiver selector]编译阶段确定了要向哪个接收者发送message消息但是接收者如何响应决定于运行时的判断。
重要概念
objc_msgSend
所有 Objective-C 方法调用在编译时都会转化为对 C 函数 objc_msgSend 的调用。objc_msgSend(receiverselector); 是 [receiver selector]; 对应的 C 函数。
Object对象
在 objc/runtime.h 中Object对象 被定义为指向 objc_object 结构体 的指针objc_object结构体 的数据结构如下
//runtime对objc_object结构体的定义
struct objc_object {Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};//id是一个指向objc_object结构体的指针即在Runtime中
typedef struct objc_object *id;//OC中的对象虽然没有明显的使用指针但是在OC代码被编译转化为C之后每个OC对象其实都是拥有一个isa指向对象的类的指针的Class(类)
在 objc/runtime.h 中Class类 被定义为指向 objc_class 结构体 的指针objc_class结构体 的数据结构如下
//runtime对objc_class结构体的定义
struct objc_class {Class _Nonnull isa; // objc_class 结构体的实例指针#if !__OBJC2__Class _Nullable super_class; // 指向父类的指针const char * _Nonnull name; // 类的名字long version; // 类的版本信息默认为 0long info; // 类的信息供运行期使用的一些位标识long instance_size; // 该类的实例变量大小;struct objc_ivar_list * _Nullable ivars; // 该类的实例变量列表struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定义的列表struct objc_cache * _Nonnull cache; // 方法缓存struct objc_protocol_list * _Nullable protocols; // 遵守的协议列表
#endif};//class是一个指向objc_class结构体的指针即在Runtime中
typedef struct objc_class *Class;
SEL (方法选择器)
typedef struct objc_selector *SEL;//Objective-C在编译时会依据每一个方法的名字、参数序列生成一个唯一的整型标识(Int类型的地址)这个标识就是SEL
1.不同类中相同名字的方法对应的方法选择器是相同的。 2.即使是同一个类中方法名相同而变量类型不同也会导致它们具有相同的方法选择器。
获取SEL有三种方法
1.OC中使用selector(“方法名字符串”)
2.OC中使用NSSelectorFromString(“方法名字符串”)
3.Runtime方法使用sel_registerName(“方法名字符串”)Method方法
在 objc/runtime.h 中Method方法 被定义为指向 objc_method 结构体 的指针在objct_class定义中看到methodLists其中的元素就是Method,objc_method结构体 的数据结构如下
struct objc_method {SEL _Nonnull method_name; // 方法名char * _Nullable method_types; // 方法类型IMP _Nonnull method_imp; // 方法实现
};//Method表示某个方法的类型
typedef struct objc_method *Method;Runtime消息转发
动态方法解析:动态添加方法
Runtime足够强大能够在运行时动态添加一个未实现的方法这个功能主要有两个应用场景
1. 动态添加未实现方法解决代码中因为方法未找到而报错的问题
2. 利用懒加载思路若一个类有很多个方法同时加载到内存中会耗费资源可以使用动态解析添加方法方法动态解析主要用到的方法如下
//OC方法
//类方法未找到时调起可于此添加类方法实现(BOOL)resolveClassMethod:(SEL)sel//实例方法未找到时调起可于此添加实例方法实现(BOOL)resolveInstanceMethod:(SEL)sel//Runtime方法
/**运行时方法向指定类中添加特定方法实现的操作param cls 被添加方法的类param name selector方法名param imp 指向实现方法的函数指针param types imp函数实现的返回值与参数类型return 添加方法是否成功*/
BOOL class_addMethod(Class _Nullable cls,SEL _Nonnull name,IMP _Nonnull imp,const char * _Nullable types)
解决方法无响应崩溃问题
执行OC方法其实就是一个发送消息的过程若方法未实现可以利用方法动态解析与消息转发来避免程序崩溃这主要涉及下面一个处理未实现消息的过程
在这个过程中可能还会使用到的方法有: 例子:
#import ViewController.h
#import objc/runtime.hinterface ViewController ()
endimplementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 执行 fun 函数[self performSelector:selector(fun)];
}// 重写 resolveInstanceMethod: 添加对象方法实现(BOOL)resolveInstanceMethod:(SEL)sel {if (sel selector(fun)) { // 如果是执行 fun 函数就动态解析指定新的 IMPclass_addMethod([self class], sel, (IMP)funMethod, v:);return YES;}return [super resolveInstanceMethod:sel];
}void funMethod(id obj, SEL _cmd) {NSLog(funMethod); //新的 fun 函数
}
end//日志输出:2019-09-01 23:24:34.9117740800 XKRuntimeKit[3064:521123] funMethod
从执行任务的输出日志中可以看到:
虽然没有实现 fun 方法但是通过重写 resolveInstanceMethod: 利用 class_addMethod 方法添加对象方法实现 funMethod 方法并执行。从打印结果来看成功调起了funMethod 方法。
消息接收者重定向
如果上一步中 resolveInstanceMethod:或者 resolveClassMethod: 没有添加其他函数实现运行时就会进行下一步消息接受者重定向。
如果当前对象实现了 -forwardingTargetForSelector:Runtime 就会调用这个方法允许将消息的接受者转发给其他对象,其主要方法如下:
//重定向类方法的消息接收者返回一个类
- (id)forwardingTargetForSelector:(SEL)aSelector//重定向实例方法的消息接受者返回一个实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector
例子:
#import ViewController.h
#import objc/runtime.hinterface Person : NSObject
- (void)fun;
endimplementation Person- (void)fun {NSLog(fun);
}
endimplementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 执行 fun 方法[self performSelector:selector(fun)];
} (BOOL)resolveInstanceMethod:(SEL)sel {return YES; // 为了进行下一步 消息接受者重定向
}// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector {if (aSelector selector(fun)) {return [[Person alloc] init];// 返回 Person 对象让 Person 对象接收这个消息}return [super forwardingTargetForSelector:aSelector];
}//日志输出:2019-09-01 23:24:34.9117740800 XKRuntimeKit[3064:521123] fun
从执行任务的输出日志中可以看到:
虽然当前 ViewController 没有实现 fun 方法resolveInstanceMethod: 也没有添加其他函数实现。 但是我们通过 forwardingTargetForSelector 把当前 ViewController 的方法转发给了 Person 对象去执行了。
通过forwardingTargetForSelector 可以修改消息的接收者该方法返回参数是一个对象如果这个对象是不是 nil也不是 self系统会将运行的消息转发给这个对象执行。否则继续进行下一步消息重定向流程
消息重定向
如果经过消息动态解析、消息接受者重定向Runtime 系统还是找不到相应的方法实现而无法响应消息Runtime 系统会利用 -methodSignatureForSelector: 方法获取函数的参数和返回值类型。
其过程
如果 -methodSignatureForSelector: 返回了一个 NSMethodSignature 对象函数签名Runtime 系统就会创建一个 NSInvocation 对象 并通过 -forwardInvocation: 消息通知当前对象给予此次消息发送最后一次寻找 IMP指向实现方法的函数指针 的机会。如果 -methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 -doesNotRecognizeSelector: 消息程序也就崩溃了。
所以可以在-forwardInvocation:方法中对消息进行转发。
其主要方法
// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation// 获取函数的参数和返回值类型返回签名
- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector;
例子:
#import ViewController.h
#import objc/runtime.hinterface Person : NSObject
- (void)fun;
endimplementation Person
- (void)fun {NSLog(fun);
}
endimplementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 执行 fun 函数[self performSelector:selector(fun)];
} (BOOL)resolveInstanceMethod:(SEL)sel {return YES; // 为了进行下一步 消息接受者重定向
}- (id)forwardingTargetForSelector:(SEL)aSelector {return nil; // 为了进行下一步 消息重定向
}// 获取函数的参数和返回值类型返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {if ([NSStringFromSelector(aSelector) isEqualToString:fun]) {return [NSMethodSignature signatureWithObjCTypes:v:];}return [super methodSignatureForSelector:aSelector];
}// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {SEL sel anInvocation.selector; // 从 anInvocation 中获取消息Person *p [[Person alloc] init];if([p respondsToSelector:sel]) { // 判断 Person 对象方法是否可以响应 sel[anInvocation invokeWithTarget:p]; // 若可以响应则将消息转发给其他对象处理} else {[self doesNotRecognizeSelector:sel]; // 若仍然无法响应则报错找不到响应方法}
}
end//日志输出:
2019-09-01 23:24:34.9117740800 XKRuntimeKit[30032:8724248] fun
从执行任务的输出日志中可以看到:
在 -forwardInvocation: 方法里面让 Person 对象去执行了 fun 函数
问既然 -forwardingTargetForSelector: 和 -forwardInvocation: 都可以将消息转发给其他对象处理那么两者的区别在哪
答区别就在于 -forwardingTargetForSelector: 只能将消息转发给一个对象。而 -forwardInvocation: 可以将消息转发给多个对象。
Runtime的应用
动态方法交换
实现动态方法交换(Method Swizzling )是Runtime中最具盛名的应用场景其原理是
通过Runtime获取到方法实现的地址进而动态交换两个方法的功能。
类目添加新的属性
在日常开发过程中常常会使用类目Category为一些已有的类扩展功能。虽然继承也能够为已有类增加新的方法而且相比类目更是具有增加属性的优势但是继承毕竟是一个重量级的操作添加不必要的继承关系无疑增加了代码的复杂度。
获取类详细属性
获取属性列表获取所有成员变量获取所有方法获取当前遵循的所有协议
解决同一方法高频率调用的效率问题
Runtime源码中的IMP作为函数指针指向方法的实现。通过它可以绕开发送消息的过程来提高函数调用的效率。当需要持续大量重复调用某个方法的时候会十分有用。
动态操作属性
修改私有属性改进iOS归档和解档实现字典与模型的转换
利用Runtime实现的思路大体如下:
借助Runtime可以动态获取成员列表的特性遍历模型中所有属性然后以获取到的属性名为key在JSON字典中寻找对应的值value再将每一个对应Value赋值给模型就完成了字典转模型的目的。
Swift中的Runtime
Swift是静态语言本身没有动态特性。
结论
对于纯Swift类来说没有动态特性。方法和属性不加任何修饰符的情况下这个时候已经不具备我们所谓的Runtime特性了。对于纯Swift类方法和属性添加objc标识的情况下当前我们可以通过Runtime API拿到但是在我们的OC中是没办法进行调度的。对于继承自NSObject类来说如果我们想要动态的获取当前的属性和方法必须在其声明前添加objc关键字方法交换需要添加 dynamic 标识否则也是无法通过Runtime API获取的。
反射
反射是Swift中动态获取的一种方法可以动态获取类型、成员信息在运行时可以调用方法、属性等行为的特性。上面的结论说了对于一个纯Swift类来说并不支持像OC那样操作但是Swift标准库依然提供了反射机制让我们访问成员信息。
用法如下
import UIKit//下方OC的部分可以不加没问题
class LGTeacher: NSObject{objc var age: Int 18objc dynamic func teach(){print(teach)}
}let t LGTeacher()let mirror Mirror(reflecting: t.self)
for pro in mirror.children{print(\(pro.label):\(pro.value))
}
运行结果
Optional(age):18