RunTime

作者: 题小记 | 来源:发表于2020-02-23 11:39 被阅读0次

一、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");
}

相关文章

网友评论

      本文标题:RunTime

      本文链接:https://www.haomeiwen.com/subject/pacfzftx.html