iOS之Runtime - Runtime应用场景

发布时间 2023-03-22 21:13:55作者: 带缝儿的沙盒

Runtime 的应用

1 - 查看私有成员变量:利用 Runtime 改变 UITextField 占位符字体颜色

 1 #import "ViewController.h"
 2 #import <objc/runtime.h>
 3 
 4 @interface ViewController ()
 5 @property (weak, nonatomic) IBOutlet UITextField *textField;
 6 @end
 7 
 8 @implementation ViewController
 9 
10 - (void)viewDidLoad {
11     [super viewDidLoad];
12     
13     unsigned int count;
14     Ivar *ivars = class_copyIvarList([UITextField class], &count);
15 
16     // 取出成员变量
17     for (int i = 0; i < count; i++) {
18 
19         Ivar ivar = ivars[i];
20         // 我们就可以通过对应的私有变量直接取值、赋值
21         NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
22     }
23     free(ivars);
24 
25     self.textField.placeholder = @"请输入用户名";
26     
27     // 变更占位字体颜色
28     
29     // iOS 13 开始;UITextField 禁止通过 KVC 修改属性
30     // 方式一
31     // UILabel *placeholderLabel01 = [self.textField valueForKeyPath:@"_placeholderLabel"]; // crash
32     
33     // 方式二
34     NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
35     attrs[NSForegroundColorAttributeName] = [UIColor blackColor];
36     self.textField.attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:@"请输入用户名" attributes:attrs];
37     
38     // 方式三
39     Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
40     UILabel *placeholderLabel = object_getIvar(self.textField, ivar);
41     placeholderLabel.textColor = [UIColor orangeColor];
42 
43 }
44 
45 @end

2 - 字典转模型

① Person 中有以下属性

1 #import <Foundation/Foundation.h>
2 @interface Person : NSObject
3 
4 @property (assign, nonatomic) int ID;
5 @property (assign, nonatomic) int weight;
6 @property (assign, nonatomic) int age;
7 @property (copy, nonatomic) NSString *name;
8 
9 @end

② 创建分类 NSObject +Json,实现字典转模型的功能(把字典里的 值 一一对应地赋值给 成员变量)

 1 #import "NSObject+Json.h"
 2 #import <objc/runtime.h>
 3 
 4 @implementation NSObject (Json)
 5 
 6 + (instancetype)test_objectWithJson:(NSDictionary *)json{
 7     
 8     id obj = [[self alloc] init];
 9     
10     // 获取成员变量
11     unsigned int count;
12     Ivar *ivars = class_copyIvarList(self, &count);
13     for (int i = 0; i < count; i++) {
14 
15         Ivar ivar = ivars[i];
16         
17         NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
18         // 删除成员变量前面的下划线
19         [name deleteCharactersInRange:NSMakeRange(0, 1)];
20         
21         id value = json[name];
22         // 简单判断
23         if ([name isEqualToString:@"ID"]) {
24             value = json[@"id"];
25         }
26         [obj setValue:value forKey:name];
27     }
28     free(ivars);
29     
30     return obj;
31 }
32 
33 @end

③ 具体使用:如果要写出一个完整的字典转模型的实现,考虑的因素太多了:后台返回数据格式、数据对等、继承.......

 1 #import <Foundation/Foundation.h>
 2 #import "Person.h"
 3 #import <objc/runtime.h>
 4 #import "NSObject+Json.h"
 5 
 6 int main(int argc, const char * argv[]) {
 7     @autoreleasepool {
 8         
 9         // 字典转模型
10         NSDictionary *json = @{
11                                @"id" : @20,
12                                @"age" : @30,
13                                @"weight" : @70,
14                                @"name" : @"Jack"
15                                };
16         
17         Person *person = [Person test_objectWithJson:json];
18         NSLog(@"%@",person); //  20 Jack
19     
20     }
21     return 0;
22 }

3 - 交换方法:开发中经常用到的情况,尤其在第三方框架中

// - Person.h

#import <Foundation/Foundation.h>
@interface Person : NSObject <NSCoding>
- (void)run;
- (void)test;
@end

// - Person.m

#import "Person.h"
@implementation Person
- (void)run{
    NSLog(@"%s", __func__);
}

- (void)test{
    NSLog(@"%s", __func__);
}

@end

// - main.m

 1 #import <Foundation/Foundation.h>
 2 #import "Person.h"
 3 #import <objc/runtime.h>
 4 void myrun(){
 5     
 6     NSLog(@"myrun");
 7 }
 8 
 9 int main(int argc, const char * argv[]) {
10     @autoreleasepool {
11         
12         Person *person = [[Person alloc] init];
13         
14         // 交换两个方法
15         Method runMethod = class_getInstanceMethod([Person class], @selector(run));
16         Method testMethod = class_getInstanceMethod([Person class], @selector(test));
17         method_exchangeImplementations(runMethod, testMethod);
18         [person run];  // 执行 test
19         [person test]; // 执行 run
20         
21         
22         // 替换某个方法
23         Person *person02 = [[Person alloc] init];
24         
25         // 方式一
26         class_replaceMethod([Person class], @selector(run), (IMP)myrun, "v");
27         [person02 run]; // 执行的时 myrun
28         
29         // 方式二
30         class_replaceMethod([Person class], @selector(run), imp_implementationWithBlock(^{
31             
32             NSLog(@"1234567890");
33         }), "v");
34         
35         [person02 run]; // 执行 block 里面的内容
36     }
37     
38     return 0;
39 }

4 - 方法签名:在开发过程中经常会遇到 unrecognized selector sent to instance 错误。下面我们使用方法签名解决该问题

// - Person.h

#import <Foundation/Foundation.h>
@interface Person : NSObject

-(void)run;

@end

// - Person.m

 1 #import "Person.h"
 2 @implementation Person
 3 
 4 -(void)run{
 5     
 6     NSLog(@"Person run.......");
 7 }
 8 
 9 // 方法签名
10 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
11     // 正常流程:如果存在 run 方法,就去执行该方法
12     if ([self respondsToSelector:aSelector]) {
13         return [super methodSignatureForSelector:aSelector];
14     }
15     
16     // 返回找不到的方法,就会启用下面的 forwardInvocation
17     return [NSMethodSignature signatureWithObjCTypes:"v@:"];
18 }
19 
20 // 找不到的方法都回来到这里
21 - (void)forwardInvocation:(NSInvocation *)anInvocation{
22 
23     NSLog(@"找不到 %@方法",NSStringFromSelector(anInvocation.selector));
24 }
25 @end

// - main.m

 1 #import <Foundation/Foundation.h>
 2 #import "Person.h"
 3 int main(int argc, const char * argv[]) {
 4     
 5     Person *p1 = [Person new];
 6     // 正常调用
 7     [p1 run];
 8     
 9     // 调用不存在的方法
10     [p1 makeT]; // 不再 crash
11     [p1 goT];   // 不再 crash
12     
13     return 0;
14     
15 }

结语

1 - 什么是 Runtime

   OC 是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行;OC 的动态性就是由 Runtime 来支撑和实现的,Runtime 是一套 C语言 的 API,封装了很多动态性相关的函数;平时编写的 OC 代码,底层都是转换成了 Runtime API 进行调用

2 - 平时项目运用到的场景

① 利用关联对象(AssociatedObject)给分类添加属性

② 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)

③ 交换方法实现(交换系统的方法)

④ 方法签名:利用消息转发机制解决方法找不到的异常问题

 ......