iOS Runtime几种常见的应用场景

OC的动态性

OC 的函数是属于动态调用,在编译的时候是不能决定真正去调用那个函数的,只有在运行的时候才能决定去调用哪一个函数 ,在编译阶段,OC可以调用任何的函数,即使这个函数没有实现,只要声明过也就不会报错。

场景一:防止button重复事件点击

Method Swizzle执行点击事件,我们利用runtime来方法交换,来达到防止重复点击的目的:

  1. runtime添加时间间隔属性
//UIButton添加一个Category
//.h文件
@interface UIButton (YY_FixMultiClick)
@property (nonatomic, assign) NSTimeInterval yy_acceptEventInterval; // 时间间隔
@property (nonatomic, assign) NSTimeInterval yy_acceptEventTime;
@end

注意:Category不能给类添加属性, 所以以上的yy_acceptEventIntervalyy_acceptEventTime只会有对应的gettersetter方法, 不会添加真正的成员变量.
如果我们不在实现文件中添加其gettersetter方法, 则采用*** btn.yy_acceptEventInterval = 0.5; *** 这种方法访问程序会崩溃.

2019-07-15 08:30:52.538 RuntimeDemo[17380:1387981] -[UIButton setYy_acceptEventInterval:]: unrecognized selector sent to instance 0x7fe8e198e880

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

#import "UIControl+YY_FixMultiClick"
#import <objc/runtime.h>
@implementation UIButton (YY_FixMultiClick)

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

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

- (void)setYy_acceptEventInterval:(NSTimeInterval)yy_acceptEventInterval {
    objc_setAssociatedObject(self, UIControl_acceptEventInterval, @(yy_acceptEventInterval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

static const char *UIControl_acceptEventTime = "UIControl_acceptEventTime";

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

- (void)setYy_acceptEventTime:(NSTimeInterval)yy_acceptEventTime {
    objc_setAssociatedObject(self, UIControl_acceptEventTime, @(yy_acceptEventTime), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
  1. 方法交换
//文件:.m
// 在load时执行
+ (void)load {
	[super load];
    Method normal = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method newMethod = class_getInstanceMethod(self, @selector(yy_sendAction:to:forEvent:));
    method_exchangeImplementations(normal, newMethod);
}

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

    if (self.yy_acceptEventInterval > 0) {
        self.yy_acceptEventTime = [NSDate date].timeIntervalSince1970;
    }
    [self yy_sendAction:action to:target forEvent:event];
}
  1. button防止重复点击使用:
btn.yy_acceptEventInterval = 0.5f; //UIButton指定了0.5s的时间间隔用于防止重复点击.

场景二:访问NSArray越界问题

为NSArray添加Category代码如下:

//
//  NSArray+Crash.m
//  YMallShop
//
//  Created by bruce on 2018/8/8.
//  Copyright © 2018年 bruce. All rights reserved.
//
#import "NSArray+Crash.h"
@implementation NSArray (Crash)
+ (void)load{
    [super load];
    //替换不可变数组方法
    Method oldObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
    Method newObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtSafeIndex:));
    method_exchangeImplementations(oldObjectAtIndex, newObjectAtIndex);
    
    //替换可变数组方法
    Method oldMutableObjectAtIndex = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(objectAtIndex:));
    Method newMutableObjectAtIndex =  class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(mutableObjectAtSafeIndex:));
    method_exchangeImplementations(oldMutableObjectAtIndex, newMutableObjectAtIndex);
}


- (id)objectAtSafeIndex:(NSUInteger)index {
    if (index > self.count - 1 || !self.count) {
        @try {
            return [self objectAtSafeIndex:index];
        }
        @catch (NSException *exception) {
            NSLog(@"exception: %@", exception.reason);
            return nil;
        }
    }else {
        return [self objectAtSafeIndex:index];
    }
}

- (id)mutableObjectAtSafeIndex:(NSUInteger)index{
    if (index > self.count - 1 || !self.count) {
        @try {
            return [self mutableObjectAtSafeIndex:index];
        }
        @catch (NSException *exception) {
            NSLog(@"exception: %@", exception.reason);
            return nil;
        }
    }else {
        return [self mutableObjectAtSafeIndex:index];
    }
}
@end

