编程开发

iOS中的runTime

2019-08-20  本文已影响0人  cyhai

运行时,我们最常见的可能就是创建类别(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];

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

结束

上一篇 下一篇

猜你喜欢

热点阅读