iOS中的runTime

作者: cyhai | 来源:发表于2019-08-20 17:21 被阅读0次

    运行时,我们最常见的可能就是创建类别(Category),为类别添加属性,类别是不能直接添加属性的,可以添加方法,但是加上运行时,便可以添加属性。还有一种就是使用运行时,通过获取属性列表,成员变量列表,方法列表,协议列表,MJExtension就是利用了运行时进行字典与模型转换。现在我们来实现这两种做法。

    为类别添加属性

    创建继承Object的Category并引入

    #import <objc/runtime.h>
    

    定义一个属性

    /**
     标记属性
     */
    @property (nonatomic , copy)NSString * rumTimeString;
    

    固定一个对应的key

    const char * key1 = "rumTimeString";
    

    初始化getter和setter
    然后实现objc_getAssociatedObject和objc_setAssociatedObject方法

    const char * key1 = "rumTimeString";
    @implementation NSObject (Object)
    @dynamic rumTimeString;
    - (NSString *)rumTimeString
    {
        return objc_getAssociatedObject(self, key1);
    }
    - (void)setRumTimeString:(NSString *)rumTimeString
    {
        objc_setAssociatedObject(self, key1, rumTimeString, OBJC_ASSOCIATION_COPY);
    }
    
    

    到此就结束了就完成属性的添加了

    获取属性列表字典转模型

    创建一个类方法

    + (id)initWithDict:(NSDictionary *)dict;
    
    //初始化对象
    id obj = [[self class] new];
     if (obj) {
    }
    
    //获取类的属性及属性对应的类型
     NSMutableArray * keys = [NSMutableArray array];
     NSMutableArray * attributes = [NSMutableArray array];
    
    //获取属性列表与个数
    unsigned int outCount;
    objc_property_t * properties = class_copyPropertyList([self class], &outCount);
    
    //遍历属性
    for (int i = 0; i < outCount; i ++) {
                objc_property_t property = properties[i];
                //通过property_getName函数获得属性的名字
                NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
                [keys addObject:propertyName];
                //通过property_getAttributes函数可以获得属性的名字和@encode编码
                NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
                [attributes addObject:propertyAttribute];
            }
    //释放properties指向的内存
    free(properties);
    
    //给属性赋值
     for (NSString * key in keys) {
                if ([dict valueForKey:key] == nil) continue;
                [obj setValue:[dict valueForKey:key] forKey:key];
            }
    

    最后返回

        return obj;
    

    全部代码

    + (id)initWithDict:(NSDictionary *)dict {
        
        id obj = [[self class] new];
        if (obj) {
            //获取类的属性及属性对应的类型
            NSMutableArray * keys = [NSMutableArray array];
            NSMutableArray * attributes = [NSMutableArray array];
            unsigned int outCount;
            objc_property_t * properties = class_copyPropertyList([self class], &outCount);
            for (int i = 0; i < outCount; i ++) {
                objc_property_t property = properties[i];
                //通过property_getName函数获得属性的名字
                NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
                [keys addObject:propertyName];
                //通过property_getAttributes函数可以获得属性的名字和@encode编码
                NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
                [attributes addObject:propertyAttribute];
            }
            //释放properties指向的内存
            free(properties);
            
            //给属性赋值
            for (NSString * key in keys) {
                if ([dict valueForKey:key] == nil) continue;
                [obj setValue:[dict valueForKey:key] forKey:key];
            }
        }
        return obj;
        
    }
    

    创建Model,并定义属性,引用上面的方法得到model

    @interface Model : NSObject
    
    @property (nonatomic , assign)NSUInteger age;
    @property (nonatomic , copy)NSString * testString;
    
    @end
    
    NSDictionary * dict = @{@"age":@1,@"testString":@"属性列表赋值"};
    Model * model = [Model initWithDict:dict];
    NSLog(@"%@",model.testString);
    
    

    如果模型存在嵌套模型,单纯这样处理还是不够。需要再加判断与循环遍历。
    比如这里如果出现model中再嵌套一个model01

    @interface Model01 : NSObject
    
    @property (nonatomic , copy)NSString * testStr;
    
    @end
    
    @interface Model : NSObject
    
    @property (nonatomic , assign)NSUInteger age;
    @property (nonatomic , copy)NSString * testString;
    @property (nonatomic , strong)NSArray * testArr;
    @property (nonatomic , strong)Model01 * m01;
    @end
    
    

    按照刚才的处理

    NSDictionary * dict = @{@"age":@1,@"testString":@"属性列表修改",@"testArr":@[@"1",@"2"],@"m01":@{@"testStr":@"model01赋值"}};
        Model * model = [Model initWithDict:dict];
        [model getPropertyList];
        NSLog(@"%@",model.m01.testStr);
    

    这里肯定是得不到testStr的。
    那该怎么处理,可以添加一个递归循环处理。

     //给属性赋值
            int i = 0;
            for (NSString * key in keys) {
                if ([dict valueForKey:key] == nil) continue;
                if ( [[dict valueForKey:key] isKindOfClass:[NSDictionary class]]) {
                    NSDictionary * dict2 = [dict valueForKey:key];
                    NSLog(@"第二层%@",key.superclass);
                  id classobj = [attributes[i] componentsSeparatedByString:@"\""][1] ;//获取属性所属class名字
                   id obj02 = [NSClassFromString(classobj) initWithDict:dict2];//递归循环,如果碰到value是字典,那么就遍历,并给嵌套的model初始化赋值。
                    [obj setValue:obj02 forKey:key];//将已经嵌套的model赋值后再赋值给上一层model。Model01对象(赋值OK)-->这一层循环结后交给Model对象的obj的m01。
                }else
                {
                    [obj setValue:[dict valueForKey:key] forKey:key];
                }
                i ++;
            }
        }
        return obj;
    

    重新跑,会发现,model.m01.testStr,拿到了model01赋值。

    Method Swizzling方法交换

    好像之前看到有人说,交换灵魂,什么魔术师之类的,我承认土鳖。
    UIViewController里面有个方法是viewWillAppear,现在把这个方法替换掉。

    创建一个类别#import "UIViewController+Swizzling.h"

    写一个类方法+ (void)load,引入#import <objc/runtime.h>
    写一个新方法,代替viewWillAppear,注意的是这里的+ (void)load是#import <objc/runtime.h>方法中的一个。并不是无中生有。

    image.png
    - (void)new_viewWillAppear:(BOOL)animated {
            NSLog(@"只要加载viewWillAppear: %@", self);
    }
    

    使用运行时实现+ (void)load如下

    + (void)load
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = self;
            
            SEL originalSelector = @selector(viewWillAppear:);//原方法选择器
            SEL swizzledSelector = @selector(new_viewWillAppear:);//替换后方法选择器
            
            Method originalMethod = class_getInstanceMethod(class, originalSelector);//原方法
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);//替换方法
           //添加
            BOOL didAddMethod =
            class_addMethod(class,
                            originalSelector,
                            method_getImplementation(swizzledMethod),
                            method_getTypeEncoding(swizzledMethod));
            //替换
            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);//双互交换
            }
        });
    }
    

    然后在ViewController引入#import "UIViewController+Swizzling.h"就完事了,然后你每次跑起来的时候,都会运行到new_viewWillAppear

    现在重新创建一个方法- (void)newload

    并实现如下

    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Class class = [self class];
            SEL originalSelector = @selector(riginalMethod_click);
            SEL swizzledSelector = @selector(swizzledMethod_click);
            
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);    
            BOOL didAddMethod =
            class_addMethod(class,
                            originalSelector,
                            method_getImplementation(swizzledMethod),
                            method_getTypeEncoding(swizzledMethod));
            
            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);//双互交换
            }
        });
    }
    

    注意,如果你这里的方法是写在#import "UIViewController+Swizzling.h"那么引用的时候要确定你当前类有

    riginalMethod_click
    swizzledMethod_click
    

    这两个方法,别无脑崩。实现方法如下


    image.png

    引用newload与与方法riginalMethod_click

    [self newload];
    [self riginalMethod_click];
    

    跑起来,然后会发现,打印的并不是“原方法”而是“替换方法”。

    结束

    相关文章

      网友评论

        本文标题:iOS中的runTime

        本文链接:https://www.haomeiwen.com/subject/swjpsctx.html