Runtime的特性是消息/方法的传递,如果消息/方法在对象中找不到,就进行转发。是对获取Objective-C运行时和Objective-C根类底层的访问。
都知道Objective-C是C语言的一门衍生语言,而Runtime的核心就是C。C语言在函数调用的时候就会决定用哪个函数,如果调用了未实现的函数就会报错。然而Objective-C是一门动态语言,即在动态调用过程(运行时)编译器才会决定调用哪个函数。当调用的某对象为被实现时,可以通过”消息转发”进行解决,即编译截断。
下面讲讲Runtime的作用:
动态交换(类方法)
Method Swizzling(方法交换):本质上就是对IMP和SEL进行交换。是发生在运行时的,主要用于将两个Method进行交换。需要注意的是只有在这段Method Swizzling代码执行完毕之后互换才起作用。
IMP:指向方法实现开始的指针。
1
2 > id (*IMP)(id, SEL, ...)
>此数据类型是指向实现该方法的函数的开头指针。此函数使用为当前CPU体系结构实现的标准C调用约定。第一个参数是指向
self
的指针(即此类的特定实例的内存
或者对应的方法
,指向元类的指针)。第二个参数是方法选择器。
SEL:类成员方法的指针,但不同于C语言的函数指针。函数指针直接保存了方法第地址,但SEL只是方法编码。
1
2 > typedef struct objc_selector *SEL;
>方法选择器用于表示运行时方法的名称。是已使用Objective-C运行时注册的
C字符串
。加载类时编译器生成选择器将由运行时自动映射。
借用网上的两张图了表达Method Swizzling的原理再好不过了。(有侵权请联系博主谢谢!)
从图中不难看出SEL和IMP都是在Dispatch Table这个表中的,也就是说SEL最后还是要通过Dispatch Table来寻找对应的IMP后在执行。
具体实现直接看代码:
1 | + (void)load { |
- 注意的是在自己定义的方法里[self swizzlingViewDidLoad]的作用其实是在调用原方。因为自己定义的方法是在交换代码结束后才开始调用的,也就是在调用自定义的方法时[self swizzlingViewDidLoad]中的
swizzlingViewDidLoad
方法其实是相当于原方法了。
为分类添加属性
该功能请转至Category & Extension
在此补充下objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy)
中objc_AssociationPolicy policy
1 | policy:关联策略。有五种关联策略。 |
获取类中所有的变量和方法
1 | - (void)getProperties { |
实现NSCoding的自动归档和自动解档
首页得了解什么是归档
和解档
归档(序列化 持久化):就是把需要存储的对象的数据存储到沙盒的Documents目录下的文件中,即存储到磁盘上,实现数据的持久化存储和备份。
解档(接档 反序列化):从磁盘上读取归档下的文件数据,用来完成用户的需求。
首先用代码简单讲解下归档和解档的实现
1.创建一个Model类并添加NSSecureCoding协议
1 | // .h 文件 |
2.ViewController.m文件下
归档
1 | // 注释掉的代码为iOS 12之前的实现方式 iOS 12之后以弃用了改方法 |
解档
1 | // 注释掉的代码为iOS 12之前的实现方式 iOS 12之后以弃用了改方法 |
以上是归解档的简单实现方式,接下来了解下
Runtime
的自动归档和自动解档.
如果还是拿上面的例子来修改的话其实只要改变Model类.m文件下的代码即可(记得添加#import <objc/runtime.h>
)
归档
1 | - (void)encodeWithCoder:(NSCoder *)aCoder { |
解档
1 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { |
其实从代码中就可以看出其实就是通过Runtime
的Ivar
来获取成员变量的列表从而使用for循环把成员变量自动添加到归档文件当中,而不用繁琐的一个个添加。
动态添加方法
用意:当硬件内存过小的时候,如果我们将每个方法都直接加载到内存当中,但是却极少的使用这就造成了内存的浪费。像懒加载
就很好的避开了这个问题,只有当被用到时才会被加载。
都知道调用一个未实现的方法必然会报错奔溃 如
1 | // FourLines类中未实现`draw:`方法 |
当利用
- (id)performSelector:(SEL)aSelector withObject:(id)object;
1.调用一个未实现的对象方法
时,就会调用+(BOOL)resolveInstanceMethod:(SEL)sel
方法2.调用一个未实现的
类方法
时,就会调用+(BOOL)resolveClassMethod:(SEL)sel
方法
因为我们需要FourLines类中添加如下代码
首选添加#import <objc/message.h>
1 | // 因为调用的是一个对象方法 所以添加 + (BOOL)resolveInstanceMethod:(SEL)sel 方法 |
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,const char * _Nullable types)
cls : 表示给哪个类添加方法
SEL _Nonnull name : SEL即方法编号 name 指的应该是方法名称 因为有判断是sel方法 所以对应输入 sel
IMP _Nonnull imp : 表示方法的实现 具体应该是值 SEL 所指定的 IMP 方法的实现。
const char * _Nullable types : 方法类型。如果有参数的方法需要对方法的实现和class_addMethod方法内的方法类型参数做一些修改。
实现字典和模型的自动转换
说到字典和模型的转换很贴切的例子就是在数据请求后的JSON转Model了,因为这个过其实就是字典转模型的过程。直接代码讲解:
首选来实现一个简单的GET请求
1 | 在ViewController中添加一个NSMutableData类型的实例变量,并添加<NSURLSessionDataDelegate>协议 |
实现
1 | // 请求的是一个天气预报的API |
接下来设置Model类
1 | // 添加两个类方法 来对NSDictionary进行类型转换 |
1 | // 实现 |
重写- (NSString *)description;
方法
1 | - (NSString *)description { |
打印输出NSLog(@”lines = %@”,lines.weatherinfo);
1 | 2019-05-12 19:34:27.705523+0800 Test[2666:406683] lines = { |
返回的JSON格式还有模型套模型,模型中的数组中装着模型。在此就讲解最简单的一种,后期在讲解数据请求时在来细致讨论。
其实
Runtime
实现字典和模型的转换,其实跟自动归解档原理上都是很像是的。都是利用Runtime
的Ivar
来获取成员变量列表,在利用for寻来来自动设置value和key,从而实现自动的转换。