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的占位文字颜色、字典转模型、自动归档解档)
③ 交换方法实现(交换系统的方法)
④ 方法签名:利用消息转发机制解决方法找不到的异常问题
......