iOS高级理论:Runtime应用

一、遍历类的属性,快速归档

在 iOS 中,可以使用 Runtime 遍历类的属性来实现快速的归档(Archiving)操作。归档是将对象转换为数据流以便存储或传输的过程。下面是一个简单的示例,展示如何使用 Runtime 遍历类的属性进行归档操作:

假设有一个名为 Person 的类,我们想要对其属性进行归档操作:

objective-c 复制代码
#import <objc/runtime.h>

@interface Person : NSObject <NSCoding>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person

- (void)encodeWithCoder:(NSCoder *)coder {
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    for (int i = 0; i < count; i++) {
        objc_property_t property = properties[i];
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        id propertyValue = [self valueForKey:propertyName];
        
        [coder encodeObject:propertyValue forKey:propertyName];
    }
    
    free(properties);
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        unsigned int count;
        objc_property_t *properties = class_copyPropertyList([self class], &count);
        
        for (int i = 0; i < count; i++) {
            objc_property_t property = properties[i];
            NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
            id propertyValue = [coder decodeObjectForKey:propertyName];
            
            [self setValue:propertyValue forKey:propertyName];
        }
        
        free(properties);
    }
    return self;
}

@end

在上面的示例中,encodeWithCoder: 方法遍历了 Person 类的所有属性,并将属性的值使用 NSCoder 进行归桋操作。initWithCoder: 方法则对归档的数据进行解档,恢复对象的状态。

通过使用 Runtime 遍历类的属性,我们可以实现一个通用的归档和解档方法,而无需手动编写大量的归档代码。这样可以提高代码的复用性和可维护性。

二、字典转模型

1、创建一个NSObject的分类

objective-c 复制代码
@interface NSObject (Json)
+ (instancetype)dictToModel:(NSDictionary *)dict;
@end

2、实现分类中字典转模型的方法

objective-c 复制代码
#import "NSObject+Json.h"
#import <objc/runtime.h>

@implementation NSObject (Json)

+ (instancetype)dictToModel:(NSDictionary *)dict
{
    id obj = [[self alloc] init];
    unsigned int count = 0;
    
    Ivar *ivars = class_copyIvarList([self class], &count);
    
    for (int i=0; i<count; i++) {
        Ivar ivar = ivars[i];
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        [name deleteCharactersInRange:NSMakeRange(0, 1)];        
        [obj setValue:dict[name] forKey:name];
    }
    
    return obj;
}
@end

3、调用字典转模型的方法

objective-c 复制代码
- (void)viewDidLoad {
    [super viewDidLoad];

    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    [dict setObject:@"张三" forKey:@"name"];
    [dict setObject:@"20" forKey:@"age"];
    [dict setObject:@"北京" forKey:@"address"];
    
    Student *student = [Student dictToModel:dict];
    
    NSLog(@"name:%@\n",student.name);
    NSLog(@"age:%@\n",student.age);
    NSLog(@"address:%@\n",student.address);
}

4、运行结果

objective-c 复制代码
2019-04-13 10:51:32.136568+0800 AppLife[19195:4640916] name:张三
2019-04-13 10:51:32.136707+0800 AppLife[19195:4640916] age:20
2019-04-13 10:51:32.136803+0800 AppLife[19195:4640916] address:北京

三 防止数组插入空值

1、创建一个NSMutableArray的分类

objective-c 复制代码
@interface NSMutableArray (Extension)

@end

2、实现分类中方法的交换

objective-c 复制代码
#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>

@implementation NSMutableArray (Extension)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(cs_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)cs_insertObject:(id)anObject atIndex:(NSUInteger)index {
    if (anObject == nil) {
        return;
    }
    
    [self cs_insertObject:anObject atIndex:index];
}

@end

3、调用

objective-c 复制代码
#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>

@implementation NSMutableArray (Extension)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(cs_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)cs_insertObject:(id)anObject atIndex:(NSUInteger)index {
    if (anObject == nil) {
        return;
    }
    
    [self cs_insertObject:anObject atIndex:index];
}

@end

4、运行结果

objective-c 复制代码
2019-04-13 11:24:19.562363+0800 AppLife[20661:4661256] (
    Test
)

运用Rutime中交换方法的思想,还可以实现拦截所有按钮的点击时间和防止字典中插入空值等。

四、给分类添加属性

1、在分类里声明一个属性

objective-c 复制代码
#import "Student.h"

@interface Student (Test)
@property (nonatomic, copy) NSString *englishName;
@end

2、实现get和set方法

objective-c 复制代码
@implementation Student (Test)

- (void)setEnglishName:(NSString *)englishName
{
	// 第一个参数:给哪个对象添加关联
    // 第二个参数:关联的key,通过这个key获取
    // 第三个参数:关联的value
    // 第四个参数:关联的策略
    objc_setAssociatedObject(self, @"EnglishName", englishName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

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

@end

五、其他

(1) 实现第一个场景:跟踪程序每个ViewController展示给用户的次数,可以通过Method Swizzling替换ViewDidAppear初始方法。创建一个UIViewController的分类,重写自定义的ViewDidAppear方法,并在其+load方法中实现ViewDidAppear方法的交换。

(2) 开发中常需要在不改变某个类的前提下为其添加一个新的属性,尤其是为系统的类添加新的属性,这个时候就可以利用Runtime的关联对象(Associated Objects)来为分类添加新的属性了。

(3) 实现字典的模型和自动转换,优秀的JSON转模型第三方库JSONModel、YYModel等都利用runtime对属性进行获取,赋值等操作,要比KVC进行模型转换更加强大,更有效率。阅读YYModel的源码可以看出,YY大神对NSObject的内容进行了又一次封装,添加了许多描述内容。其中YYClassInfo是对Class进行了再次封装,而YYClassIvarInfo、YYClassMethodInfo、YYClPropertyInfo分别是对Class的Ivar、Method和property进行了封装和描述。在提取Class的相关信息时都运用了Runtime。

相关推荐
HarderCoder4 小时前
iOS 知识积累第一弹:从 struct 到 APP 生命周期的全景复盘
ios
叽哥14 小时前
Flutter Riverpod上手指南
android·flutter·ios
用户092 天前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan2 天前
iOS26适配指南之UIColor
ios·swift
权咚2 天前
阿权的开发经验小集
git·ios·xcode
用户092 天前
TipKit与CloudKit同步完全指南
ios·swift
法的空间3 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918413 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
00后程序员张3 天前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
Magnetic_h3 天前
【iOS】设计模式复习
笔记·学习·ios·设计模式·objective-c·cocoa