iOS-Runtime机制详解

时间:2023-03-09 12:51:29
iOS-Runtime机制详解

一、简介

  runtime是一套底层的纯c语言的API,我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething))

二、类在runtime中的表示(可以使用command+shiift+o搜索runtime.h查看)

struct objc_class {
    Class isa;//指针,顾名思义,表示是一个什么,
    //实例的isa指向类对象,类对象的isa指向元类

#if !__OBJC2__
    Class super_class;  //指向父类
    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 //协议列表
    #endif
} OBJC2_UNAVAILABLE;
///相关的定义
/// 描述类中的一个方法
typedef struct objc_method *Method;

/// 实例变量
typedef struct objc_ivar *Ivar;

/// 类别Category
typedef struct objc_category *Category;

/// 类中声明的属性
typedef struct objc_property *objc_property_t;

三、API的使用

//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换
+ (void)load{
    //方法交换应该被保证,在程序中只会执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        //获得viewController的生命周期方法的selector
        SEL systemSel = @selector(viewWillAppear:);
        //自己实现的将要被交换的方法的selector
        SEL swizzSel = @selector(swiz_viewWillAppear:);
        //两个方法的Method
        Method systemMethod = class_getInstanceMethod([self class], systemSel);
        Method swizzMethod = class_getInstanceMethod([self class], swizzSel);

        //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
        BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        if (isAdd) {
            //如果成功,说明类中不存在这个方法的实现
            //将被交换方法的实现替换到这个并不存在的实现
            class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
        }else{
            //否则,交换两个方法的实现
            method_exchangeImplementations(systemMethod, swizzMethod);
        }

    });
}

- (void)swiz_viewWillAppear:(BOOL)animated{
    //这时候调用自己,看起来像是死循环
    //但是其实自己的实现已经被替换了
    [self swiz_viewWillAppear:animated];
    NSLog(@"swizzle");
}

#pragma mark--实例变量
- (void)queryProperty{
    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
    ; i < count; i++) {
        objc_property_t pro = propertyList[i];
        const char *key = property_getName(pro);
        NSString *keyString = [NSString stringWithUTF8String:key];
        NSLog(@"%@",keyString);
    }
}
#pragma mark--成员变量
- (void)qyeryVar{
    unsigned int count;
    Ivar *ivar = class_copyIvarList([Person class], &count);
    ; i < count; i++) {
        Ivar var = ivar[i];
        const char *key = ivar_getName(var);
        NSString *keyString = [NSString stringWithUTF8String:key];
        NSLog(@"%@",keyString);
    }
}
#pragma mark--方法遍历
- (void)querymethod{
    unsigned int count;
    Method *methodList = class_copyMethodList([Person class], &count);
    ; i < count; i++) {
        Method method = methodList[i];
        SEL select = method_getName(method);
        NSString *selectString = NSStringFromSelector(select);
        NSLog(@"%@",selectString);
    }
}

四、应用场景--用runtime机制防止UIButton重复点击

1、UIControl的sendAction:to:forEvent:方法用于处理事件响应.如果我们在该方法的实现中, 添加针对点击事件的时间间隔相关的处理代码, 则能够做到在指定时间间隔中防止重复点击.

首先, 为UIButton添加一个Category:

@interface UIButton (CS_FixMultiClick)

@property (nonatomic, assign) NSTimeInterval cs_acceptEventInterval; // 重复点击的间隔

@property (nonatomic, assign) NSTimeInterval cs_acceptEventTime;

@end

2、Category不能给类添加属性, 所以以上的cs_acceptEventInterval和cs_acceptEventTime只会有对应的getter和setter方法, 不会添加真正的成员变量.如果我们不在实现文件中添加其getter和setter方法, 则采用 btn.cs_acceptEventInterval = 1; 这种方法尝试访问该属性会出错.

在实现文件中通过runtime的关联对象的方式, 为UIButton添加以上两个属性. 代码如下:

#import "UIControl+CS_FixMultiClick.h"
#import <objc/runtime.h>

@implementation UIButton (CS_FixMultiClick)

// 因category不能添加属性,只能通过关联对象的方式。
static const char *UIControl_acceptEventInterval = "UIControl_acceptEventInterval";

- (NSTimeInterval)cs_acceptEventInterval {
    return  [objc_getAssociatedObject(self, UIControl_acceptEventInterval) doubleValue];
}

- (void)setCs_acceptEventInterval:(NSTimeInterval)cs_acceptEventInterval {
    objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(cs_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime";

- (NSTimeInterval)cs_acceptEventTime {
    return  [objc_getAssociatedObject(self, UIControl_acceptEventTime) doubleValue];
}

- (void)setCs_acceptEventTime:(NSTimeInterval)cs_acceptEventTime {
    objc_setAssociatedObject(self, UIControl_acceptEventTime, @(cs_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// 在load时执行hook
+ (void)load {
    Method before   = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method after    = class_getInstanceMethod(self, @selector(cs_sendAction:to:forEvent:));
    method_exchangeImplementations(before, after);
}

- (void)cs_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    if ([NSDate date].timeIntervalSince1970 - self.cs_acceptEventTime < self.cs_acceptEventInterval) {
        return;
    }

    ) {
        self.cs_acceptEventTime = [NSDate date].timeIntervalSince1970;
    }

    [self cs_sendAction:action to:target forEvent:event];
}

@end

3、使用

btn.cs_acceptEventInterval = ;