iOS Runtime的妙用

Runtime是一套底层的C语言API(包含强大的C语言数据类型和函数),RunTime简称运行时。OC就是运行时机制,其中最主要的是消息机制。

苹果和GNU各自维护一个开源的runtime版本
https://github.com/opensource-apple/objc4 (Apple)

消息机制

Runtime进行方法调用本质上是发送消息,通过objc_msgSend()函数进行消息发送,下面就以一个对象的实例化为例

Person *p = [[Person alloc] init];

对于我们平常使用的初始化方法 alloc、 init、new,如果用消息机制分解步骤具体如下:

//通过类名获取类
Class class = objc_getClass("Person");
//注意Class实际上也是对象,所以同样能够接受消息,向Class发送alloc消息
Person *p = objc_msgSend(class,@selector(alloc));
//发送init消息给Person实例p
p = objc_msgSend(p,@selector(init));
//发送run消息给p 即调用run实例方法
objc_msgSend(p, @selector(run));
//汇总传递消息链
objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("Person"), @selector(alloc)), @selector(init)), @selector(run));

如果只申明了方法编号 ,没有对应的方法实现,在method_array_t的方法列表中没有该方法编号

使用objc_msgSend消息机制,必须导入头文件 @import <objc/messgae..h>,让后配置
Build Setting–>Apple LLVM 6.0 - Preprocessing–>Enable Strict Checking of objc_msgSend Calls 改为 NO

当向一个实例对象发送消息时,对象会根据super class指针找到object_class这个结构体,在这个结构体中,有一个method_array_t存储的方法列表,向对象发送消息就回去

在消息发送过程中,倘若没找到指定消息,runtime为我们预留了三次补救机会method_array_t中找是否有对应方法的实现,如果没有找到,就会沿着继承树去找,直到找到根类, 如果根类也没有对应方法实现,那么接下来就进入了我们常说的消息转发流程

消息转发机制,步骤如下图:

//第一次 动态方法解析
+(BOOL)resolveInstanceMethod:(SEL)sel;
//第二次 快速消息转发
-(id)forwardingTargetForSelector:(SEL)aSelector;
//第三次 标准消息转发 重定向
-(void)forwardInvocation:(NSInvocation *)anInvocation;
//消息中转中心
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

动态修改变量

/** 1.动态修改变量(私有变量也可修改)*/
- (void)dynamicModifyVariable {
    NSLog(@"修改前姓名为:%@", [p valueForKey:@"age"]);
    unsigned int count = 0;
    // 获取类的成员变量列表(包括私有) 获取属性,方法,协议列表 类似
    Ivar *varList = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        Ivar var = varList[i];
        const char *varName = ivar_getName(var);
        NSString *proname = [NSString stringWithUTF8String:varName];
        NSLog(@"查看成员变量:%@",proname);
        if ([proname isEqualToString:@"_age"]) {
            object_setIvar(p, var, @"22");
//            [p setValue:@"22" forKey:@"age"];
        }
    }
    NSLog(@"修改后姓名为:%@", [p valueForKey:@"age"]);
}

动态添加方法

分类不能增加方法,Extension 可以

//1、注册方法
SEL getInformationSelector = sel_registerName("getPersonAllInfo");
//2、添加方法
class_addMethod([Person class], getInformationSelector, (IMP)getInformation, "v@");

动态拦截与替换方法

拦截系统自带方法的调用(Swizzle黑魔法),也可以理解为替换系统方法
使用场景:比如数据埋点,搜集某些页面进入次数,或者按钮点击次数,在不改变原有结构的前提下,使用拦截方法可以实现无侵入式埋点或者其他功能, 但是方法拦截需要慎重使用,可能牵一发而动全身.

+(void)load {
    [super load];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //获取方法名
        SEL origSel = @selector(sendAction:to:forEvent:);
        SEL swizSel = @selector(ept_sendAction:to:forEvent:);
        Method origMethod = class_getInstanceMethod([self class], origSel);
        Method swizMethod = class_getInstanceMethod([self class], swizSel);
        //判断方法是否添加成功
        BOOL addMethod = class_addMethod([self class], origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
        if (addMethod) {
            //替换类中已有方法的实现,如果该方法不存在添加该方法
            class_replaceMethod([self class], swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
        }else{
            //替换Method
            method_exchangeImplementations(origMethod, swizMethod);
        }
    });
}
- (void)ept_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    [self ept_sendAction:action to:target forEvent:event];
    NSLog(@"拦截系统点击方法成功,调用ept_sendAction");
}

动态拦截替换方法常用函数:

1、class_getInstanceMethod 获取方法名
2、class_addMethod 判断方法是否添加成功
3、class_replaceMethod 替换类中已有方法的实现,如果该方法不存在添加该方法
4、method_exchangeImplementations 替换Method

动态添加属性

// 重写set和get方法 设置关联
- (NSString *)mobile {
    return objc_getAssociatedObject(self, "mobile");
}
- (void)setMobile:(NSString *)mobile {
    objc_setAssociatedObject(self, "mobile", mobile, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} 

重写set和get方法,设置关联属性

/*
第一个参数:当前对象
第二个参数:新增属性键值
/

objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

/*
第一个参数id object, 当前对象
第二个参数const void
key, 关联的key,是c字符串
第三个参数id value, 被关联的对象的值
第四个参数objc_AssociationPolicy policy关联引用的规则
*/

objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy)

自动化归解档

归解档遵循NSCoding协议即可,实现归档和解档即可

#pragma mark - coding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder;

对于自定义类型的归解档,最原始的方法就是挨个属性去归档、解档,但是当你要归档的对象永远数亿属性资产的时候,很显然最原始的方法已经无法满足我们对于效率的诉求,从而就有了自动化归解档的诞生

分别在两个方法去遍历成员变量,取值、设值

#pragma mark - coding
- (void)encodeWithCoder:(NSCoder *)aCoder{

    /**
     传统做法:挨个属性去归档
     但是当遇到n多属性的时候 显然一个个去归档就很繁琐了
     */
//    [aCoder encodeObject:self.name forKey:@"name"];
//    [aCoder encodeObject:self.sex forKey:@"sex"];
//    [aCoder encodeObject:self.friends forKey:@"friends"];


    unsigned int count = 0;

    Ivar *ivarList = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i ++) {
        Ivar ivar = ivarList[i];                     // 从成员列表中取出成员变量
        const char *name = ivar_getName(ivar);       // 获取成员变量名
        // 进行归档
        NSString *key = [NSString stringWithUTF8String:name];
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
    free(ivarList);
}

获取成员变量,赋值成员变量

- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if (self) {

//        _name = [aDecoder decodeObjectForKey:@"name"];
//        _sex = [aDecoder decodeObjectForKey:@"sex"];
//        _friends = [aDecoder decodeObjectForKey:@"friends"];

        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivarList[i];                        // 从成员列表中取出成员变量
            const char *name = ivar_getName(ivar);       // 获取成员变量名
            // 进行解档
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [aDecoder decodeObjectForKey:key];
            // 将值赋值给成员变量
            [self setValue:value forKey:key];
        }
        free(ivarList);
    }
    return self;
}

字典转模型


目录