Runtime

作者: Jey | 来源:发表于2017-12-19 17:53 被阅读4次

    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;
    }
    
    

    相关文章

      网友评论

        本文标题:Runtime

        本文链接:https://www.haomeiwen.com/subject/bbjiwxtx.html