简介
runtime 是OC一个很重要的机制,运行时机制,我们平时写的OC代码都会被转成runtime代码, runtime是一套比较底层的纯C语言的API。 runtime最主要的应该就是消息机制了,OC调用函数为消息发送,属于动态调用,编译的时候不能决定真正调用的哪个函数,这个和C,C++不同,C,C++在一个函数没有实现的时候编译也是通过不了的,而OC则不会。
查看runtime代码
查看runtime代码需要在终端中,cd 到想要查看文件的指定路径,输入clang -rewrite-objc xxx.m,但是记不清从Xcode那个版本之后,输入这个命令会报
UIKit/UIKit.h' file not found 错误,后来经过查找资料,编译之前,需要一些编译环境 和库的参数配置,或者是三方库头文件,需要借助 xcrun 命令。
xcrun -sdk iphonesimulator clang -rewrite-objc xxx.m
这样,编译成功的话,在文件中就会生成一个.cpp的文件,打开就能看到指定文件的OC代码转换成的runtime代码 (C++)。
runtime使用场景
使用runtime需要 #import
1.给分类添加属性
我们应该都试过,直接在分类中是不能添加属性的,运行会crash,但是我们可以利用runtime给分类添加属性。
#例如给项目中的控制器添加一个别名 -- 关键代码
static const char aliasKey;
- (void)setName:(NSString *)alias {
//将值和对象关联起来
objc_setAssociatedObject(self, &aliasKey, alias, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)alias {
return objc_getAssociatedObject(self, &aliasKey);
}
2.交换两个方法的实现
比较常用的是拦截系统自带方法调用(Swizzling),如果我们想统一给项目中的控制器自定义返回按钮样式,则可以在UIViewController的分类中Swizzling 控制器的ViewDidLoad方法,在新的方法中处理导航栏。
注:统一设置项目中的返回按钮,常用的方法还有自定义导航控制器,重写导航控制器的push方法统一设置。这里主要是介绍OC的黑魔法 runtime Swizzling使用。
//当程序装载到内存中的时候调用
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originalMethod = class_getInstanceMethod([UIViewController class], @selector(viewDidLoad));
Method swizzledMethod = class_getInstanceMethod([UIViewController class], @selector(swizzling_viewDidLoad));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
- (void)swizzling_viewDidLoad {
if (self.navigationController) {
UIImage *buttonNormal = [[UIImage imageNamed:@"nav_back_normal"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
[self.navigationController.navigationBar setBackIndicatorImage:buttonNormal];
[self.navigationController.navigationBar setBackIndicatorTransitionMaskImage:buttonNormal];
UIBarButtonItem *backItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil];
self.navigationItem.backBarButtonItem = backItem;
}
[self swizzling_viewDidLoad];
}
3.获取某个类的所有成员变量和方法
具体的使用,放到runtime动态创建类和后面实现归档的例子中体现。
- 获得所有成员变量
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
- 获得所有方法
Method *class_copyMethodList(Class cls, unsigned int *outCount)
- 获得成员变量名
const char *ivar_getName(Ivar v)
- 获得成员变量类型
const char *ivar_getTypeEncoding(Ivar v)
4.动态生成一个类
在运行时的机制中,我们可以动态生成一个类,例如 KVO的底层实现,在这里不介绍KVO是如何通过动态创建一个类来实现监听对象的改变,感兴趣的可以去网上了解一下,相关资料很多。
- 动态创建类
参数:
1.superclass = 要动态创建类的父类
2.name = 动态创建的类名
3.extraBytes = 值通常为0
Class objc_allocateClassPair(Class superclass, const char *name,
size_t extraBytes)
- 添加成员变量
参数:
1.cls = 为哪个类添加成员变量
2.name = 成员变量的名字
3.size = 成员变量的字节数
4.alignment = 对其方式
5.type = 成员变量的类型
BOOL class_addIvar(Class cls, const char *name, size_t size,
uint8_t alignment, const char *types)
- 添加方法
参数:
1.cls = 为哪个类添加方法
2.name = 方法的名字
3.imp = 对应的方法
4.types = 描述方法参数类型的字符数组
BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char *types)
- 注册到运行时环境
必须将创建的类注册到运行时环境才能生效。
void objc_registerClassPair(Class cls)
下面的例子是在Animal类中,动态创建了它的子类Dog类,并打印出了Dog类及其父类的所有成员变量。
- (void)printDogIvars {
unsigned int varCount = 0;
Class c = NSClassFromString(@"Dog");
while (c && c != [NSObject class]) {
Ivar *vars = class_copyIvarList(c, &varCount);
// class_copyPropertyList 获得是当前类的所有属性,不包括成员变量hhh
for (int i = 0; i < varCount; i ++) {
Ivar var = vars[i];
//获得成员变量的名字
const char *varName = ivar_getName(var); //“_type”,"hhh"
//获得成员变量的类型
const char *varType = ivar_getTypeEncoding(var);//"NSString"
NSLog(@"varName = %s,varType = %s", varName, varType);
}
c = [c superclass];
free(vars);
}
}
- (void)dymicCreateDogClass {
const char *className = "Dog";
Class Dog = object_getClass(NSClassFromString([NSString stringWithUTF8String:className]));
if (!Dog) {
//动态创建一个继承自Animal的Dog类
Dog = objc_allocateClassPair([Animal class], className, 0);
//给创建的Dog类添加成员变量
if (class_addIvar(Dog, "header", sizeof(NSString *), 0, "@")) {
NSLog(@"success");
}
class_addMethod(Dog, @selector(printDogIvarsValue), (IMP)printDogIvarsValue, "");
//注册到运行时环境
objc_registerClassPair(Dog);
}
id smallDog = [[Dog alloc] init];
// //给变量赋值
[smallDog setValue:@"smallHeader" forKey:@"header"];
[smallDog printDogIvarsValue];
}
//这个方法实际上没有调用,但是必须实现这个方法,才能调用下面的方法
- (void)printDogIvarsValue {
}
//调用这个方法,self和_cmd 参数是必须的,在之后也可以随便添加参数
static void printDogIvarsValue(id self, SEL _cmd) {
Ivar var = class_getInstanceVariable([self class], "header");
id value = object_getIvar(self, var);
NSLog(@"动态创建的Dog类的成员变量header的值==%@", value);
}
5.利用runtime实现对象的归档
利用runtime实现对象的归档是很常见的,我们平时在实现对象归档的时候通常的做饭是,实现归档和解档的两个方法 encodeWithCoder:和initWithCoder:,在里面对要归档和解档的属性进行重复的decodeObjectForKey: 和 encodeObject:,如果属性很多的话就要写很多重复的代码,这样并不好。现在可以利用runtime获取全部的成员变量来进行归档操作。
//不需要进行归解档的属性
- (NSArray *)ignoredNames {
return @[@"_one",@"_tow",@"_three"];
}
//解档
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int varCount = 0;
Ivar *vars = class_copyIvarList([self class], &varCount);
for (int i = 0; i < varCount; i ++) {
Ivar var = vars[i];
const char *varName = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:varName];
// 忽略不需要解档的属性
if ([[self ignoredNames] containsObject:key]) {
continue;
}
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
free(vars);
}
return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int varCount = 0;
Ivar *vars = class_copyIvarList([self class], &varCount);
for (int i = 0; i < varCount; i ++) {
Ivar var = vars[i];
const char *varName = ivar_getName(var);
NSString *key = [NSString stringWithUTF8String:varName];
// 忽略不需要归档的属性
if ([[self ignoredNames] containsObject:key]) {
continue;
}
id value = [self valueForKey:key];
[aCoder encodeObject:value forKey:key];
}
free(vars);
}
另外还有一种处理方式是给NSObject添加一个分类,提供归档和解档的方法,将上面归解档操作放到相应的方法里,如果多个类都需要归解档时,就可以直接调用分类中的方法,实现了对代码的封装,简化了代码。
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
[self decode:aDecoder];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[self encode:aCoder];
}
归解档的操作,建议去看一下MJExtension的实现,代码封装的很好,很值得学习,这里只是讲解runtime的使用,没有花时间去封装代码,还请见谅!
最后
感谢您的阅读,希望我的这篇文章能帮助到您!
代码在这里
在这个时代根本就没有怀才不遇, 关键是你真的必须具备才华,所以请你一定要强大自己!