Runtime、method swizzing (俗称黑魔法) 一

本文举例说明Runtime的一下几个用途:

1、拦截并替换方法

2、给分类添加属性

3、字典转模型

4、动态添加方法,处理一个未实现方法和去除报错

5、动态设置变量的值,可设置私有属性

6、实现NSCoding协议,完成归档和解档

7、获取属性、成员变量、方法(类/实例)、协议

8、添加方法、替换原方法、交换方法

9、动态添加方法


扫描二维码关注公众号,回复: 2648550 查看本文章

1、在分类为系统方法添加功能

例:输出UIImage imageNamed: 图片加载成功与否

#import "UIImage+image.h"
#import <objc/runtime.h>

@implementation UIImage (image)

#pragma mark - 把类加载进内存时调用,只会调用一次
+ (void)load {
    // class_getClassMethod 获取某个类的方法
    // method_exchangedImplementations 交换两个方法的地址
    
    // 1、获取系统方法
    Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
    // 2、获取自定义的方法
    Method in_imageNamedMethod = class_getClassMethod(self, @selector(in_imageNamed:));
    // 3、交换方法地址
    method_exchangeImplementations(imageNamedMethod, in_imageNamedMethod);
}

+ (UIImage *)in_imageNamed:(NSString *)name {
    // 这里调用的是系统的imageNamed,因为已经替换过了
    UIImage *image = [UIImage in_imageNamed:name];
    if (image) {
        NSLog(@"图片加载成功");
    } else {
        NSLog(@"图片加载失败");
    }
    return image;
}

2、给系统分类添加属性

例:给NSObject添加name属性

#import "NSObject+property.h"
#import <objc/runtime.h>

@implementation NSObject (property)

- (void)setName:(NSString *)name {
    // objc_setAssociatedObject 将某个值 赋值给某个对象的某个属性
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, @"name");
}

3、字典转模型 (包含字典套字典,字典套数组 情况)

(1)NSObject分类实现

#import "NSObject+arrayContain.h"
#import <objc/runtime.h>

@implementation NSObject (arrayContain)

+ (instancetype)modelWithDict:(NSDictionary *)dict {
    id objc = [[self alloc] init];
    // 成员变量个数
    unsigned int count = 0;
    // 获取类中的所有成员变量
    Ivar *ivarList = class_copyIvarList(self, &count);
    // 遍历所有成员变量
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];
        // 类型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        // @"User" -> User
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
        
        // 名字 ivar_getName
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        // 去掉name前面的"_"
        NSString *key = [ivarName substringFromIndex:1];
        
        
        // 根据名字取字典里找对应的value
        id value = dict[key];
        
        // --------   字典里还有字典   --------
        if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
            // 根据类型名 生成类对象
            Class modelClass = NSClassFromString(ivarType);
            if (modelClass) {   // 有对应的类型才转
                value = [modelClass modelWithDict: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 *dic in value) {
                    id model = [classModel modelWithDict:dic];
                    [arrM addObject:model];
                }
                value = arrM;
            }
        }
        if (value) {    // 给属性赋值
            [objc setValue:value forKey:key];
        }
    }
    return objc;
}

(2)定义一个协议,返回字典: 数组名 对应的 类名

#import <Foundation/Foundation.h>

@protocol NSObjectDelegate <NSObject>
+ (NSDictionary *)arrayContainModelClass;
@end

@interface NSObject (arrayContain)
+ (instancetype)modelWithDict:(NSDictionary *)dict;
@end
#import "NSObject+arrayContain.h"

@interface Student : NSObject <NSObjectDelegate, NSCoding>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, strong) NSArray *friends;
#import <objc/runtime.h>

@implementation Student

+ (NSDictionary *)arrayContainModelClass {
    return @{@"friends":NSStringFromClass([self class])};
}

即可实现字典转模型了:

// --------   字典转模型   --------
NSDictionary *friend = @{@"name":@"huhu", @"age":@25};
NSDictionary *dic = @{@"name":@"momo", @"age":@24, @"friends":@[friend, friend]};
Student *stu = [Student modelWithDict:dic];
NSLog(@"%@", stu.name);

4、动态添加方法处理一个未实现的方法 和 去除报错

@implementation Student

