一、简介
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 = ;