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。

相关推荐
二流小码农13 分钟前
鸿蒙开发:DevEcoStudio中的代码提取
android·ios·harmonyos
Digitally6 小时前
如何用4 种可靠的方法更换 iPhone(2025 年指南)
ios·iphone
97650333510 小时前
iOS 审核 cocos 4.3a【苹果机审的“分层阈值”设计】
flutter·游戏·unity·ios
I烟雨云渊T10 小时前
iOS Alamofire库的使用
ios
程序员老刘·10 小时前
iOS 26 beta1 真机无法执行hot reload
flutter·ios·跨平台开发·客户端开发
EndingCoder10 小时前
React Native 构建与打包发布(iOS + Android)
android·react native·ios
程序员小刘11 小时前
HarmonyOS 5鸿蒙多端编译实战:从Android/iOS到HarmonyOS 5 的跨端迁移指南详
android·ios·华为·harmonyos
I烟雨云渊T11 小时前
iOS swiftUI的实用举例
ios·swiftui·swift
getapi12 小时前
将 App 安装到 iPhone 真机上测试
ios·iphone
90后的晨仔1 天前
RxSwift 中的 `Single`:单元素响应式编程简单实践
ios