runtime 的概念和使用场景
一、runtime 是什么
- 运行时(Runtime)是指将数据类型的确定由编译时推迟到了运行时
- Runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API
- 平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代码,Runtime是Object-C的幕后工作者
- Object-C需要Runtime来创建类和对象,进行消息发送和转发
二、runtime 的使用场景
- 给系统分类添加属性、方法
- 方法交换
- 获取对象的属性、私有属性
- 字典转换模型
- KVC、KVO
- 归档(编码、解码)
- NSClassFromString class<->字符串
- block
- 类的自我检测
1.0、给对象的增加属性
1.1为什么不能再分类中直接增加成员变量?这里我们可以查看分类的实现。
//Category表示一个结构体指针的类型
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
结果:可以看到,实现的结构体指针中有 类名、类、实例方法列表、类方法列表、协议列表、属性列表。但是却没有
const struct _ivar_list_t *ivars;
在分类中添加属性,不能生成成员变量,以及生成Geter/Seter方法,需要自己手动去实现GET/SET方法。然后利用关联对象去设置属性的值,但是却还是没有生成对应成员变量。
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person (Name)
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
#import "Person+Name.h"
#import
static NSString *nameKey = @"name";
@implementation Person (Name)
- (void)setName:(NSString *)name{
/**
* @param object 关联的源对象
* @param key 关联的Key
* @param value 关联的值
* @param policy 关联策略
* objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy);
*/
objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
/**
* @param object 关联的源对象
* @param key 关联的Key
* objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key);
*/
return objc_getAssociatedObject(self, &nameKey);
}
@****end****
1.2 给对象增加方法
//viewController 中p对象调用未实现方法eat
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
Person *p = [[Person alloc]init];
//调用未实现的方法
[p performSelector:@selector(eat)];
}
#import "Person.h"
#import
void eat(id self ,SEL _cmd)
{
// 实现内容
NSLog(@"%@的%@方法动态实现了",self,NSStringFromSelector(_cmd));
}
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel{
/*
NSString *sel = [NSStringFromSelector(sel); //将SEL 数据转换成字符串
第一个参数: cls:给哪个类添加方法
第二个参数: SEL name:添加方法的编号
第三个参数: IMP imp: 方法的实现,函数入口,函数名可与方法名不同(建议与方法名相同)
第四个参数: types :方法类型(函数的返回值类型,参数类型),需要用特定符号,参考API
v -> void 表示无返回值
@ -> object 表示id参数
: -> method selector 表示SEL
*/
if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
class_addMethod(self, sel, (IMP)eat, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
2.0 方法交换
[图片上传失败...(image-eb8338-1589277849589)]
/*
实例方法交换
*/
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method origleMe = class_getInstanceMethod([self class], @selector(viewWillAppear:)); // 如果子类没有实现 这个获取到的是superClass的方法
Method newMe = class_getInstanceMethod([self class], @selector(my_viewWillAppear:));
// 这个方法 往类里面添加 方法
//1.如果类没有实现这个方法,class_addMethod给类添加一个方法,方法的选择器还是它本身,方法的实现和参数类型都是要替换的新的方法,这种情况返回的bool是YES,在调用class_replaceMethod替换新增的方法的实现为继承的superclass的方法实现,
// 疑问1 class_addMethod 如果添加成功,添加的方法的实现其实是新增的方法的实现,class_replaceMethod 替换的时候获取方法IMP时候应该用最开始获取的method,如果不这样有可能用,再次获取Method那么class_replaceMethod替换的还是新增的方法,相当于系统的和新的方法的实现都是新增的实现 验证 正确 ,如果这样写 会循环调用
//2.如果类里面以及有这个方法,class_addMethod添加失败,直接交换两个方法的实现即可
BOOL isSuccess = class_addMethod([self class], @selector(viewWillAppear:), method_getImplementation(newMe), method_getTypeEncoding(newMe));
// 测试 疑问1
// Method aOrigleMe = class_getInstanceMethod([self class], @selector(viewWillAppear:));
if (isSuccess) {
class_replaceMethod([self class], method_getName(newMe), method_getImplementation(origleMe), method_getTypeEncoding(origleMe));
// 测试 疑问1
// class_replaceMethod([self class], method_getName(newMe), method_getImplementation(aOrigleMe), method_getTypeEncoding(aOrigleMe));
} else {
method_exchangeImplementations(origleMe, newMe);
}
});
}
- (void)my_viewWillAppear:(BOOL)animated
{
[self my_viewWillAppear:animated];
// [self viewWillAppear:animated];
NSLog(@"%s",__func__);
}
/*
类方法交换
*/
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 这个应为要获取类方法,所以需要获取到元类对象
// 因为实例方法存放在类对象里面,类方法存放在元类对象里面
Class aClass = object_getClass(self);
SEL orgiSel = @selector(testOrgiClassMethod);
SEL newSel = @selector(testNewClassMethod);
Method oriClassMe = class_getClassMethod(aClass, orgiSel);
Method newClassMe = class_getClassMethod(aClass, newSel);
BOOL isSuccess = class_addMethod(aClass, orgiSel, method_getImplementation(newClassMe), method_getTypeEncoding(newClassMe));
if (isSuccess) {
class_replaceMethod(aClass, newSel, method_getImplementation(oriClassMe), method_getTypeEncoding(oriClassMe));
} else {
method_exchangeImplementations(oriClassMe, newClassMe);
}
});
}
+ (void)testOrgiClassMethod
{
NSLog(@"%s",__func__);
}
+ (void)testNewClassMethod
{
NSLog(@"%s",__func__);
}
这里需要注意实例方法和类方法交换的不同点,获取类,和获取方法实现(IMP)
3.0 获取对象的属性、私有属性
//获取UIPageControl 属性
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([UIPageControl class], &count);
for (unsigned int i=0; i
//打印属性
2020-05-12 13:37:49.477157+0800 Business[8658:654027] name = hash
2020-05-12 13:37:49.477234+0800 Business[8658:654027] name = superclass
2020-05-12 13:37:49.477294+0800 Business[8658:654027] name = description
2020-05-12 13:37:49.477346+0800 Business[8658:654027] name = debugDescription
2020-05-12 13:37:49.477398+0800 Business[8658:654027] name = legibilityStyle
2020-05-12 13:37:49.477458+0800 Business[8658:654027] name = legibilitySettings
2020-05-12 13:37:49.477514+0800 Business[8658:654027] name = numberOfPages
2020-05-12 13:37:49.477564+0800 Business[8658:654027] name = currentPage
2020-05-12 13:37:49.477616+0800 Business[8658:654027] name = hidesForSinglePage
2020-05-12 13:37:49.477666+0800 Business[8658:654027] name = defersCurrentPageDisplay
2020-05-12 13:37:49.477715+0800 Business[8658:654027] name = pageIndicatorTintColor
2020-05-12 13:37:49.477758+0800 Business[8658:654027] name = currentPageIndicatorTintColor
如果要获取私有私有属性的话,方法类似,只不过改成获取成员变量列表了
// 获取成员变量数组
unsigned int count;
Ivar *ivars = class_copyIvarList(cla, &outCount);
for (unsigned int i=0; i
4.0 字典转模型
4.1 简单的Dictionary
NSDictionary *dictionary = @{
@"name" : @"Xiaoming",
@"age" : @18,
@"sex" : @"男"
};
#import "NSObject+Model.h"
#import
@implementation NSObject (Model)
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary{
NSObject *object = [[self alloc] init];
[object configModelWithDictionary:dictionary];
return object;
}
- (void)configModelWithDictionary:(NSDictionary *)dictionary{
Class cls = [self class];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
for (unsigned int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
//获取成员变量的名字
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
//去除下划线
key = [key substringFromIndex:1];
// 取出字典的值
id value = dictionary[key];
if (value == nil) continue;
// 利用KVC将字典中的值设置到模型上
[self setValue:value forKeyPath:key];
}
//释放指针
free(ivars);
}
@end
NSDictionary *dictionary = @{
@"name" : @"Xiaoming",
@"age" : @18,
@"sex" : @"男"
};
Person *p = [Person modelWithDictionary:dictionary];
NSLog(@"p.name = %@",p.name);
NSLog(@"p.nageame = %@",p.age);
NSLog(@"p.sex = %@",p.sex);
2020-05-12 14:09:00.503762+0800 Business[9629:761184] p.name = Xiaoming
2020-05-12 14:09:00.503856+0800 Business[9629:761184] p.nageame = 18
2020-05-12 14:09:00.503930+0800 Business[9629:761184] p.sex = 男
4.2 字典中嵌套字典
NSDictionary *dictionary = @{
@"name" : @"Xiaoming",
@"age" : @18,
@"sex" : @"男",
@"school" : @{
@"name" : @"海淀一中",
@"address" : @"海淀区",
@"grade" : @{
@"name" : @"九年级",
@"teacher" : @"Mr Li"
}
}
};
//创建Model Person.h
#import
NS_ASSUME_NONNULL_BEGIN
@class School;
@class Grade;
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, strong) School *school;
@end
@interface School : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *address;
@property (nonatomic, strong) Grade *grade;
@end
@interface Grade : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *teacher;
@end
NS_ASSUME_NONNULL_END
//Person.m
@implementation Person
@end
@implementation School
@end
@implementation Grade
@end
#import "NSObject+Model.h"
#import
@implementation NSObject (Model)
+ (instancetype)modelWithDictionary:(NSDictionary *)dictionary{
NSObject *object = [[self alloc] init];
[object configModelWithDictionary:dictionary];
return object;
}
- (void)configModelWithDictionary:(NSDictionary *)dictionary{
Class cls = [self class];
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
for (unsigned int i = 0; i < count; i ++) {
Ivar ivar = ivars[i];
//获取成员变量的名字
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
//去除下划线
key = [key substringFromIndex:1];
// 取出字典的值
id value = dictionary[key];
if (value == nil) continue;
// 利用KVC将字典中的值设置到模型上
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
/*
NSLog(@"type = %@",type);
2020-05-12 15:04:58.817721+0800 Business[11356:937324] type = @"NSString"
2020-05-12 15:04:58.817830+0800 Business[11356:937324] type = @"NSString"
2020-05-12 15:04:58.817905+0800 Business[11356:937324] type = @"NSNumber"
这就要去除@"",取得NSString、NSNumber
*/
// 如果属性是对象类型(字典)
NSRange range = [type rangeOfString:@"@"];
if (range.location != NSNotFound) {
// 那么截取对象的名字(比如@"School",截取为School)
type = [type substringWithRange:NSMakeRange(2, type.length - 3)];
// 排除系统的对象类型(如果人为的设置自定义的类带@”NS“如:NSSchool,则会出现错误)
if (![type hasPrefix:@"NS"]) {//字典
//将对象名转换为对象的类型,将新的对象字典转模型(递归),如Grade,并将其对象grade对应的字典转换成模型
Class class = NSClassFromString(type);
value = [class modelWithDictionary:value];
}
}
[self setValue:value forKeyPath:key];
}
//释放指针
free(ivars);
}
@end
NSDictionary *dictionary = @{
@"name" : @"Xiaoming",
@"age" : @18,
@"sex" : @"男",
@"school" : @{
@"name" : @"海淀一中",
@"address" : @"海淀区",
@"grade" : @{
@"name" : @"九年级",
@"teacher" : @"Mr Li"
}
}
};
Person *p = [Person modelWithDictionary:dictionary];
NSLog(@"p.name = %@",p.name);
NSLog(@"p.nageame = %@",p.age);
NSLog(@"p.sex = %@",p.sex);
School *school = p.school;
NSLog(@"school.name = %@",school.name);
NSLog(@"school.nageame = %@",school.address);
Grade *grade = school.grade;
NSLog(@"grade.name = %@",grade.name);
NSLog(@"grade.teacher = %@",grade.teacher);
//打印结果
2020-05-12 15:04:58.818413+0800 Business[11356:937324] p.name = Xiaoming
2020-05-12 15:04:58.818483+0800 Business[11356:937324] p.nageame = 18
2020-05-12 15:04:58.818545+0800 Business[11356:937324] p.sex = 男
2020-05-12 15:04:58.818600+0800 Business[11356:937324] p.isMarray = 1
2020-05-12 15:04:58.818641+0800 Business[11356:937324] school.name = 海淀一中
2020-05-12 15:04:58.818695+0800 Business[11356:937324] school.nageame = 海淀区
2020-05-12 15:04:58.818749+0800 Business[11356:937324] grade.name = 九年级
2020-05-12 15:04:58.818798+0800 Business[11356:937324] grade.teacher = Mr Li
5、KVO、KVC
5.1 KVC 的原理
KVC 原理剖析
5.2 如何手动实现KVO
Glow技术团队-如何自己动手实现 KVO