赞
踩
OC是对C语言的扩展,加入了面向对象和消息发送机制,Runtime
是OC的一个核心,是用C语言和汇编语言编写。OC是动态运行时语言,在运行时确定一个对象的类型、调用哪个对象的方法,因此需要Runtime来做类和对象的动态创建,消息传递和消息转发等。OC代码最终会转换成Runtime库中对应的函数结构体。任何语言最终都会被编译为汇编语言,再汇编为机器语言。 OC到可执行文件编译过程:
OC->Runtime->C->汇编->可执行文件。
Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码,通过 Foundation
框架的NSObject类定义的方法,通过对 runtime 函数的直接调用。大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。
image.png
二、Runtime源码初探
runtime 是 OC底层的一套C语言的API(引入 <objc/runtime.h>
或<objc/message.h>
),编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite-objc xxx.m
可以看到编译后的xxx.cpp
(C++文件)。
比如我们创建了一个对象 [[NSObject alloc]init]
,最终被转换为几万行代码,截取最关键的一句可以看到底层是通过runtime创建的对象
image.png
删除掉一些强制转换语句,可以看到调用方法本质就是发消息,[[NSObject alloc]init]
语句发了两次消息,第一次发了alloc
消息,第二次发送init
消息。利用这个功能我们可以探究底层,比如block的实现原理。
image.png
三、Runtime功能介绍+使用场景
动态添加属性
动态添加方法
方法交换
归档接档
字典转模型
1.动态添加属性
使用场景: 给系统的类添加属性的时候,可以使用runtime
动态添加属性方法;
- @implementation NSObject (Property)
-
- - (void)setName:(NSString *)name
- {
- /*
- object:保存到哪个对象中
- key:用什么属性保存 属性名
- value:保存值
- policy:策略,strong,weak
- */
- objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
-
- - (NSString *)name
- {
- return objc_getAssociatedObject(self, "name");
- }
- - (void)viewDidLoad {
-
- [super viewDidLoad];
-
- self.view.backgroundColor = [UIColor orangeColor];
-
- //给系统NSObject类动态添加属性name
-
- NSObject *objc = [[NSObject alloc] init];
-
- objc.name = @"石虎你是最棒的....";
-
- NSLog(@"objc.name = %@",objc.name);
-
- }
2.动态添加方法
开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决。(例如:会员机制)
- // 1.创建Person 对象
- Person *p = [[Person alloc] init];
- // 2.调用没有实现的eat方法
- [p performSelector:@selector(eat)];
- // 3.在person.m文件中调用方法:
- // 作用:调用了一个未实现方法时一定会来到这里
- + (BOOL)resolveInstanceMethod:(SEL)sel
- {
- // 判断方法名是不是eat
- if (sel == NSSelectorFromString(@"eat")) {
- // 动态添加eat方法
- /*
- 第一个参数:给哪个类添加方法
- 第二个参数:添加什么方法
- 第三个参数IMP:方法实现,函数入口:函数名
- 第四个参数:方法类型
- v 没有返回值
- @ 对象 id
- : 方法
- */
- class_addMethod(self, @selector(eat), eat, "v@:");
- return YES;
- }
- return [super resolveInstanceMethod:sel];
- }
-
- // 4.eat方法实现
- // self:方法调用者
- // _cmd:当前方法编号
- // 任何一个方法都能调用self,_cmd,其实任何一个方法都有这两个隐式参数
- void eat(id self, SEL _cmd)
- {
- NSLog(@"吃东西");
- }
- // 2.调用没有实现的run方法
- [p performSelector:@selector(run:) withObject:@10];
- // 3.在person.m文件中调用方法:
- // 作用:调用了一个未实现方法时一定会来到这里
- + (BOOL)resolveInstanceMethod:(SEL)sel
- {
- // 判断方法名是不是eat
- if (sel == NSSelectorFromString(@"run:")) {
- // 动态添加run方法
- /*
- 第一个参数:给哪个类添加方法
- 第二个参数:添加什么方法
- 第三个参数IMP:方法实现,函数入口:函数名
- 第四个参数:方法类型
- v 没有返回值
- @ 对象 id
- : 方法
- */
- class_addMethod(self, @selector(run:), run, "v@:@");
- return YES;
- }
- return [super resolveInstanceMethod:sel];
- }
-
- // 4.run方法实现
- // self:方法调用者
- // _cmd:当前方法编号
- // 任何一个方法都能调用self,_cmd,其实任何一个方法都有这两个隐式参数
- void run(id self, SEL _cmd, NSNumber *metre)
- {
- NSLog(@"跑了%@米",metre);
- }
3.方法交换(Swizzle 黑魔法)
平时我们app中用到的系统方法有很多,有时候我们需要对系统方法进行修改,已实现我们的需求和解决问题,我们不可能每个去改去处理.所以我们就要用到方法替换了.
使用场景: array
越界空等引起的崩溃, button
重复点击, image
空图片懒加载等很多功能.
- Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
- Method My_imageNameMethod = class_getClassMethod(self, @selector(My_imageNamed:));
- method_exchangeImplementations(imageNameMethod, My_imageNameMethod);
-
- // 不能在分类中重写系统方法imageNamed,因为会把系统的功能给覆盖掉,而且分类中不能调用super.
-
- + (instancetype)My_imageNamed:(NSString *)name
- {
- // 这里调用My_imageNamed,相当于调用imageNamed
- UIImage *image = [self My_imageNamed:name];
-
- if (image == nil) {
- NSLog(@"加载空的图片");
- }
-
- return image;
- }
4.归档解档
使用场景: 归档解档
不用运行时的归档方法:(还好只有5个属性,如果20个,30个或者后台突然增加了属性,这么直接写死估计代码就不灵了)
- // YYPerson.m
-
- #import "YYPerson.h"
-
- @implementation YYPerson
-
- // 当将一个自定义对象保存到文件的时候就会调用该方法
- // 在该方法中说明如何存储自定义对象的属性
- // 也就说在该方法中说清楚存储自定义对象的哪些属性
- - (void)encodeWithCoder:(NSCoder *)aCoder
- {
- NSLog(@"调用了encodeWithCoder:方法");
- [aCoder encodeObject:self.name forKey:@"name"];
- [aCoder encodeInteger:self.age forKey:@"age"];
- [aCoder encodeDouble:self.height forKey:@"height"];
- }
-
- // 当从文件中读取一个对象的时候就会调用该方法
- // 在该方法中说明如何读取保存在文件中的对象
- // 也就是说在该方法中说清楚怎么读取文件中的对象
- - (id)initWithCoder:(NSCoder *)aDecoder
- {
- NSLog(@"调用了initWithCoder:方法");
- //注意:在构造方法中需要先初始化父类的方法
- if (self=[super init]) {
- self.name=[aDecoder decodeObjectForKey:@"name"];
- self.age=[aDecoder decodeIntegerForKey:@"age"];
- self.height=[aDecoder decodeDoubleForKey:@"height"];
- }
- return self;
- }
- @end
runtime 归档接档
- //
- // Apply.m
- // 01-RuntimeSendMessage
- //
- // Created by Mac on 2019/11/1.
- // Copyright © 2019 Mac. All rights reserved.
- //
-
- #import "Apply.h"
- #import <objc/runtime.h>
-
-
- @implementation Apply
- // 归档的时候,系统会使用编码器把当前对象编码成二进制流
- - (void)encodeWithCoder:(NSCoder *)coder {
- unsigned int count = 0;
- // 获取所有实例变量
- Ivar *ivars = class_copyIvarList([self class], &count);
- // 遍历
- for (int i = 0; i < count; i++) {
- Ivar ivar = ivars[I];
- const char *name = ivar_getName(ivar);
- NSString *key = [NSString stringWithUTF8String:name];
- // KVC
- id value = [self valueForKey:key];
- // 编码
- [coder encodeObject:value forKey:key];
- }
-
- // 因为是 C 语言的东西,不会自动释放,所以这里需要手动释放。
- free(ivars);
- }
-
- // 解档的时候,系统会把二进制流解码成对象
- - (instancetype)initWithCoder:(NSCoder *)coder {
- self = [super init];
- if (self) {
- unsigned int count = 0;
- // 获取所有实例变量
- Ivar *ivars = class_copyIvarList([self class], &count);
- // 遍历
- for (int i = 0; i < count; i++) {
- Ivar ivar = ivars[I];
- const char *name = ivar_getName(ivar);
- NSString *key = [NSString stringWithUTF8String:name];
- id value = [coder decodeObjectOfClasses:[NSSet setWithObject:[self class]] forKey:key];
- // KVC
- [self setValue:value forKey:key];
- }
-
- free(ivars);
- }
- return self;
- }
-
- + (BOOL)supportsSecureCoding {
- return YES;
- }
-
- @end
- // 4.自动解归档
- Apply *apply = [Apply new];
- apply.name = @"张三";
- apply.age = @18;
- apply.nick = @"zhangsan";
-
- Apply *apply_2;
- NSString *fileName = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"archive.plist"];
-
- if (@available(iOS 11.0, *)) {
- NSData *data_1 = [NSKeyedArchiver archivedDataWithRootObject:apply requiringSecureCoding:YES error:nil];
- [data_1 writeToFile:fileName atomically:YES];
-
- NSData *data_2 = [[NSData alloc] initWithContentsOfFile:fileName];
- apply_2 = [NSKeyedUnarchiver unarchivedObjectOfClass:[Apply class] fromData:data_2 error:nil];
- } else {
-
- [NSKeyedArchiver archiveRootObject:apply toFile:fileName];
-
- apply_2 = [NSKeyedUnarchiver unarchiveObjectWithFile:fileName];
- }
-
- NSLog(@"name: %@, age: %@, nick: %@", apply_2.name, apply_2.age, apply_2.nick);
5.字典转模型
使用场景:字典转模型时,希望可以不用与字典中属性一一对应(案例:NSObject+JSONExtension.h
)
方法:可以使用runtime,遍历模型中有多少个属性,直接去字典中取出对应value,给模型赋值
- + (instancetype)modelWithDict:(NSDictionary *)dict
- {
- id objc = [[self alloc] init];
-
- int count = 0;
-
- // 成员变量数组 指向数组第0个元素
- Ivar *ivarList = class_copyIvarList(self, &count);
-
- // 遍历所有成员变量
- for (int i = 0; i < count; i++) {
-
- // 获取成员变量 user
- Ivar ivar = ivarList[i];
- // 获取成员变量名称
- NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
-
- // 获取成员变量类型
- NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
-
- // @"@\"User\"" -> @"User"
- type = [type stringByReplacingOccurrencesOfString:@"@\"" withString:@""];
- type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];
-
- // 成员变量名称转换key
- NSString *key = [ivarName substringFromIndex:1];
-
- // 从字典中取出对应value dict[@"user"] -> 字典
- id value = dict[key];
-
- // 二级转换
- // 并且是自定义类型,才需要转换
- if ([value isKindOfClass:[NSDictionary class]] && ![type containsString:@"NS"]) { // 只有是字典才需要转换
-
- Class className = NSClassFromString(type);
-
- // 字典转模型
- value = [className modelWithDict:value];
- }
-
- // 给模型中属性赋值 key:user value:字典 -> 模型
- if (value) {
- [objc setValue:value forKey:key];
- }
-
- }
-
- return objc;
- }
6.万能界面跳转方法
使用场景: 消息接收后跳转
利用runtime动态生成对象、属性、方法这特性,我们可以先跟服务端商量好,定义跳转规则,比如要跳转到A控制器,需要传属性id
、type
,那么服务端返回字典给我,里面有控制器名,两个属性名跟属性值,客户端就可以根据控制器名生成对象,再用kvc给对象赋值,这样就搞定了 ---O(∩_∩)O哈哈哈
- // 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数
- NSDictionary *userInfo = @{
- @"class": @"HSFeedsViewController",
- @"property": @{
- @"ID": @"123",
- @"type": @"12"
- }
- };
- - (void)push:(NSDictionary *)params
- {
- // 类名
- NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
- const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
- // 从一个字串返回一个类
- Class newClass = objc_getClass(className);
- if (!newClass)
- {
- // 创建一个类
- Class superClass = [NSObject class];
- newClass = objc_allocateClassPair(superClass, className, 0);
- // 注册你创建的这个类
- objc_registerClassPair(newClass);
- }
- // 创建对象
- id instance = [[newClass alloc] init];
- // 对该对象赋值属性
- NSDictionary * propertys = params[@"property"];
- [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
- // 检测这个对象是否存在该属性
- if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
- // 利用kvc赋值
- [instance setValue:obj forKey:key];
- }
- }];
- // 获取导航控制器
- UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
- UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
- // 跳转到对应的控制器
- [pushClassStance pushViewController:instance animated:YES];
- }
- - (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
- {
- unsigned int outCount, i;
- // 获取对象里的属性列表
- objc_property_t * properties = class_copyPropertyList([instance
- class], &outCount);
- for (i = 0; i < outCount; i++) {
- objc_property_t property =properties[i];
- // 属性名转成字符串
- NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
- // 判断该属性是否存在
- if ([propertyName isEqualToString:verifyPropertyName]) {
- free(properties);
- return YES;
- }
- }
- free(properties);
- return NO;
- }
具体使用和代码: https://github.com/HHuiHao/Universal-Jump-ViewController
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。