My Article

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.实现字典和模型的自动转换。

  1. 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:"),@"香蕉");
  1. 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会为空,要做编码处理
}
  1. 实现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);
}
  1. 实现字典和模型的自动转换
原始字典转模型
-(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
  1. 动态添加方法
首先从外部隐式调用一个不存在的方法:

// 隐式调用方法
[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;
}

上一篇下一篇

猜你喜欢

热点阅读