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。

相关推荐
黑化旺仔20 小时前
【OC】Runtime:类元数据、加载初始化与消息派发
开发语言·macos·ios·objective-c
songgeb21 小时前
iOS App支持设置RTL语言的实践
ios·swift·ui kit
软泡芙1 天前
【iOS】swift启动屏显示黑屏
ios
用户69371750013841 天前
当你向领导申请 AI 编程工具预算时,如果领导追问“能提效多少,有量化数据吗”,你该怎么回答?
android·后端·ios
Layer1 天前
一些脱离 Mac 依赖的 Agent 操作 iPhone 实现
ios·ai编程·测试
用户69371750013841 天前
当我说 Claude 搭配 GLM-5.3-Flash 太好用了,同事却提醒我:你可能正在失去对代码的掌控
android·后端·ios
茶底世界之下1 天前
一键裁方会丢掉什么?用 Awaking 给园林照片做两种画幅
ios
Marco_Marco1351 天前
定位SDK接入实战:Android/iOS/鸿蒙三端怎么选、怎么接
android·ios·harmonyos
老李IT笔记2 天前
关掉设备上的 AI 扩展:两个键各管一层,配错了等于没配
人工智能·ios·智能手机
2501_915921432 天前
iOS应用耗电测试:使用Energy Log、Keymob和Instruments进行性能监控与能源消耗分析
android·ios·小程序·https·uni-app·iphone·webview