Runtime介绍

本文目录

-1.Runtime简介

1.Runtime简介

因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。

Runtime其实有两个版本:“modern”和 “legacy”。我们现在用的 Objective-C 2.0 采用的是现行(Modern)版的Runtime系统,只能运行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X较老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系统。这两个版本最大的区别在于当你更改一个类的实例变量的布局时,在早期版本中你需要重新编译它的子类,而现行版就不需要。

Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。

-2.Runtime相关的头文件

2.Runtime相关的头文件

ios的sdk中 usr/include/objc文件夹下面有这样几个文件

List.h
NSObjCRuntime.h
NSObject.h
Object.h
Protocol.h
a.txt
hashtable.h
hashtable2.h
message.h
module.map
objc-api.h
objc-auto.h
objc-class.h
objc-exception.h
objc-load.h
objc-runtime.h
objc-sync.h
objc.h
runtime.h

都是和运行时相关的头文件,其中主要使用的函数定义在message.h和runtime.h这两个文件中。 在message.h中主要包含了一些向对象发送消息的函数,这是OC对象方法调用的底层实现。 runtime.h是运行时最重要的文件,其中包含了对运行时进行操作的方法。 主要包括:

1.操作对象的类型的定义
/// An opaque type that represents a method in a class definition. 一个类型,代表着类定义中的一个方法
typedef struct objc_method *Method; /// An opaque type that represents an instance variable.代表实例(对象)的变量 typedef struct objc_ivar *Ivar; /// An opaque type that represents a category.代表一个分类 typedef struct objc_category *Category; /// An opaque type that represents an Objective-C declared property.代表OC声明的属性 typedef struct objc_property *objc_property_t; // Class代表一个类,它在objc.h中这样定义的 typedef struct objc_class *Class; struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;
这些类型的定义,对一个类进行了完全的分解,将类定义或者对象的每一个部分都抽象为一个类型type,对操作一个类属性和方法非常方便。 OBJC2_UNAVAILABLE标记的属性是Ojective-C 2.0不支持的,但实际上可以用响应的函数获取这些属性,例如:如果想要获取Class的name属性,可以按如下方法获取:
Class classPerson = Person.class; // printf("%s\n", classPerson->name); //用这种方法已经不能获取name了 因为OBJC2_UNAVAILABLE const char *cname = class_getName(classPerson); printf("%s", cname); // 输出:Person
2.函数的定义

对对象进行操作的方法一般以object_开头

对类进行操作的方法一般以class_开头

对类或对象的方法进行操作的方法一般以method_开头

对成员变量进行操作的方法一般以ivar_开头

对属性进行操作的方法一般以property_开头开头

对协议进行操作的方法一般以protocol_开头

根据以上的函数的前缀 可以大致了解到层级关系。对于以objc_开头的方法,则是runtime最终的管家,可以获取内存中类的加载信息,类的列表,关联对象和关联属性等操作。

例如:使用runtime对当前的应用中加载的类进行打印,别被吓一跳。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { unsigned int count = 0; Class *classes = objc_copyClassList(&count); for (int i = 0; i < count; i++) { const char *cname = class_getName(classes[i]); printf("%s\n", cname); } }
-3.技术点和应用场景

3.技术点和应用场景

在以下的代码中,都用到了Person类,Person类知识简单的定义了一个成员变量和两个属性

@interface Person : NSObject { @private float _height; } @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) int age; @end

3_1.获取属性\成员变量列表

