OC中的runtime是什么|OC中的runtime是什么 (runtime可以干什么?)

什么是runtime?

  • 从字面上理解为 运行时,简单来说 runtime是一个实现OC语言的C库。
  • 我们编写的OC代码会在程序运行过程中会转换成runtime的c语言代码,当然我们也可以直接运用runtime提供的APi进行魔法操作。
  • OC是运行时动态语言在运行时将对象类型确定、方法调用、代码和资源的装载等。
Runtime 概念 及术语
1. Object(objc_object) 实例
/// Represents an instance of a class. struct objc_object { Class isaOBJC_ISA_AVAILABILITY; }; /// A pointer to an instance of a class. typedef struct objc_object *id;

  • 这里我们看到 objc_object 结构体 里面只包含一个 class类型 的isa 指针。
    这里也就说明 一个Object (实例)唯一保存的就是他所属Class(类)的地址,当我们对一个 实例进行方法调用时候。
    例如 [object message] ,会通过objc_object结构体的 isa指针 去找到到对应的 objec_class 结构体。
2. Class(objc_class) 类
/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; struct objc_class { Class _Nonnull isa; // objc_class 结构体的实例指针#if !__OBJC2__ Class _Nullable super_class; // 指向父类的指针 const char * _Nonnull name; // 类的名字 long version; // 类的版本信息,默认为 0 long info; // 类的信息,供运行期使用的一些位标识 long instance_size; // 该类的实例变量大小; struct objc_ivar_list * _Nullable ivars; // 该类的实例变量列表 struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定义的列表 struct objc_cache * _Nonnull cache; // 方法缓存 struct objc_protocol_list * _Nullable protocols; // 遵守的协议列表 #endif

  • 这里我们看到 objc_object 结构体里面定义了很多变量 通过命名不难发现 结构体里面保存了 指向父类的 指针、类的名字、版本、实例大小、实例变量 list 方法 list 缓存 协议列表 等。一个类包含的信息不就正式这些吗?
    objc_class结构体 的第一个成员变量是 isa指针,isa指针 保存的是所属类的结构体的实例指针也就是对象。
    所以Class(类)的本质就是一个对象, 称之为 类对象 。
  • 类对象就是一个结构体 struct objc_class ,这个结构体存放的数据 称之为 元数据 (metadata)
3. Meta Class(元类)
  • 从上面可以看出,对象(objc_object)结构体的 isa指针 指向的是对应 类对象(objc_class)结构体 ,那么类对象 (objc_class)的isa指向什么?答案指向 元类
  • 元类 是类对象(objc_class) 的类 听起来绕口 下面看它的作用就会豁然开朗
  • 在OC中,每当我们创建一个类。在编译时就会创建一个元类,而这个元类的对象 就是我们创建的这个类。(我们创建的类本质也是一个对象objc_class)
  • 那么为什么要有元类?我们看类对象( objc_class结构体) ivars 用来存放属性变量,objc_method_list 用来存放 实例方法 (-方法),那一些静态变量 和 类(+)方法 哪里去了? 没错,他们就是存放在 元类的methodLists和ivars里面。
4. Method(objc_method)
/// An opaque type that represents a method in a class definition. /// 代表类定义中一个方法的不透明类型 typedef struct objc_method *Method; struct objc_method { SEL _Nonnull method_name; /// 方法名 char * _Nullable method_types; /// 方法类型 IMP _Nonnull method_imp; ///方法实现 };

  • SEL _method_name (方法名)释义
/// An opaque type that represents a method selector. typedef struct objc_selector *SEL;

SEL: 是一个指向 objc_selecter 结构体的指针,但是在runtime相关头文件中 并没有找到明确的定义,经过测试打印 得出 猜测 结论 SEL 只是一个保存方法名的字符串。
  • char *_Nullable method_types(方法类型)释义
方法类型 method_types 是个字符串,用来存储方法的参数类型和返回值类型。
  • IMP method_imp(方法实现)释义
/// A pointer to the function of a method implementation. #if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); #endif

IMP 的实质是一个函数指针,所指向的就是方法的实现。IMP用来找到函数地址,然后执行函数。
  • objc_class (类对象)结构体中的 methodLists(方法列表)中存放的元素就是Method(方法)
  • 由此看出Method将 SEL(方法名)和IMP(函数指针)相关联。当对一个对象 发送消息的时候 会通过SEL(方法名)去找到IMP(函数指针)进行执行。
消息机制的基本原理
根据上面分析,大家脑海中应该会有一个方法调用,也就是消息发送的全过程。下面总结一下
///实例方法调用 Person * p = [[Person alloc]init]; [p eat]; ///类方法调用 [Person sleep];

#import "Person.h"@implementation Person -(void)eat; { NSLog(@"吃饭"); }+(void)sleep; { NSLog(@"睡觉"); } @end

对象方法调用过程
  1. 通过 实例(p) 的isa指针 找到 实例( p)的 Class(objc_class类对象);
  2. 在Class(objc_class类对象)的结构体中找到 cache(方法缓存)散列表 中根据method_name 看里面有没有对应的IMP(方法实现);
  3. 如果没有找到 就继续在 Class(objc_class类对象)的methodLists(方法列表中)寻找对应的selector ,如果找到,填充到cache(方法缓存)中,并返回 selector;
  4. 如果Class(类)中没有找到这个selector ,就继续在他的superClass中寻找;
  5. 一旦找到对应的selector,就直接执行对应selector方法实现的IMP(方法实现);
  6. 若找不到对应的selector,即将进入 runtime的 消息转发机制。消息转发不做处理 程序发生崩溃。
类方法的调用
  1. 上面我们有说过 我们创建的类 在编译时会创建一个元类,而这个元类的对象 就是我们创建的这个类
  2. 通过Class(objc_class类对象) 的isa指针找到所属元类;
  3. 在进行上述后续操作。
runtime可以干什么?
  • 获取类中所有的实例变量
class_copyIvarList()返回一个指向类的成员变量和属性数组的指针 class_copyPropertyList()返回一个指向类的属性数组的指针

///我是.m #import "Person.h"@interface Person ()@property (nonatomic,copy) NSString *name; @property (nonatomic,copy) NSString *age; @property (nonatomic,copy) NSString *sex; @end@implementation Person { NSNumber *phone; }@end

#import #import "Person.h" #import // 包含对类、成员变量、属性、方法的操作 #import // 包含消息机制 int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); //返回所有的属性和实例变量 class_copyIvarList unsigned int methodCount = 0; Ivar * ivars = class_copyIvarList([Person class], &methodCount); for (unsigned int i = 0; i< methodCount; i++) { Ivar ivar = ivars[i]; const char * name = ivar_getName(ivar); const char * type = ivar_getTypeEncoding(ivar); NSLog(@" Persons成员变量属性为%s==类型%s",name,type); } free(ivars); //只返回 属性方法 class_copyPropertyList unsigned int method_Count = 0; objc_property_t * properties = class_copyPropertyList([Person class], &method_Count); for (unsigned int i = 0; i< method_Count; i++) { objc_property_t property = properties[i]; const char * properName = property_getName(property); NSLog(@" Person 属性为%s",properName); } free(properties); } return 0; }

2019-10-29 14:18:17.595647+0800 runtime[7816:359804] Hello, World! 2019-10-29 14:18:17.596917+0800 runtime[7816:359804]Persons成员变量属性为phone==类型@"NSNumber" 2019-10-29 14:18:17.597031+0800 runtime[7816:359804]Persons成员变量属性为_name==类型@"NSString" 2019-10-29 14:18:17.597093+0800 runtime[7816:359804]Persons成员变量属性为_age==类型@"NSString" 2019-10-29 14:18:17.597145+0800 runtime[7816:359804]Persons成员变量属性为_sex==类型@"NSString" 2019-10-29 14:18:17.597213+0800 runtime[7816:359804]Person 属性为name 2019-10-29 14:18:17.597608+0800 runtime[7816:359804]Person 属性为age 2019-10-29 14:18:17.597708+0800 runtime[7816:359804]Person 属性为sex Program ended with exit code: 0

  • 使用runtime动态添加一个类
objc_allocateClassPair:注册一个新类或者元类如想让这个类成为基类那么参数superclass指针定为nil.参数extraByte是分配给类和元类对象尾部的索引ivars的字节数,通常指为0 objc_registerClassPair:当创建完新类后,需要调用这个方法注册这个类 之后这个类才可以在程序中使用 objc_disposeClassPair:用于销毁一个类及其元类,需要注意的是,如果程序运行中还存在类或者其子类的实例,那么就不能调用此方法

int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); //创建一个新类 Class myClass = objc_allocateClassPair([NSObject class], "myClass", 0); //添加ivar //@encode(aType):返回该类型的字符串 class_addIvar(myClass, "_address", sizeof(NSString*), log2(sizeof(NSString*)),@encode(NSString*)); class_addIvar(myClass, "_age", sizeof(NSUInteger), log2(sizeof(NSUInteger)), @encode(NSUInteger)); //注册类 objc_registerClassPair(myClass); //创建实例 id object = [[myClass alloc] init]; //为ivar赋值 [object setValue:@"china" forKey:@"address"]; [object setValue:@20 forKey:@"age"]; NSLog(@"address=%@,age=%@",[object valueForKey:@"address"],[object valueForKey:@"age"]); //当类或者它的子类的实例还存在 则不能调用objc_disposeClassPair object = nil; //销毁 objc_disposeClassPair(myClass); } return 0; }

