iOS_Runtime、method swizzing (俗稱黑魔法) 一

本文舉例說明Runtime的一下幾個用途:

1、攔截並替換方法

2、給分類添加屬性

3、字典轉模型

4、動態添加方法,處理一個未實現方法和去除報錯

5、動態設置變量的值,可設置私有屬性

6、實現NSCoding協議,完成歸檔和解檔

7、獲取屬性、成員變量、方法(類/實例)、協議

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

9、動態添加方法

 

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");
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章