一、Runtime简介
Runtime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制,代码在程序运行过程中都会被转化成Runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething));,向消息接收者发送一个消息,Runtime会根据消息接收者是否能响应该消息而做出不同的反应。
OC中的类被实例化之后,在Runtime中用结构体表示。
typedef struct objc_class *Class;
struct objc_class {
Class isa; // //isa指针,实例的isa指向类对象,类对象的isa指向元类
Class super_class ; // 指向其父类 如果为根类指向NULL
const char *name ; // 类名
long version ;
long info;
long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
struct objc_method_list **methodLists ; // 方法列表;
struct objc_cache *cache; // 方法缓存
struct objc_protocol_list *protocols; // 协议链表
}
Demo中对这些重要参数进行了获取,详情可以参考
二、Swift和OC关于Runtime的区别
1.Swift 没有运行时特性,它是一门静态语言。
2.因为OC可以和Swift混编,可以互相调用对应的方法,所以可以用Swift调用OC的Runtime接口
3.Swift只是调用的OC的Runtime的接口间接拥有了运行时的特性
三、Runtime的使用
1.消息转发
isa 指针:是一个Class 类型的指针. 每个实例对象有个isa的指针,指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身
在objc_msgSend函数中。首先通过target的isa指针找到target对应的class。在Class中先去cache中 通过SEL查找对应函数method,若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加 入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
概括:实现方法时先在自身的方法列表寻找对应的方法,找到实现,如果没找到去父类的方法列表查找,直到查找到根类,如果没找到会转向拦截调用,如果没有实现拦截调用,就会报错。
针对这一特性,遇到unrecognized selector sent to instance 的错误之前可以转定向拦截 进行消息转发 防止崩溃
消息转发分为三个步骤
1、动态方法解析
2、备用接收者
3、完整转发
当前一步骤没有实现的时候可以在下一步骤拦截
//没有实现对应的类方法时调用
+ (BOOL)resolveClassMethod:(SEL)sel;
//没有实现对应的实例方法调用
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//调用其他类实现对应的方法
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
(1)动态方法解析(类方法和实例方法使用方式类似)
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//动态添加一个实例方法
if ([NSStringFromSelector(sel) isEqualToString:@"think"]) {
class_addMethod(self, sel, (IMP)newThink, "v@:*");
}
return YES;
}
void newThink(id self, SEL _cmd, NSString *string){
NSLog(@"Cat is thingking");
}
//没有实现对应的类方法时调用 使用方法跟实例方法类似
+(BOOL)resolveClassMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"think"]) {
class_addMethod(self, sel, (IMP)newThink, "v@:*");
}
return YES;
}
(2)备用接收者
//Cat 类没有实现think方法 在Person类实现think方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"think"]) {
return [[Person alloc]init];
}
return nil;
}
(3)完整转发
// 先通过指定方法签名,若返回nil,则表示不处理。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSString *selName = NSStringFromSelector(aSelector);
if ([selName isEqualToString:@"think"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
//在Person类实现think方法
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([NSStringFromSelector(anInvocation.selector) isEqualToString:@"think"]) {
Person *p = [[Person alloc]init];
[anInvocation invokeWithTarget:p];
}
}
2.关联对象
typedef struct objc_category *Category
struct objc_category{
char *category_name OBJC2_UNAVAILABLE; // 类别名
char *class_name OBJC2_UNAVAILABLE; // 类别所属的类名
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 类别实例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类别类方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 类别所实现的协议列表
}
这是类别再runtime中的结构体表示,其中instance_methods列表是元类实例方法列表的子集,而class_methods列表是元类方法列表的一个子集。但是类别中没有ivar成员变量指针,所以类别不能添加属性,当我们想使用系统的某些类,并想为之添加属性的时候我们可以用到关联对象的方式添加属性(OC中是category Swift中是Extension)比如我们想给UIbutton添加一个handle 具体实现在参考Demo
//添加关联
void objc_setAssociatedObject(id object, const void *key, id value, objc _AssociationPolicy policy)
//获取
id objc_getAssociatedObject(id object, const void *key)
//Swift
func SwiftaddtouchHandle(handle:clickBlock) {
objc_setAssociatedObject(self, &blockKey, handle, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
self.addTarget(self, action: #selector(clicked), for: UIControlEvents.touchUpInside)
}
//OC
- (void)addOChandle:(dispatch_block_t)handle
{
objc_setAssociatedObject(self, &"myBlock", handle, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(blockEvent:)forControlEvents:UIControlEventTouchUpInside];
}
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy){
OBJC_ASSOCIATION_ASSIGN = 0, // 表示弱引用关联,通常是基本数据类型
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 表示强引用关联对象,是线程安全的
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // 表示关联对象copy,是线程安全的
OBJC_ASSOCIATION_RETAIN = 01401, // 表示强引用关联对象,不是线程安全的
OBJC_ASSOCIATION_COPY = 01403 // 表示关联对象copy,不是线程安全的
};
3.自动归档,字典模型互转
(1)自动归档
当我们需要对对象进行归档时 需要重写 encodeWithCoder方法,如果属性过多的时候,原本的[aCoder encodeObject:self.name forKey:@"name"];特别不方便使用这时候可以使用Runtime获取所有的属性。进行归档
- (void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int outCount = 0;
Ivar *vars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar var = vars[i];
const char *name = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
}
return self;
}
(2)字典转模型
思路类似归档,Runtime可以获取所有模型的所有属性,遍历属性进行赋值
//获取所有的属性
+ (NSArray *)getAllproperty
{
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i< count; i++) {
//获取属性
objc_property_t property = propertyList[i];
//获取属性名称
const char *cName = property_getName(property);
NSString *propertyname = [[NSString alloc]initWithUTF8String:cName];
//添加到数组中
[arr addObject:propertyname];
}
return arr.copy;
}
+ (instancetype)modelserializeWithDic:(NSDictionary *)dic
{
id cat = [self new];
// 遍历属性数组
NSArray *propretyList = [self getAllproperty];
for (NSString *property in propretyList) {
// 判断字典中是否包含这个key 如果有就进行赋值操作
if (dic[property]) {
[cat setValue:dic[property] forKey:property];
}
}
return cat;
}
4.方法交换
所谓方法交换就是指 有A B两个方法 交换方法后 调用A的方法 实际执行的是B的代码 反之亦然 详情参见Demo
+ (void)swizzlingMehod{
SEL eatSel = @selector(eat);
SEL runSel = @selector(run);
Method eatMethod = class_getInstanceMethod([self class], eatSel);
Method runMethod = class_getInstanceMethod([self class], runSel);
method_exchangeImplementations(eatMethod, runMethod);
}
- (void)eat {
NSLog(@"eat");
}
- (void)run {
NSLog(@"run");
}
网友评论