Men的博客

欢迎光临!

0%

Runtime常见作用

动态的添加对象的成员变量和方法

class_addMethod([self.person class], @selector(guess), (IMP)guessAnswer, "v@:");
参数说明
>(IMP)guessAnswer 意思是guessAnswer的地址指针;
>"v@:" 意思是,v代表无返回值void,如果是i则代表int;@代表 id sel; : 代表 SEL _cmd;
>“v@:@@” 意思是,两个参数的没有返回值。  

动态交换两个方法的实现

交换方法之后,以后每次调用这两个方法都会交换方法的实现
Method m1 = class_getInstanceMethod([self.person class], @selector(sayName));
Method m2 = class_getInstanceMethod([self.person class], @selector(saySex));
method_exchangeImplementations(m1, m2);

拦截并替换方法

我们要改变这个方法的实现,但是又不能去动它的源代码
(正如一些开源库出现问题的时候),
这个时候runtime就派上用场了
我们先增加一个tool类,然后写一个我们自己实现的方法-change,
通过runtime把test1替换成change。
Class PersionClass = object_getClass([Person class]);
Class toolClass = object_getClass([tool class]);
////源方法的SEL和Method
SEL oriSEL = @selector(test1);
Method oriMethod = class_getInstanceMethod(PersionClass, oriSEL);
////交换方法的SEL和Method
SEL cusSEL = @selector(change);
Method cusMethod = class_getInstanceMethod(toolClass, cusSEL);
////先尝试給源方法添加实现,这里是为了避免源方法没有实现的情况
BOOL addSucc = class_addMethod(PersionClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
if (addSucc) {
    // 添加成功:将源方法的实现替换到交换方法的实现     
    class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
}else {
    //添加失败:说明源方法已经有实现,直接将两个方法的实现交换即
    method_exchangeImplementations(oriMethod, cusMethod);  
}

在方法上增加额外功能

有这样一个场景,出于某些需求,我们需要跟踪记录APP
中按钮的点击次数和频率等数据,怎么解决?当然通过继承按钮
类或者通过类别实现是一个办法,但是带来其他问题比如别人不
一定会去实例化你写的子类,或者其他类别也实现了点击方法导
致不确定会调用哪一个,runtime可以这样解决,
实现场景二、我们app经常会遇到一些cash,我们可以通过
runtime拦截替换这些方法,从而保证不会崩溃,例如如下列举
    NSArray+NSRangeException
    NSDictionary+NSRangeException
    NSMutableAttributedString+NSRangeException
    NSMutableString+NSRangeException
    NSNull+Exception
    NSObject+Swizzling
    NSString+NSRangeException
    UINavigationController+Consistent
load方法会在类第一次加载的时候被调用,调用的时间比较靠前,
适合在这个方法里做方法交换,方法交换应该被保证,
在程序中只会执行一次。
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class selfClass = [self class];
        SEL oriSEL = @selector(sendAction:to:forEvent:);
        Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);
        SEL cusSEL = @selector(mySendAction:to:forEvent:);
        Method cusMethod = class_getInstanceMethod(selfClass, cusSEL);
        BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
        if (addSucc) {
            class_replaceMethod(selfClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else {
            method_exchangeImplementations(oriMethod, cusMethod);
        }
    });
}
- (void)mySendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    [[Tool sharedManager] addCount];
    [self mySendAction:action to:target forEvent:event];
}

实现NSCoding的自动归档和解档

- (void)encodeWithCoder:(NSCoder *)encoder
{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Movie class], &count);
    for (int i = 0; i<count; i++) {
        // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        // 查看成员变量
        const char *name = ivar_getName(ivar);
        // 归档
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
    }
    free(ivars);
}

- (id)initWithCoder:(NSCoder *)decoder
{
    if (self = [super init]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Movie class], &count);
        for (int i = 0; i<count; i++) {
            // 取出i位置对应的成员变量
            Ivar ivar = ivars[i];
            // 查看成员变量
            const char *name = ivar_getName(ivar);
            // 归档
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [decoder decodeObjectForKey:key];
            // 设置到成员变量身上
            [self setValue:value forKey:key];
        }
        free(ivars);
    }
    return self;
}

实现字典转模型的自动转换

字典转模型的应用可以说是每个app必然会使用的场景,
虽然实现的方式略有不同,但是原理都是一致的:遍历模型中所有
属性,根据模型的属性名,去字典中查找key,取出对应的值,
给模型的属性赋值。
像几个出名的开源库:JSONModel,MJExtension等都是通过这
种方式实现的。
// 创建对应模型对象
id objc = [[self alloc] init];
unsigned int count = 0;
// 1.获取成员属性数组
Ivar *ivarList = class_copyIvarList(self, &count);
// 2.遍历所有的成员属性名,一个一个去字典中取出对应的
value给模型属性赋值
for (int i = 0; i < count; i++) {
    // 2.1 获取成员属性
    Ivar ivar = ivarList[i];
    // 2.2 获取成员属性名 C -> OC 字符串
    NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
    // 2.3 _成员属性名 => 字典key
    NSString *key = [ivarName substringFromIndex:1];
    // 2.4 去字典中取出对应value给模型属性赋值
    id value = dict[key];
    // 获取成员属性类型
    NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
}
如果模型比较简单,只有NSString,NSNumber等,
这样就可以搞定了。但是如果模型含有NSArray,或者NSDictionary等,那么我们还需要进行第二步转换。
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { 
    //  是字典对象,并且属性名对应类型是自定义类型
    // 处理类型字符串 @\"User\" -> User
    ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
    ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
    // 自定义对象,并且值是字典
    // value:user字典 -> User模型
    // 获取模型(user)类对象
    Class modalClass = NSClassFromString(ivarType);
    // 字典转模型
    if (modalClass) {
        // 字典转模型 user
        value = [modalClass objectWithDict:value];
    }
}
if ([value isKindOfClass:[NSArray class]]) {
    // 判断对应类有没有实现字典数组转模型数组的协议
    if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
        // 转换成id类型,就能调用任何对象的方法
        id idSelf = self;
        // 获取数组中字典对应的模型
        NSString *type =  [idSelf arrayContainModelClass][key];
        // 生成模型
        Class classModel = NSClassFromString(type);
        NSMutableArray *arrM = [NSMutableArray array];
        // 遍历字典数组,生成模型数组
        for (NSDictionary *dict in value) {
            // 字典转模型
            id model =  [classModel objectWithDict:dict];
            [arrM addObject:model];
        }
        // 把模型数组赋值给value
        value = arrM;
    }
}