2019-10-29 15:06:02.931562+0800 runtime[8254:384872] Hello, World! 2019-10-29 15:06:02.936912+0800 runtime[8254:384872] address=china,age=20 Program ended with exit code: 0

  • 在category中增加属性(众所周知正常来说不可以添加但是用runtime完全可以实现)
    给Person类添加个Category
#import #import"Person.h" @interface Person (Category) //不会生成添加属性的getter和setter方法,必须我们手动生成 @property (nonatomic, copy) NSString *phone; @end

#import "Person+Category.h" #import #import @implementation Person (Category) // 定义关联的key static const char *key = "phone"; /** phone的getter方法 */ -(NSString *)phone { // 根据关联的key,获取关联的值。 return objc_getAssociatedObject(self, key); } /** phone的setter方法 */-(void)setPhone:(NSString *)phone { // 第一个参数:给哪个对象添加关联 // 第二个参数:关联的key,通过这个key获取 // 第三个参数:关联的value // 第四个参数:关联的策略 objc_setAssociatedObject(self, key, phone, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }@end

#import #import "Person.h" #import "Person+Category.h" #import // 包含对类、成员变量、属性、方法的操作 #import // 包含消息机制 int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); Person * per = [[Person alloc]init]; per.phone = @"111000001011"; NSLog(@"Category不能添加属性?可以的我用Category为Person添加了一个phone属性,phone=%@",per.phone); } return 0; }

