iOS 底层 day16 runtime的使用 runtimeA
2020-09-08 本文已影响0人
望穿秋水小作坊
一、什么是 Runtime?平时项目中有用过么?
1. 什么是 Runtime?
- Objective-C 是一门
动态性
比较强的编程语言,允许把很多操作都推迟到运行时决定。 - Objective-C 的
动态性
就是由Runtime
来支撑和实现的,Runtime
是一套C 语言封装的 API
,封装了很多动态性
相关的函数。
2. 平时项目中有用过么(四点)?
- 使用
objc_setAssociatedObject
关联对象技术,动态为分类添加属性 - 使用
class_copyIvarList
遍历类的成员变量,①字典转模型 ②自动归档解档 ③寻找系统空间的隐藏属性,从而修改展示(比如拿到 textfield 的 placeholder所对应的 label) - 使用
method_exchangeImplementations
交换方法实现,用于替换一些系统的默认实现 - 使用
objc_msgSend
消息转发机制,可以用于解决平时方法找不到,需要动态生成方法的情况
二、RuntimeAPI 的具体使用例子
1. 使用 objc_setAssociatedObject
关联对象技术,动态为分类添加属性
2. 字典转模型简化版的实现
- 创建
NSObject
的分类,实现如下代码
#import "NSObject+YYJsonToModel.h"
#import <objc/runtime.h>
@implementation NSObject (YYJsonToModel)
-(void)yy_modelFromJson:(NSDictionary *)dic {
unsigned int ivarCount;
Ivar *ivars = class_copyIvarList([self class], &ivarCount);
for (int i = 0 ; i < ivarCount; i++) {
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivars[i])];
if ([name hasPrefix:@"_"]) {
name = [[name substringFromIndex:1] mutableCopy];
}
[self setValue:dic[name] forKey:name];
}
free(ivars);
}
@end
- 使用如下:
int main(int argc, const char * argv[]) {
NSDictionary *dic = @{
@"name":@"LYY",
@"age":@10,
@"height":@170
};
YYPerson *person = [[YYPerson alloc] init];
[person yy_modelFromJson:dic];
NSLog(@"断点");
return 0;
}
- 这样我们就实现了一个简单的
Json
转Model
的功能 - 如果要达到库的标准,还需要非常多的细节考虑和增加扩展性(比如继承、类嵌套等)
3. 自动归档、自动解档(NSSecureCoding)
- 代码实现
#import "YYPerson.h"
#import <objc/runtime.h>
@implementation YYPerson
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
if (self = [super init]) {
Class cls = self.class;
// 截取类和父类的成员变量
while (cls && cls != [NSObject class]) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
for (int i = 0; i < ivarCount; i++) {
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
id value = [coder decodeObjectForKey:key];
if (value) { // 增加容错
[self setValue:value forKey:key];
}
}
// 获得c的父类
cls = [cls superclass];
free(ivars);
}
}
return self;
}
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
Class cls = self.class;
// 截取类和父类的成员变量
while (cls && cls != [NSObject class]) {
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
for (int i = 0; i < ivarCount; i++) {
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
id value = [self valueForKey:key];
if (value) { // 增加容错
[coder encodeObject:value forKey:key];
}
}
cls = [cls superclass];
// 释放内存
free(ivars);
}
}
+ (BOOL)supportsSecureCoding{
return YES;
}
@end
- 代码调用(NSKeyedArchiver)
int main(int argc, const char * argv[]) {
YYPerson *person = [[YYPerson alloc] init];
person.name = @"LYY";
person.age = 10;
person.height = 180;
NSLog(@"person: %@",person);
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:nil];
NSLog(@"data: %@",data);
YYPerson *decodePerson = [NSKeyedUnarchiver unarchivedObjectOfClass:[YYPerson class] fromData:data error:nil];
NSLog(@"decodePerson: %@",decodePerson);
return 0;
}
-
iOS6中,苹果引入了一个新的协议,是基于NSCoding的,叫做NSSecureCoding。NSSecureCoding和NSCoding是一样的,除了在解码时要同时指定key和要解码的对象的类,如果要求的类和从文件中解码出的对象的类不匹配,NSCoder会抛出异常,告诉你数据已经被篡改了。大部分支持NSCoding的系统对象都已经升级到支持NSSecureCoding了
-
NSKeyedArchiver
就是用来存储对象数据到本地,即归档 -
NSKeyedUnarchiver
,负责从本地存储还原对象数据,即反归档 -
同时需要配合
NSCoding
使用,实现序列化以及反序列化 -
存储的文件本质是一个
plist
文件
三、RuntimeAPI(常用)
1. RuntimeAPI - 类
- 动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name,
size_t extraBytes)
- 注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class _Nonnull cls)
- 获取 isa 指向的 Class、设置 isa 指向的 Class
Class object_getClass(id _Nullable obj)
Class object_setClass(id _Nullable obj, Class _Nonnull cls)
- 判断一个 OC 对象是否为 Class
BOOL object_isClass(id _Nullable obj)
- 判断一个 OC 对象是否为元类
BOOL class_isMetaClass(id _Nullable obj)
- 获取父类
Class class_getSuperclass(Class _Nullable cls)
2. RuntimeAPI - 成员变量
- 获取一个实例变量信息
Ivar class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
- 拷贝实例变量列表(最后需要调用 free 释放)
Ivar *class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)
- 设置和获取成员变量的值
void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value)
id object_getIvar(id _Nullable obj, Ivar _Nonnull ivar)
- 动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size,
uint8_t alignment, const char * _Nullable types)
- 获取成员变量的相关信息
const char * ivar_getName(Ivar _Nonnull v)
const char * ivar_getTypeEncoding(Ivar _Nonnull v)
3. RuntimeAPI - 属性
- 拷贝属性列表(最后需要调用 free 释放)
objc_property_t *class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
- 动态添加属性
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,
const objc_property_attribute_t * _Nullable attributes,
unsigned int attributeCount)
4. RuntimeAPI - 方法
- 获得一个实例方法、类方法
Ivar class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
Ivar class_getClassVariable(Class _Nullable cls, const char * _Nonnull name)
- 方法实现相关操作
IMP class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name)
IMP method_setImplementation(Method _Nonnull m, IMP _Nonnull imp)
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
- 动态添加方法
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
- 动态替换方法
IMP class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
- 获取方法的相关信息(带有 copy 的需要调用 free 释放)
SEL method_getName(Method _Nonnull m)
IMP method_getImplementation(Method _Nonnull m)
const char * method_getTypeEncoding(Method _Nonnull m)
- 选择器相关
const char * _Nonnull sel_getName(SEL _Nonnull sel)
SEL sel_registerName(const char * _Nonnull str)
- 用 block 作为方法实现
IMP imp_implementationWithBlock(id _Nonnull block)
id imp_getBlock(IMP _Nonnull anImp)
BOOL imp_removeBlock(IMP _Nonnull anImp)
四、RuntimeAPI 的一些练习
1. 动态创建一个 YYCat
类,为它添加 name
、 weight
两个成员变量。再添加一个 print
方法,然后创建实例,分别为 name
、 weight
赋值再将它们打印出来,调用方法 print
。
int main(int argc, const char * argv[]) {
Class YYCat = objc_allocateClassPair([NSObject class], "YYCat", 0);
class_addIvar(YYCat, "_name", 8, 1, @encode(NSString*));
class_addIvar(YYCat, "_age", 4, 1, @encode(int));
IMP catRunImp = imp_implementationWithBlock(^{
printf("cat run run: %s\n", __func__);
});
class_addMethod(YYCat, @selector(run), catRunImp, "v");
objc_registerClassPair(YYCat);
id cat = class_createInstance(YYCat, 0);
[cat setValue:[NSString stringWithFormat:@"Mimi"] forKey:@"_name"];
[cat setValue:@3 forKey:@"_age"];
[cat run];
NSLog(@"cat name is %@, age is %d", [cat valueForKey:@"_name"], [[cat valueForKey:@"_age"] intValue]);
NSLog(@"断点观察");
return 0;
}
2. 将 YYPerson
中 run
的方法实现 和 YYDog
中 eat
的方法实现 进行交换
int main(int argc, const char * argv[]) {
YYPerson *person = [[YYPerson alloc] init];
YYDog *dog = [[YYDog alloc] init];
Method personRun = class_getInstanceMethod([YYPerson class], @selector(run));
Method dogEat = class_getInstanceMethod([YYDog class], @selector(eat));
method_exchangeImplementations(personRun, dogEat);
[person run];
[dog eat];
return 0;
}
-
class_getInstanceMethod
获取实例方法 -
class_getClassMethod
获取类方法
四、源码和本质追踪的时候的一些思路
1. 我们在研究OC 底层实现的时候,一般有三种思路
- ① 转成 C++ 代码(有一点误差 ,99%正确)
- ② 查看汇编(汇编可以断点查看,也可以用 Xocde 的 assembly 功能 )
- ③ 查看源码