Runtime
2017-12-19 本文已影响4人
Jey
Runtime 在实际开发中,其实就是一组C语言的函数。
Runtime库主要做下面几件事:
封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。
找出方法的最终执行代码:当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。
运行时的应用:
1.将某些OC代码转为运行时代码,探究底层,比如block的实现原理(上边已讲到);
2.拦截系统自带的方法调用(Swizzle 黑魔法),比如拦截imageNamed:、viewDidLoad、alloc;
3.实现分类也可以增加属性;
4.实现NSCoding的自动归档和自动解档;
5.实现字典和模型的自动转换。
- Runtime 在实际开发中,其实就是一组C语言的函数。
1. oc写法
Person *p = [[Person alloc] init];
[p eat:@"香蕉"];
2. 拆分写法
Person *p = [Person alloc];
p = [p init];
[p eat:@"香蕉"];
3. sel写法
Person *p = objc_msgSend(objc_getClass("Person"), @selector(alloc));
objc_msgSend(p, @selector(init));
objc_msgSend(p, @selector(eat:),@"香蕉");
4. runtime写法
Person *p = objc_msgSend(objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
objc_msgSend(p, sel_registerName("eat:"),@"香蕉");
- runtime交换方法例子:
#import "NSURL+url.h"
#import <objc/message.h>
@implementation NSURL (url)
// 分类中重写系统方法,这种方法不保险,重写的方法可能系统自己也会有调用,重写的代码影响
//+ (instancetype)URLWithString:(NSString *)URLString
//{
// NSURL *url = [[NSURL alloc] initWithString:URLString];
// if (!url) {
// NSLog(@"为空");
// }
// return url;
//}
// runtime的方法交换
+ (void)load
{
Method me1 = class_getClassMethod([super class], @selector(URLWithString:));
Method me2 = class_getClassMethod([super class], @selector(Jey_Url:));
method_exchangeImplementations(me1, me2);
}
+ (instancetype)Jey_Url:(NSString *)str
{
/*
已经交换了方法,所以在这里调用 Jey_Url: 实际为 URLWithString:
如果这里写 URLWithString: 会递归造成死循环
*/
NSURL *url = [NSURL Jey_Url:str];
if (!url) {
NSLog(@"为空");
}
return url;
}
@end
// 调用URLWithString,这时候NSURL+url分类中做了方法交换,其实调用的是Jey_Url
- (void)viewDidLoad {
[super viewDidLoad];
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com/哈哈"]; // url中有中文url会为空,要做编码处理
}
- 实现NSCoding的自动归档和自动解档,此时不管pig有多少个属性,不需要在归档和解挡的时候写 [aCoder encodeObject:_name forKey:@"name"];这种代码,
import "Pig.h"
#import <objc/message.h>
@interface Pig ()
@property(copy, nonatomic) NSString *pSex;
@end
@implementation Pig
// 告诉系统归档的属性
- (void)encodeWithCoder:(NSCoder *)aCoder
{
// [aCoder encodeObject:_name forKey:@"name"];
// [aCoder encodeInt:_age forKey:@"age"];
// 利用运行时,上面可以注释
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Pig class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
NSString *KEY = [NSString stringWithUTF8String:name]; // c字符串转oc字符串
[aCoder encodeObject:[self valueForKey:KEY] forKey:KEY];
}
// C语言有copy,creat new 要释放
free(ivars);
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
// _name = [aDecoder decodeObjectForKey:@"name"];
// _age = [aDecoder decodeIntForKey:@"age"];
// 上面两行可注
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Pig 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 = [aDecoder decodeObjectForKey:KEY];
[self setValue:value forKey:KEY];
}
free(ivars);
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSString *tmpPath = NSTemporaryDirectory();
NSLog(@"%@",tmpPath);
// 运行时
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Pig class], &count);
NSLog(@"===%d",count); // 对象属性的个数
Ivar ivar = ivars[2];
const char *name = ivar_getName(ivar);
NSLog(@"@===%s",name); // 属性
}
- (void)encode
{
Pig *p = [[Pig alloc] init];
p.name = @"pig";
p.age = 18;
NSString *tmpPath = NSTemporaryDirectory();
NSString *filePath = [tmpPath stringByAppendingString:@"hank.han"];
[NSKeyedArchiver archiveRootObject:p toFile:filePath];
}
- (void)decode
{
NSString *tmpPath = NSTemporaryDirectory();
NSString *filePath = [tmpPath stringByAppendingString:@"hank.han"];
Pig *p = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@",p.name);
}
- 实现字典和模型的自动转换
原始字典转模型
-(instancetype)initWithDictionary:(NSDictionary *)dict{
if (self = [super init]) {
self.age = dict[@"age"];
self.name = dict[@"name"];
}
return self;
}
模型转字典的时候:
1.调用 class_copyPropertyList 方法获取当前 Model 的所有属性
2.调用 property_getName 获取属性名称
3.根据属性名称生成 getter 方法
4.使用 objc_msgSend 调用 getter 方法获取属性值(或者 KVC)
#import "NSObject+KeyValues.h"
#import #import @implementation NSObject (KeyValues)
//字典转模型
+(id)objectWithKeyValues:(NSDictionary *)aDictionary{
id objc = [[self alloc] init];
for (NSString *key in aDictionary.allKeys) {
id value = aDictionary[key];
/*判断当前属性是不是Model*/
objc_property_t property = class_getProperty(self, key.UTF8String);
unsigned int outCount = 0;
objc_property_attribute_t *attributeList = property_copyAttributeList(property, &outCount);
objc_property_attribute_t attribute = attributeList[0];
NSString *typeString = [NSString stringWithUTF8String:attribute.value];
if ([typeString isEqualToString:@"@\"TestModel\""]) {
value = [self objectWithKeyValues:value];
}
/**********************/
//生成setter方法,并用objc_msgSend调用
NSString *methodName = [NSString stringWithFormat:@"set%@%@:",[key substringToIndex:1].uppercaseString,[key substringFromIndex:1]];
SEL setter = sel_registerName(methodName.UTF8String);
if ([objc respondsToSelector:setter]) {
((void (*) (id,SEL,id)) objc_msgSend) (objc,setter,value);
}
}
return objc;
}
//模型转字典
-(NSDictionary *)keyValuesWithObject{
unsigned int outCount = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (int i = 0; i < outCount; i ++) {
objc_property_t property = propertyList[i];
//生成getter方法,并用objc_msgSend调用
const char *propertyName = property_getName(property);
SEL getter = sel_registerName(propertyName);
if ([self respondsToSelector:getter]) {
id value = ((id (*) (id,SEL)) objc_msgSend) (self,getter);
/*判断当前属性是不是Model*/
if ([value isKindOfClass:[self class]] && value) {
value = [value keyValuesWithObject];
}
/**********************/
if (value) {
NSString *key = [NSString stringWithUTF8String:propertyName];
[dict setObject:value forKey:key];
}
}
}
return dict;
}
@end
- 动态添加方法
首先从外部隐式调用一个不存在的方法:
// 隐式调用方法
[target performSelector:@selector(sleep:) withObject:@"pig"];
然后,在target对象内部重写拦截调用的方法,动态添加方法。
// 前两个参数是隐藏参数,要注意
void runAddMethod(id self, SEL _cmd, NSString *string){
NSLog(@"add C IMP ", string);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//给本类动态添加一个方法
if ([NSStringFromSelector(sel) isEqualToString:@"sleep:"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
return YES;
}