2019-10-29 15:26:13.400545+0800 runtime[8485:392320] Category不能添加属性?可以的我用Category为Person添加了一个phone属性,phone=111000001011 Program ended with exit code: 0

  • runtime进行消息传递
#import "Person.h"@interface Person ()@property (nonatomic,copy) NSString *name; @property (nonatomic,copy) NSString *age; @property (nonatomic,copy) NSString *sex; @end@implementation Person{ NSNumber *phone; }-(void)callPhone; { NSLog(@"电话正在拨打中。。。"); } @end

int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); Person * person = [[Person alloc]init]; //OC中,当调用某个对象的方法时,其实质上就是向该对象发送了一条消息,比如: // //[person callPhone]; 本质为以下代码 objc_msgSend(person, @selector(callPhone)); } return 0;

2019-10-29 15:53:47.954497+0800 runtime[8667:403765] Hello, World! 2019-10-29 15:53:47.955521+0800 runtime[8667:403765] 电话正在拨打中。。。 Program ended with exit code: 0

  • 使用runtime动态添加方法[实现消息转发] (https://www.jianshu.com/p/7f868b0e2575)
int main(int argc, const char * argv[]) { @autoreleasepool { // insert code here... NSLog(@"Hello, World!"); //Person类中并不存在 sendMassage方法 Person * person = [[Person alloc]init]; SEL aSel = NSSelectorFromString(@"sendMassage"); [person performSelector:aSel]; } return 0; }

运行
-[Person sendMassage]: unrecognized selector sent to instance 0x100626da0

运行崩溃此异常为没有找到sendMassage ,消息转发过程以程序崩溃结束 但是可以利用runtime补救 程序消息转发详解请看这里[实现消息转发] (https://www.jianshu.com/p/7f868b0e2575)
//person.m 没有找到sendMassage方法调用callPhone方法吧-(void)callPhone; { NSLog(@"电话正在拨打中。。。"); }//动态补加方法 +(BOOL)resolveInstanceMethod:(SEL)sel { NSString *str = NSStringFromSelector(sel); if ([str isEqualToString:@"sendMassage"]) { // 得到实例方法 Method_Nonnull m =class_getInstanceMethod(self, @selector(callPhone)); //返回方法的调用地址 IMP imp = method_getImplementation(m); //给类添加一个新的方法和该方法的具体实现 class_addMethod(self, @selector(sendMassage), imp, ""); } return [super resolveInstanceMethod:sel]; }

  • 利用kvc和runtime暴力访问私有属性和变量
Person * person = [[Person alloc]init]; //kvo 暴力访问 私有属性和私有变量[person setValue:@"小明" forKey:@"name"]; NSString * pname = [person valueForKey:@"name"]; [person setValue:@"19" forKey:@"age"]; NSString * page = [person valueForKey:@"age"]; [person setValue:@"男" forKey:@"sex"]; NSString * psex = [person valueForKey:@"sex"]; [person setValue:@1111111111 forKey:@"phone"]; NSString * pphone = [person valueForKey:@"phone"]; NSLog(@"姓名%@,年龄%@,性别%@,电话%@",pname,page,psex,pphone); //runtime如何来做? unsigned int count = 0; //获取所有属性变量名字 Ivar * members= class_copyIvarList([Person class], &count); for (unsigned int i = 0; i

  • 使用runtime进行方法交换
    为什么在load里面交换请看 [load方法和initialize方法异同] (https://www.jianshu.com/p/d4ce4530246e)
///我是Person.m + (void)load { //获取实例的方法 Method M1 =class_getInstanceMethod(self, @selector(callBB机)); Method M2 = class_getInstanceMethod(self, @selector(callPhone)); //交换方法 method_exchangeImplementations(M1, M2); //获取类方法 Method cl1 =class_getClassMethod(self, @selector(callQQ)); Method cl2 = class_getClassMethod(self, @selector(callWX)); //交换方法 method_exchangeImplementations(cl1, cl2); }-(void)callBB机; { NSLog(@"打bb机中.."); }-(void)callPhone; { NSLog(@"电话正在拨打中。。。"); }+(void)callQQ { NSLog(@"打QQ中.."); }+(void)callWX { NSLog(@"打微信中.."); }

int main(int argc, const char * argv[]) { @autoreleasepool {Person * person = [[Person alloc]init]; NSLog(@"打电话------------"); [person callPhone]; NSLog(@"打BB机------------"); [person callBB机]; NSLog(@"打QQ------------"); [Person callQQ]; NSLog(@"打WX------------"); [Person callWX]; } return 0;

运行
2019-10-29 17:56:41.131014+0800 runtime[9536:457820] 打电话------------ 2019-10-29 17:56:41.131757+0800 runtime[9536:457820] 打bb机中.. 2019-10-29 17:56:41.131928+0800 runtime[9536:457820] 打BB机------------ 2019-10-29 17:56:41.131989+0800 runtime[9536:457820] 电话正在拨打中。。。 2019-10-29 17:56:41.132038+0800 runtime[9536:457820] 打QQ------------ 2019-10-29 17:56:41.132083+0800 runtime[9536:457820] 打微信中.. 2019-10-29 17:56:41.132127+0800 runtime[9536:457820] 打WX------------ 2019-10-29 17:56:41.132173+0800 runtime[9536:457820] 打QQ中..

  • 使用runtime为model赋值
建PersonModel类
///我是.h #import @interface PersonModel : NSObject @property (nonatomic,copy) NSString *name; @property (nonatomic,copy) NSString *age; @property (nonatomic,copy) NSString *sex; @property (nonatomic,copy) NSString *phone; +(instancetype)modelWithDict:(NSDictionary*)dict; @end

///我是.m #import "PersonModel.h"#import @implementation PersonModel+(instancetype)modelWithDict:(NSDictionary*)dict; { id object = [[self alloc]init]; unsigned int count = 0; Ivar * ivarList = class_copyIvarList(self, &count); for (int i = 0; i

main函数赋值
int main(int argc, const char * argv[]) { @autoreleasepool {NSDictionary * data= https://www.it610.com/article/@{@"name":@"小花花", @"age":@"10", @"sex":@"女", @"phone":@"1111111111111"}; PersonModel * model =[PersonModel modelWithDict:data]; NSLog(@"name:%@.age:%@.sex:%@.phone:%@",model.name,model.age,model.sex,model.phone); } return 0; }

运行 模型赋值成功
2019-10-30 11:21:59.319949+0800 runtime[18571:872011] name:小花.age:10.sex:女.phone:1111111111111 Program ended with exit code: 0

  • 自动归档解档
当我们需要将一个对象进行归档时,都要让该对象的类遵守NSCoding协议,再实现归档和接档方法。以上面的PersonModel为例
/** *将对象写入某个文件时需要调用,在该方法中说明哪些属性需要存储 */ - (void)encodeWithCoder:(NSCoder *)coder; { [coder encodeObject:self.name forKey:@"name"]; [coder encodeObject:self.age forKey:@"age"]; [coder encodeObject:self.sex forKey:@"sex"]; [coder encodeObject:self.phone forKey:@"phone"]; } /** *从文件中解析对象时会调用,在该方法中解析对象的属性 */ - (nullable instancetype)initWithCoder:(NSCoder *)coder; { if (self = [super init]) { // 解析之后要赋值给属性 _name= [coder decodeObjectForKey:@"name"]; _age= [coder decodeObjectForKey:@"age"]; _sex= [coder decodeObjectForKey:@"sex"]; _phone = [coder decodeObjectForKey:@"phone"]; } return self; }//

如果这个类属性上百个那一个一个的写会累死 而且还很low
应该使用runtime来进行
- (nullable instancetype)initWithCoder:(NSCoder *)coder {if (self = [super init]) { Class c = self.class; // 截取类和父类的成员变量 while (c && c != [NSObject class]) { unsigned int count = 0; Ivar *ivars = class_copyIvarList(c, &count); for (int i = 0; i < count; i++) {NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])]; id value = https://www.it610.com/article/[coder decodeObjectForKey:key]; [self setValue:value forKey:key]; } // 获得c的父类 c = [c superclass]; free(ivar); }} return self; }/** *从文件中解析对象时会调用,在该方法中解析对象的属性 */ - (void)encodeWithCoder:(NSCoder *)coder; {Class c = self.class; // 截取类和父类的成员变量 while (c && c != [NSObject class]) { unsigned int count = 0; Ivar *ivars = class_copyIvarList(c, &count); for (int i = 0; i < count; i++) { Ivar ivar = ivars[i]; NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)]; id value = [self valueForKey:key]; [coder encodeObject:value forKey:key]; } c = [c superclass]; // 释放内存 free(ivar); }}

参考资料 【OC中的runtime是什么|OC中的runtime是什么 (runtime可以干什么?)】博文:『Runtime』详解(一)基础知识
博文: iOS Runtime详解

    推荐阅读