场景三:快速接手新工程ViewController中添加Hook

//
//  UIViewController+Swizzling.m
//  qzy
//
//  Created by BruceYao on 2017/1/17.
//  Copyright © 2017年 BruceYao. All rights reserved.
//

#import "UIViewController+Swizzling.h"

@implementation UIViewController (Swizzling)

+ (void)load {
// 只在开发模式下,才会出现交换
#ifdef DEBUG
    
//    原来的viewWillAppear方法
    Method viewWillAppear = class_getInstanceMethod(self, @selector(viewWillAppear:));
//    需要替换成,能够输出的日志的viewWillAppear
    Method logViewWillAppear = class_getInstanceMethod(self, @selector(logViewWillAppear:));
//    两个方法进行交换
    method_exchangeImplementations(viewWillAppear, logViewWillAppear);
    
    //    原来的viewWillDisappear方法
    Method viewWillDisappear = class_getInstanceMethod(self, @selector(viewWillDisappear:));
    //    需要替换成,能够输出的日志的viewWillAppear
    Method logViewWillDisAppear = class_getInstanceMethod(self, @selector(logViewWillDisAppear:));
    //    两个方法进行交换
    method_exchangeImplementations(viewWillDisappear, logViewWillDisAppear);
#endif
}

- (void)logViewWillAppear:(BOOL)animater {
    
    NSString *className = NSStringFromClass([self class]);
    //[MobClick beginLogPageView:className]; 统计的时候会用到、友盟统计
    NSLog(@"%@ will appear", className);
}

- (void)logViewWillDisAppear:(BOOL)animater {
    NSString *className = NSStringFromClass([self class]);
    //[MobClick endLogPageView:className];统计的时候会用到
}
@end

###场景四:归档、反归档

#import "EncodeAndUnEncode.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation EncodeAndUnEncode
//解
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar * ivarlist = class_copyIvarList([self class], &count);
        for (int i = 0; i<count; i++) {
            Ivar alvar = ivarlist[i];
            const char * name = ivar_getName(alvar);
            id value = [aDecoder decodeObjectForKey:[NSString stringWithUTF8String:name]];
            if value != nil {
             [self setValue:value forKey:[NSString stringWithUTF8String:name]];   
            }
        }
        free(ivarlist);
    }
    return self;
}
//归
-(void)encodeWithCoder:(NSCoder *)aCoder{
    
    //获取某个类的成员变量
    unsigned int count = 0;
    Ivar * ivarlist = class_copyIvarList([self class], &count);
    for (int i = 0; i < count; i ++) {
        Ivar alvar = ivarlist[i];
        //获取成员变量的名称
        const char * ivarname = ivar_getName(alvar);
        id value = [self valueForKey:[NSString stringWithUTF8String:ivarname]];
        if value != nil {
            [aCoder encodeObject:value forKey:[NSString stringWithUTF8String:ivarname]];
        }
    }
    free(ivarlist);   
}

动态的添加方法

开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。
调用:

Person *per = [[Person alloc] init];
    per.name = [[NSString alloc] initWithFormat:@"张三"];
    [per eat];
    objc_msgSend(per, @selector(eat));
    [per performSelector:@selector(sleeping)];

对应的类中:Person,或者在分类中添加也可以

void sleeping(id self, SEL sel) {
NSLog(@"%@ %@", self, NSStringFromSelector(sel));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(sleeping)) {
// type: 方法类型:void用v来表示,id参数用@来表示,SEL用:来表示
class_addMethod(self, @selector(sleeping), sleeping, "v@:");
}
return [super resolveInstanceMethod:sel];
}

发送消息

注意点:在工程配置页面中:Enable Strict Checking of objc_msgSend Calls 设置为NO即可,要不然会引起crash
在这里插入图片描述

 Person *per = [[Person alloc] init];
    per.name = [[NSString alloc] initWithFormat:@"张三"];
    [per eat];
    objc_msgSend(per, @selector(eat));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章