iOS进阶之runtime作用

2018-05-22 17:48:48 浏览数 (1)

前言

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中

代码语言:javascript复制
- (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、然后在其他类调用

代码语言:javascript复制
    DWExchangeTwoMethod *exchangeMethod = [DWExchangeTwoMethod new];
    [exchangeMethod play];

由上可以看出,调用的是play方法

但打印结果为 【玩...】,证明已经动态交换成功

二、为分类添加属性

一般情况下,分类不可以添加属性,但用runtime却可以实现

代码语言:javascript复制
- (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)

参数解析同上

三、获取实例变量列表
代码语言:javascript复制
    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)

代码语言:javascript复制
#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

上面每个类都要写一次,所以我们添加个分类会更加简化

代码语言:javascript复制
#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中取代之前的代码

代码语言:javascript复制
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [self encode:aCoder];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        [self decode:aDecoder];
    }
    return self;
}

当然还可以更加简洁,把NSCoding的定义成宏

代码语言:javascript复制
#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 */

注:

如果有空行,需要加斜线补充

使用:

代码语言:javascript复制
//宏定义
#import "Archive.h"

@implementation DWPerson

ArchiveImplemention

@end
五、动态添加方法

在viewController使用

代码语言:javascript复制
- (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];
}
六、实现字典和模型的自动转换

获取属性的列表的方法是字典转模型的比较核心的方法

0 人点赞