前言
Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码,通过 Foundation 框架的NSObject类定义的方法,通过对 runtime 函数的直接调用。大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。
1、概念
RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
𝟙、ios进阶之传递消息
𝟚
2、作用
1.动态交换两个方法的实现
2.为类别添加属性(我们知道类别是不能扩展属性的,只能扩展方法,但可以运行时实现,通过为类增加属性)
3.获取某个类的所有成员变量和成员方法
4.实现NSCoding的自动归档和自动解档
5.动态添加对象的成本变量和成员方法
作用:当硬件内存过小的时候,如果我们将每个方法都直接加到内存当中去,但是几百年不用一次,这样就造成了浪费,所有采取动态添加
6.实现字典和模型的自动转换
3、详解:
一、动态交换方法
1.在自定义类DWExchangeTwoMethod.m中
- (instancetype)init {
if (self = [super init]) {
Method workMethod = class_getInstanceMethod([DWExchangeTwoMethod class], @selector(work));
Method playMethod = class_getClassMethod([DWExchangeTwoMethod class], @selector(play));
method_exchangeImplementations(workMethod, playMethod);
}
return self;
}
- (void)play {
NSLog(@"玩...");
}
+ (void)work {
NSLog(@"工作...");
}
抛砖引玉:可以交换方法,防止数组越界导致的崩溃
2、然后在其他类调用
DWExchangeTwoMethod *exchangeMethod = [DWExchangeTwoMethod new];
[exchangeMethod play];
由上可以看出,调用的是play
方法
但打印结果为 【玩...】,证明已经动态交换成功
二、为分类添加属性
一般情况下,分类不可以添加属性,但用runtime却可以实现
- (NSString *)height {
return objc_getAssociatedObject(self, @"height");
}
- (void)setHeight:(NSString *)height {
objc_setAssociatedObject(self, @"height", height, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
注:
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
id object :表示关联者,是一个对象,变量名理所当然也是object
const void *key :获取被关联者的索引key
id value :被关联者,这里是一个block
objc_AssociationPolicy policy : 关联时采用的协议,有assign,retain,copy等协议,一般使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
objc_getAssociatedObject(id object, const void *key)
参数解析同上
三、获取实例变量列表
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([DWExchangeTwoMethod class], &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
const char* name = ivar_getName(ivar);
const char* type = ivar_getTypeEncoding(ivar);
NSLog(@"变量名%s :%s", name, type);
}
// 此时要注意, 尽管在ARC模式下, 取出变量后要依然手动释放内存, 利用free()方法即可:
free(ivars);
注:
class_copyPropertyList与class_copyIvarList
class_copyPropertyList
返回的仅仅是对象类的属性(@property申明的属性),而class_copyIvarList
返回类的所有属性和变量(包括在@interface大括号中声明的变量)
四、实现NSCoding的自动归档和自动解档;
(不用对每个属性edcode和decode了,如果几十个属性一个个的encode和decode真的很麻烦啊,使用运行时可以遍历出每个对象的属性,数组的方式遍历eccode,decode)
#import "DWPerson.h"
#define kname @"name"
#define kidCard @"idCard"
#define kage @"age"
#define kothers @"others"
@implementation DWPerson
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_name forKey:kname];
[aCoder encodeObject:_idCard forKey:kidCard];
[aCoder encodeInt:_age forKey:kage];
[aCoder encodeObject:_others forKey:kothers];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
_name = [aDecoder decodeObjectForKey:kname];
_age = [aDecoder decodeIntForKey:kage];
_idCard = [aDecoder decodeObjectForKey:kidCard];
_others = [aDecoder decodeObjectForKey:kothers];
}
return self;
}
@end
上面每个类都要写一次,所以我们添加个分类会更加简化
#import "NSObject+ArchiveExtension.h"
#import <objc/runtime.h>
@implementation NSObject (ArchiveExtension)
// 先对当前类进行编码,然后对父类进行编码,如果父类是NSObject就结束编码
- (void)encode:(NSCoder *)aCoder {
// 一层层父类往上查找,对父类的属性执行归解档方法
Class c = self.class;
while (c && c != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(c, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(ivars);
c = [c superclass];
}
}
- (void)decode:(NSCoder *)aDecoder {
Class c = self.class;
while (c && c != [NSObject class]) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(c, &outCount);
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
//注意与归档的顺序不同
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(ivars);
c = [c superclass];
}
}
@end
这时,可以在DWPerson.m中取代之前的代码
- (void)encodeWithCoder:(NSCoder *)aCoder {
[self encode:aCoder];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
[self decode:aDecoder];
}
return self;
}
当然还可以更加简洁,把NSCoding的定义成宏
#ifndef Archive_h
#define Archive_h
#import "NSObject+ArchiveExtension.h"
#define ArchiveImplemention \
\
- (instancetype)initWithCoder:(NSCoder *)aDecoder {\
if (self = [super init]) {\
[self decode:aDecoder];\
}\
return self;\
}\
\
- (void)encodeWithCoder:(NSCoder *)aCoder {\
[self encode:aCoder];\
} //最后这句不用斜线
#endif /* Archive_h */
注:
如果有空行,需要加斜线补充
使用:
//宏定义
#import "Archive.h"
@implementation DWPerson
ArchiveImplemention
@end
五、动态添加方法
在viewController使用
- (IBAction)clickBtn:(id)sender {
[self performSelector:@selector(play)];
}
void play(id self, SEL _cmd) {
NSLog(@"动态添加成功");
UIViewController *vc = [UIViewController new];
vc.view.backgroundColor = [UIColor redColor];
[self presentViewController:vc animated:YES completion:nil];
}
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == NSSelectorFromString(@"play")) {
class_addMethod(self, aSEL, (IMP) play, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
六、实现字典和模型的自动转换
获取属性的列表的方法是字典转模型的比较核心的方法