#pragma mark - 处理未实现方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == NSSelectorFromString(@"run:")) {
        class_addMethod(self, sel, (IMP)aaa, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

// 任何方法默认都有两个隐式参数:self _cmd(当前方法的编号)
void aaa(id self, SEL _cmd, NSNumber *meter) {
    NSLog(@"跑了%@米", meter);
}
[stu performSelector:@selector(run:) withObject:@10];
输出:  跑了 10


5、动态变量控制(可以操作私有)

@interface ViewController ()
@property (nonatomic, strong) Student *xiaoMing;
@end
// --------   5、动态变量控制   --------
self.xiaoMing = [Student modelWithDict:@{@"name":@"xiaoming", @"age":@22}];
unsigned int count = 0;
Ivar *ivar = class_copyIvarList([self.xiaoMing class], &count);
for (int i = 0; i < count; i++) {
    Ivar var = ivar[i];
    // 成员变量 -> 属性名
    const char *varName = ivar_getName(var);
    NSString *name = [NSString stringWithUTF8String:varName];
    
    // 属性名 -> 成员变量
    Ivar ivar = class_getClassVariable([self.xiaoMing class], varName);
    
    if ([name isEqualToString:@"_name"]) {
        // 也可对私有变量赋值
        object_setIvar(self.xiaoMing, var, @"xiaohong");
    }
    if ([name isEqualToString:@"_age"]) {
        object_setIvar(self.xiaoMing, var, @20);
    }
}
NSLog(@"xiaoMing %@", self.xiaoMing.age);
NSLog(@"xiaoMing %@", self.xiaoMing.name);

6、实现NSCoding的归档和解档

#pragma mark - 归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int count = 0;
    Ivar *ivarList = class_copyIvarList([self 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);
}

#pragma mark - 解档
- (id)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 var = ivarList[i];
            const char *name = ivar_getName(var);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [aDecoder decodeObjectForKey:key];   // 解档
            [self setValue:value forKey:key];
        }
        free(ivarList);
    }
    return self;
}
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [path stringByAppendingString:[NSString stringWithFormat:@"student:%@", stu.name]];
[NSKeyedArchiver archiveRootObject:stu toFile:filePath];    // 写
stu = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; // 读

7、获取 属性、成员变量、方法(类/实例)、协议

// 属性列表
objc_property_t *propertyList = class_copyPropertyList([self.xiaoMing class], &count);
for (int i = 0; i < count; i++) {
    const char *name = property_getName(propertyList[i]);
    NSLog(@"property : %@", [NSString stringWithUTF8String:name]);
}
Class StudentClass = object_getClass([stu class]);
// 成员变量列表
Ivar *allIvar = class_copyIvarList(StudentClass, &count);

// 方法列表
Method *methodList = class_copyMethodList([self.xiaoMing class], &count);
for (int i = 0; i < count; i++) {
    Method method = methodList[i];
    NSLog(@"method : %@", NSStringFromSelector(method_getName(method)));
}
// 类方法
Method *allMethod = class_copyMethodList(StudentClass, &count); // 类的所有方法
SEL oriSEL = @selector(arrayContainModelClass);
Method oriMethod = class_getClassMethod(StudentClass, oriSEL);
NSLog(@"class method : %@", NSStringFromSelector(method_getName(oriMethod)));

// 实例方法
SEL instanceSEL = @selector(thinking);
Method instanceMethod = class_getInstanceMethod([stu class], instanceSEL);
NSLog(@"instance method : %@", NSStringFromSelector(method_getName(instanceMethod)));    

// 协议
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([stu class], &count);
for (int i = 0; i < count; i++) {
    Protocol *protocol = protocolList[i];
    const char *protocolName = protocol_getName(protocol);
    NSLog(@"protocol : %@", [NSString stringWithUTF8String:protocolName]);
}
8、添加方法、替换原方法、交换方法
// 添加方法
Method cusMethod;
BOOL addFunc = class_addMethod(StudentClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));

// 替换原方法
class_replaceMethod(StudentClass, cusMethod, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

// 交换两个方法
method_exchangeImplementations(oriMethod, cusMethod);
9、动态添加方法

    // --------   动态添加方法   --------
    class_addMethod([Student class], @selector(sayHi), (IMP)myAddingFunction, 0);
    if ([self.xiaoMing respondsToSelector:@selector(sayHi)]) {
        [self.xiaoMing performSelector:@selector(sayHi) withObject:nil];
    }
}

void myAddingFunction(id self, SEL _cmd) {
    NSLog(@"hi");
}


猜你喜欢

转载自blog.csdn.net/Margaret_MO/article/details/79749989