对于获取成员变量的列表可以使用 class_copyIvarList函数,如果想要获取属性列表可以使用 class_copyPropertyList函数,使用的示例如下:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { Class classPerson = NSClassFromString(@"Person"); // 与下面一句效果一样,可以不用导入头文件 // Class clazz = Person.class; unsigned int count = 0; Ivar *ivarList = class_copyIvarList(classPerson, &count); // 获取成员变量数组 for (int i = 0; i < count; i++) { const char *cname = ivar_getName(ivarList[i]); // 获取成员变量的名字 NSString *name = [NSString stringWithUTF8String:cname]; NSLog(@"%@", name); } NSLog(@"-------------------分割线------------------"); objc_property_t *propertyList = class_copyPropertyList(classPerson, &count); // 获取属性数组 for (int i = 0; i < count; i++) { const char *cname = property_getName(propertyList[i]); NSString *name = [NSString stringWithUTF8String:cname]; NSLog(@"%@", name); } }
以上代码的输出为:
2015-06-05 22:28:16.194 runtime终极[4192:195757] _height 2015-06-05 22:28:16.195 runtime终极[4192:195757] _age 2015-06-05 22:28:16.195 runtime终极[4192:195757] _name 2015-06-05 22:28:16.195 runtime终极[4192:195757] -------------------分割线------------------ 2015-06-05 22:28:16.195 runtime终极[4192:195757] name 2015-06-05 22:28:16.195 runtime终极[4192:195757] age

为什么会有上面的输出结果,因为@property会做三份工作: 
1.生成一个带下划线的成员变量 
2.生成这个成员变量的get方法 
3.生成这个成员变量的set方法
因此会输出三个成员变量_height、_age和_name。需要注意的是属性名是不带下划线的,和定义时的名字一样。因此可以说:ivarList可以获取到@property关键字定义的属性 ,而propertyList不可以获取到成员变量。也就是:使用ivarList是可以将所有的成员变量和属性都获取的。

当属性是readonly的而且重写了getter时,这种情况还是会遇见的,比如一个属性是计算型属性,需要依赖其他属性的值计算而来。此时生成的带下划线的成员变量就不在了, 通过ivarList不能获取该属性了。因此当有这种值的时候,无论使用ivarList还是使用propertyList都无法获取全部的属性或变量。

在进行下一个话题之前:先需要弄清楚另一个问题:对于一个readonly的属性,到底是didSet+set好,还是重写getter好?

大部分的readonly的属性是计算型的,依旧是依赖于其他属性,因此可以使用didSet+set,也就是在其他属性的set方法内,将本属性set。 但是didSet+set有时候完全没有必要,不符合懒加载的规则,浪费了计算能力,用重写getter的方法好一些。 因此重写getter总是会好一点。

回归正题:在KVC时,想要获取全部的成员变量和属性, 怎么办呢?

首先要了解setValue: forKeyPath:方法的底层实现:以name属性为例
1.首先先去类的方法列表去寻找有木有setName:,如果有,就直接调用[person setName:value]
2.找找有没有带下划线的成员变量_name,如果有 _name = value;
3.找有没有成员变量name,如果有 name = value;
4.如果都没有找到,就直接报错。
因此对于readonly的又重写了getter的属性而言:如果对propertyList的属性一次使用kvc,就会报错,因此为保证代码正常,不能使用propertyList的属性进行kvc;
另外:这种属性本来就是计算型的了,为什么还有为它赋值呢,因此对它进行kvc也不合情理。
当使用ivaList时,直接就无法获取到这种属性,因此是kvc的最佳方案。再者,使用propertyList无法获取成员变量(_height),无法对成员变量进行赋值。而使用ivaList是可以将该赋值的成员变量都获取的。

以上就是使用ivar还是使用property进行kvc的论证。

话题外: 很多类 有些成员变量 既没有暴露给外部调用的getter又没有setter,只是用@private声明了一下:为什么??
猜测是:是方法调用时使用的中间变量,因为是跟随对象产生,不适合使用静态static,又因为外部不会使用,所以没必要给外部提供接口,但是可能有好几个方法都需要这个量,不适合做局部变量,所以就这样定义了。

对于这种情况,要想不对这种成员变量赋值,在KVC时又可以这样改进一下,通过ivarList获取,去掉propertyList中没有的成员变量,这样就过滤掉了上面的那种成员变量了。

3_1_1.应用1:KVC字典转模型

获取属性\成员列表一个重要的应用就是,一次取出模型中的属性\成员变量,根据它的名字获取字典中的key然后取出字典中这个key对应的value,使用setValue: forKeyPath:方法设置值。为什么要这样,而不再使用方法setValuesForKeysWithDictionary:。因为在setValuesForKeysWithDictionary:方法内部会执行这样一个过程
遍历字典里面的所有key,一个一个取出来,遍历每个key按照以下过程
1.取出key,
2.取出key的value,即dict[key],直接给模型的属性\成员变量赋值
3.怎么给模型的属性赋值,使用方法setValue:value forKeyPath:key进行赋值,这个方法的执行过程在前面已经提到。

因此,开发中经常遇到的字典中的key比模型中多时,会出现的 this class is not key-value compliant for ‘xxx’这个bug就很好解释了,通常是因为字典中的key,比模型中的属性\成员变量多。那么当模型中的属性比字典中多时,使用setValuesForKeysWithDictionary:会不不会有bug呢?经测试:当多出来的属性是对象数据类型时,为null,当属性是基本数据类型时,会有一个系统默认值(如int为0)。

因此使用逐一为属性赋值的方法进行KVC:

Class clazz = Person.class; unsigned int count = 0; Person *person = [[Person alloc]init]; NSDictionary *dict = @{@"name":@"zhangsan",@"age":@19, @"height": @1.75}; Ivar *ivars = class_copyIvarList(clazz, &count); // NSLog(@"%tu", count); // 3 for (int i = 0; i < count; i++) { const char *cname = ivar_getName(ivars[i]); NSString *name = [NSString stringWithUTF8String:cname]; NSString *key = [name substringFromIndex:1]; // 去掉'_' [person setValue:dict[key] forKey:key]; } NSLog(@"%@", person); // 已经重写了description方法
输出是:
<Person, 0x7ff15b80f230>{ name = zhangsan, height = 1.750000, age = 19}

使用这种方式进行kvc,即使字典中的key多的时候也不会有bug,但是新的问题出现了,如果模型中的属性比字典中的key多便会出现bug而且:如果多的是对象类型不会有bug,该属性的值为null,如果是基本数据类型就会出错could not set nil as the value for the key ‘xxx’。例如,将上面的字典改为:

NSDictionary *dict = @{@"age":@19, @"height": @1.75}; // 去掉了name NSString类型
修改之后输出为:
<Person, 0x7f996263fbd0>{ name = (null), height = 1.750000, age = 19}
如果将字典改为:
NSDictionary *dict = @{@"name":@"zhangsan",@"age":@19}; // 去掉了height float类型
程序直接崩溃。

如何解决上面的bug:可以在setValue:value forKeyPath:key方法调用之前进行如下处理:取出属性对应的类型,如果类型是基本数据类型,value替换为默认值(如int对应默认值为0)。

runtime提供的ivar_getTypeEncoding函数可以获取到属性的类型,返回值代表的含义如下:


height是float类型对应的TypeCode是"f"因此可以进行过滤一下,代码改动如下:
Class clazz = Person.class; unsigned int count = 0; Person *person = [[Person alloc]init]; NSDictionary *dict = @{@"name":@"zhangsan",@"age":@19, @"height": @1.75}; Ivar *ivars = class_copyIvarList(clazz, &count); for (int i = 0; i < count; i++) { const char *cname = ivar_getName(ivars[i]); NSString *name = [NSString stringWithUTF8String:cname]; NSString *key = [name substringFromIndex:1]; const char *coding = ivar_getTypeEncoding(ivars[i]); // 获取类型 NSString *strCode = [NSString stringWithUTF8String:coding]; id value = dict[key]; if ([strCode isEqualToString:@"f"]) {// 判断类型是否是float value = @(0.0); } [person setValue:value forKey:key]; } NSLog(@"%@", person);
这样就可以正常执行了,输出为:
<Person, 0x7fc75d004a00>{ name = zhangsan, height = 0.000000, age = 19}
3_1_2.应用2:NSCoding归档和解档
获取属性\成员列表另外一个重要的应用就是进行归档和解档,其原理和上面的kvc基本上一样,这里只是展示一些代码:
- (void)encodeWithCoder:(NSCoder *)aCoder { unsigned int count = 0; Ivar *ivars = class_copyIvarList(self.class, &count); for (int i = 0; i < count; i++) { const char *cname = ivar_getName(ivars[i]); NSString *name = [NSString stringWithUTF8String:cname]; NSString *key = [name substringFromIndex:1]; id value = [self valueForKey:key]; // 取出key对应的value [aCoder encodeObject:value forKey:key]; // 编码 } } - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { unsigned int count = 0; Ivar *ivars = class_copyIvarList(self.class, &count); for (int i = 0; i < count; i++) { const char *cname = ivar_getName(ivars[i]); NSString *name = [NSString stringWithUTF8String:cname]; NSString *key = [name substringFromIndex:1]; id value = [aDecoder decodeObjectForKey:key]; // 解码 [self setValue:value forKey

转载于:https://www.cnblogs.com/LifeTechnologySupporter/p/5025958.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值