Runtime详解及应用

简介: 动态语言:编译时确定变量的数据类型。静态语言:运行时确定变得的数据类型。

一、运行时机制


动态语言:编译时确定变量的数据类型。

静态语言:运行时确定变得的数据类型。


1、【动态语言】:Objective-C语言(以下简称“OC”)是一门动态语言,其优势有:代码编程更灵活,如可以将消息转成给我们想要的对象,或者随意交换一个方法的实现等。


2、【编译器 + 运行时操作系统】:OC作为动态语言,其需要编译器和运行时系统来执行编译的代码,而这个运行时系统就是我所要讲的Objc Runtime(Runtime)。Runtime本身是一个库,它基本上是用C和汇编写的,有了这个库使得C语言有面向对象的能力。


3、【函数的调用区别】:C语言,在编译的时候决定调用哪个函数;OC语言,在运行的时候决定调用哪个函数。


4、【在编译阶段报错】:OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错;C语言调用未实现的函数就会报错。


二、应用


1、 能获得某个类的所有成员变量

2、能获得某个类的所有属性

3、能获得某个类的所有方法

4、交换方法实现

5、能动态添加一个成员变量

6、能动态添加一个属性

7、字典转模型

8、runtime归档/解档

816327d9790e58e90b00d20824e82c8.jpg




Demo点击下载


三、基本函数和头文件


#import<objc/runtime.h> : //成员变量,类,方法
class_copyIvarList : 获得某个类内部的所有成员变量
class_copyMethodList : 获得某个类内部的所有方法
class_getInstanceMethod : 获得某个具体的实例方法 (对象方法,减号-开头)
class_getClassMethod : 获得某个具体的类方法 (加号+开头)
method_exchangeImplementations : 交换两个方法的实现
#import<objc/message.h> : //消息机制
objc_msgSend(...)


四、应用


1、更改属性值

#import <Foundation/Foundation.h>
@interface ZMPerson : NSObject
/** 姓名 **/
@property (nonatomic, copy) NSString *name;
/** 性别 **/
@property (nonatomic, copy) NSString *sex;
- (NSString *)coding;
- (NSString *)eating;
- (NSString *)changeMethod;
@end


#import "ZMPerson.h"
#import <objc/runtime.h>
@interface ZMPerson(){
    NSString *mobile;//手机号
}
/** 年龄 **/
@property (nonatomic, copy) NSString *age;
@end
@implementation ZMPerson{
    NSString *address;//地址
}
- (NSString *)coding {
    return @"coding";
}
- (NSString *)eating {
    return @"eating";
}
- (NSString *)changeMethod {
    return @"方法已被拦截并替换";
}
// void(*)()
// 默认方法都有两个隐式参数,
void eat(id self, SEL _cmd, NSNumber *meter){
    NSLog(@"%@ __ %@ __ %@",[self class],NSStringFromSelector(_cmd),meter);
}
// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *selStr = @"eat:";
    SEL selFromStr = NSSelectorFromString(selStr);
    if (sel == selFromStr) {
        // 动态添加eat方法
        // 第一个参数:给哪个类添加方法
        // 第二个参数:添加方法的方法编号
        // 第三个参数:添加方法的函数实现(函数地址)
        // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
        class_addMethod(self, sel, (IMP)eat, "v@:@");
    }
    return [super resolveInstanceMethod:sel];
}
@end


#import "ZMPerson.h"
@interface ZMFirstVC ()
@property (nonatomic, strong) ZMPerson *person;
@end
@implementation ZMFirstVC
- (void)initView {
    [super initView];
    _person = [ZMPerson new];
}
- (void)buttonClick:(UIButton *)sender {
    // ------   设置属性值   -----
    _person.sex = @"1";
    unsigned int count = 0;
    // 动态获取类中的所有属性(包括私有)
    Ivar *ivar = class_copyIvarList(_person.class, &count);
    // 遍历属性找到对应字段
    for (int i = 0; i < count; i ++) {
        Ivar tempIvar = ivar[i];
        //ivar_getName:将IVar变量转化为字符串
        const char *varChar = ivar_getName(tempIvar);
        //ivar_getTypeEncoding:获取IVar的类型
        //const char *memberType = ivar_getTypeEncoding(tempIvar);
        NSString *varString = [NSString stringWithUTF8String:varChar];
        if ([varString isEqualToString:@"_name"]) {
            // 修改对应的字段值
            object_setIvar(_person, tempIvar, @"姓名");
        }
        NSLog(@"----->%@",varString);
        //私有类
        if ([varString isEqualToString:@"_age"]) {
            object_setIvar(_person, tempIvar, @"100");
        }
        //私有类
        if ([varString isEqualToString:@"address"]) {
            object_setIvar(_person, tempIvar, @"福建省厦门市");
        }
        //私有类
        if ([varString isEqualToString:@"mobile"]) {
            object_setIvar(_person, tempIvar, @"138****8888");
        }
    }
    // ----  利用KVC键值编码 获取属性值   -----
    NSString *str0 = [_person valueForKey:@"_name"];
    NSString *str1 = [_person valueForKey:@"_sex"];
    NSString *str2 = [_person valueForKey:@"_age"];
    NSString *str3 = [_person valueForKey:@"address"];
    NSString *str4 = [_person valueForKey:@"mobile"];
    NSMutableString *mstr = [NSMutableString string];
    [mstr appendString:str0];
    [mstr appendString:str1];
    [mstr appendString:str2];
    [mstr appendString:str3];
    [mstr appendString:str4];
    NSLog(@"非私有类:%@__%@\n私有类:%@__%@__%@__",str0,str1,str2,str3,str4);
    self.testLabelText = mstr ? mstr : @"更改属性值失败";
    // -----  键值编码设置属性值   ------
    [_person setValue:@"我的家乡还是在福建啊" forKey:@"address"];
    NSLog(@"=======%@", [_person valueForKey:@"address"]);
}
@end


执行结果:

20180822183624242.png

2、动态添加属性


#import <Foundation/Foundation.h>
@interface NSObject (ZMAddAttribute_h)
@property (nonatomic, copy) NSString *name;
@end
#import "NSObject+ZMAddAttribute_h.h"
#import <objc/message.h>
@implementation NSObject (ZMAddAttribute_h)
-(void)setName:(NSString *)name{
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name{
    return objc_getAssociatedObject(self, @"name");
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "NSObject+ZMAddAttribute_h.h"
@interface ZMSecondVC ()
@property (nonatomic, strong) NSObject *person;
@end
@implementation ZMSecondVC
- (void)initView {
    [super initView];
    _person = [NSObject new];
    _person.name = @"添加属性成功";
}
- (void)buttonClick:(UIButton *)sender {
    self.testLabelText = _person.name.length ? _person.name : @"添加属性失败";
}
@end


3、添加方法成功


#import "ZMPerson.h"
@interface ZMThirdVC ()
@property (nonatomic, strong) ZMPerson *person;
@end
@implementation ZMThirdVC
- (void)initView {
    [super initView];
    _person = [ZMPerson new];
}
- (void)buttonClick:(UIButton *)sender {
    /*
     动态添加 coding 方法
     (IMP)codingOC 意思是 codingOC 的地址指针;
     "v@:" 意思是,v 代表无返回值 void,如果是 i 则代表 int;@代表 id sel; : 代表 SEL _cmd;
     “v@:@@” 意思是,两个参数的没有返回值。
     */
    class_addMethod([_person class], @selector(test), (IMP)codingObjC, "v@:");
    // 调用 coding 方法响应事件
    if ([_person respondsToSelector:@selector(test)]) {
        [_person performSelector:@selector(test)];
        self.testLabelText = @"添加方法成功";
    }else{
        self.testLabelText = @"添加方法失败";
    }
    [_person performSelector:@selector(eat:) withObject:@100 afterDelay:3];
}
-(void)test{
    NSLog(@"测试测试");
}
// 方法实现
void codingObjC(id self,SEL _cmd){
    NSLog(@"添加方法成功");
}
@end


4、交换方法实现


#import "ZMPerson.h"
@interface ZMFourthVC ()
@property (nonatomic, strong) ZMPerson *person;
@end
@implementation ZMFourthVC
- (void)initView {
    [super initView];
    _person = [ZMPerson new];
    NSLog(@"%@",_person.coding);
    NSLog(@"%@",_person.eating);   
}
- (NSArray *)buttonTitleArray {
    return @[@"交换", @"不交换"];
}
- (void)buttonClick:(UIButton *)sender {
    if (sender.tag == 0) {
        [self exchangeMethod];
    }
    NSString *str = [NSString stringWithFormat:@"%@_%@",[_person coding],[_person eating]];
    self.testLabelText = str;
    NSLog(@"%@",_person.coding);
    NSLog(@"%@",_person.eating);
}
///交换方法
-(void)exchangeMethod{
    Method aMethod = class_getInstanceMethod([_person class], @selector(coding));
    Method bMethod = class_getInstanceMethod([_person class], @selector(eating));
    method_exchangeImplementations(aMethod, bMethod);
}


5、拦截并替换方法


#import <objc/runtime.h>
@interface NSObject (Swizzle)
///拦截替换方法
+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel;
@end


@implementation NSObject (Swizzle)
///拦截替换方法
+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {
    Method originMethod = class_getInstanceMethod(self, origSel);
    Method newMethod = class_getInstanceMethod(self, aftSel);
    if(originMethod && newMethod) {//必须两个Method都要拿到
        if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
            //实现成功添加后
            class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
        }
        return YES;
    }
    return NO;
}
@end


#import "NSObject+Swizzle.h"
@implementation MyString
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = object_getClass((id)self);
        [class swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];
    });
}
+ (BOOL)myResolveInstanceMethod:(SEL)sel {
    if(! [self myResolveInstanceMethod:sel]) {
//        NSString *selString = NSStringFromSelector(sel);
//        class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");
//        return YES;
        NSString *selString = NSStringFromSelector(sel);
        ///锁定一些方法如果不存在则创建
        if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"] || [selString isEqualToString:@"test"]) {
            class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");
            return YES;
        }else {
            return NO;
        }
    }
    return YES;
}
- (void)dynamicMethodIMP {
    NSLog(@"我是动态加入的函数");
}


- (void)buttonClick:(UIButton *)sender {
    NSLog(@"begin test");
    MyString *string = [[MyString alloc] init];
    [string performSelector:@selector(countAll)];
    [string performSelector:@selector(pushViewController)];
    [string performSelector:@selector(test)];
    NSLog(@"finish test");
}


6、在方法上添加额外功能


@interface ZMCountButton : UIButton
@property (nonatomic, assign) NSInteger count;
@end
@interface ZMClickCount : NSObject
+ (instancetype)sharedInstance;
- (NSInteger)clickCount;
@end


#import <objc/runtime.h>
@implementation ZMCountButton
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method oriMethod = class_getInstanceMethod(self.class, @selector(sendAction:to:forEvent:));
        Method cusMethod = class_getInstanceMethod(self.class, @selector(customSendAction:to:forEvent:));
        // 判断自定义的方法是否实现, 避免崩溃
        BOOL addSuccess = class_addMethod(self.class, @selector(sendAction:to:forEvent:), method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));
        if (addSuccess) {
            // 没有实现, 将源方法的实现替换到交换方法的实现
            class_replaceMethod(self.class, @selector(customSendAction:to:forEvent:), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        } else {
            // 已经实现, 直接交换方法
            method_exchangeImplementations(oriMethod, cusMethod);
        }
    });
}
- (void)customSendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    _count = [[ZMClickCount sharedInstance] clickCount];
    [self customSendAction:action to:target forEvent:event];
}
@end
@interface ZMClickCount()
@property (nonatomic, assign) NSInteger count;
@end
@implementation ZMClickCount
+ (instancetype)sharedInstance {
    static ZMClickCount *_instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}
- (NSInteger)clickCount {
    ++_count;
    NSLog(@"点击了 %ld次", _count);
    return _count;
}
@end


- (void)countButtonClick:(UIButton *)sender {
    self.testLabelText = [NSString stringWithFormat:@"点击 %ld 次了", self.testButton.count];
}
#pragma mark - 懒加载
- (ZMCountButton *)testButton {
    if (!_testButton) {
        _testButton = [ZMCountButton buttonWithType:UIButtonTypeCustom];
        [_testButton addTarget:self action:@selector(countButtonClick:) forControlEvents:UIControlEventTouchUpInside];
        [_testButton setTitle:@"统计此按钮的点击数量" forState:UIControlStateNormal];
        _testButton.frame = CGRectMake(20, self.view.center.y + 100, [UIScreen mainScreen].bounds.size.width - 40, 30);
        _testButton.backgroundColor = [UIColor redColor];
    }
    return _testButton;
}


7、字典转模型


#import <Foundation/Foundation.h>
@protocol ModelDelegate <NSObject>
@optional
// 用在三级数组转换
+ (NSDictionary *)arrayContainModelClass;
@end
@interface NSObject (NNKeyValues)
/** 字典转模型 **/
+ (instancetype)modelWithDict:(NSDictionary *)dict;
@end


#import "NSObject+ZMKeyValues.h"
#import <objc/runtime.h>
@implementation NSObject (ZMKeyValues)
/** 字典转模型 **/
+ (instancetype)modelWithDict:(NSDictionary *)dict {
    id objc = [[self alloc] init];
    unsigned int count = 0;
    // 获取成员属性数组
    Ivar *ivarList = class_copyIvarList(self, &count);
    // 遍历所有的成员属性名
    for (int i = 0; i < count; i ++) {
        // 获取成员属性
        Ivar ivar = ivarList[i];
        // 获取成员属性名
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        NSString *key = [ivarName substringFromIndex:1];
        // 从字典中取出对应 value 给模型属性赋值
        id value = dict[key];
        // 获取成员属性类型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        // 判断 value 是不是字典
        if ([value isKindOfClass:[NSDictionary class]]) {
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
            ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
            Class modalClass = NSClassFromString(ivarType);
            // 字典转模型
            if (modalClass) {
                // 字典转模型
                value = [modalClass modelWithDict:value];
            }
        }
        if ([value isKindOfClass:[NSArray class]]) {
            // 判断对应类有没有实现字典数组转模型数组的协议
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
                // 转换成id类型,就能调用任何对象的方法
                id idSelf = self;
                // 获取数组中字典对应的模型
                NSString *type = [idSelf arrayContainModelClass][key];
                // 生成模型
                Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍历字典数组,生成模型数组
                for (NSDictionary *dict in value) {
                    // 字典转模型
                    id model =  [classModel modelWithDict:dict];
                    [arrM addObject:model];
                }
                // 把模型数组赋值给value
                value = arrM;
            }
        }
        // KVC 字典转模型
        if (value) {
            [objc setValue:value forKey:key];
        }
    }
    return objc;
}
@end


- (void)initView {
    [super initView];
    NSDictionary *person = @{@"name":@"Chen",
                             @"sex": @"男"};
    NSDictionary *dict = @{@"coderID":@"99",
                           @"nickName": @"rattanchen",
                           @"phoneNumber": @"138****8888",
                           @"person" : person};
    NSArray *addarr = @[dict ,dict, dict];
    NSMutableDictionary *mudict = [NSMutableDictionary dictionaryWithDictionary:dict];
    [mudict setObject:person forKey:@"person"];
    for (NSDictionary *item in addarr) {
        ZMCoding *coding = [ZMCoding modelWithDict:item];
        [self.dataArray addObject:coding];
    }
    if (self.dataArray.count) {
        self.testLabelText = @"字典转模型成功, 点击查看对应的值";
    }
}
- (void)buttonClick:(UIButton *)sender {
    ZMCoding *coding = self.dataArray.firstObject;
    switch (sender.tag) {
        case 0:
            self.testLabelText = coding.coderID;
            break;
        case 1:
            self.testLabelText = coding.nickName;
            break;
        case 2:
            self.testLabelText = coding.phoneNumber;
            break;
        case 3:
            self.testLabelText = coding.person.name;
            break;
        case 4:
            self.testLabelText = coding.person.sex;
            break;
        default:
            break;
    }
}
- (NSArray *)buttonTitleArray {
    return @[@"ID", @"昵称", @"手机号", @"姓名", @"性别"];
}
- (NSMutableArray *)dataArray {
    if (!_dataArray) {
        _dataArray = [NSMutableArray array];
    }
    return _dataArray;
}
@end


8、归档解档


#import <Foundation/Foundation.h>
#import "NSObject+ZMKeyValues.h"
#import "ZMPerson.h"
@interface ZMCoding : NSObject<NSCoding>
@property (nonatomic, strong) ZMPerson *person;
@property (nonatomic, copy) NSString *coderID;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, copy) NSString *phoneNumber;
@end


#import <objc/runtime.h>
@implementation ZMCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int count = 0;
    // 获取类中所有属性
    Ivar *ivars = class_copyIvarList(self.class, &count);
    // 遍历属性
    for (int i = 0; i < count; i ++) {
        // 取出 i 位置对应的属性
        Ivar ivar = ivars[i];
        // 查看属性
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        // 利用 KVC 进行取值,根据属性名称获取对应的值
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
    free(ivars);
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int count = 0;
        // 获取类中所有属性
        Ivar *ivars = class_copyIvarList(self.class, &count);
        // 遍历属性
        for (int i = 0; i < count; i ++) {
            // 取出 i 位置对应的属性
            Ivar ivar = ivars[i];
            // 查看属性
            const char *name = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:name];
            // 进行解档取值
            id value = [aDecoder decodeObjectForKey:key];
            // 利用 KVC 对属性赋值
            [self setValue:value forKey:key];
        }
    }
    return self;
}
@end


- (void)initView {
    [super initView];
    ZMCoding *coding = [ZMCoding new];
    coding.coderID = @"99";
    coding.nickName = @"rattanchen";
    coding.phoneNumber = @"138****8888";
    NSString *path = [ZMTools getDomainsPathWithFile:@"123"];
    BOOL isSuc = [NSKeyedArchiver archiveRootObject:coding toFile:path];
    if (isSuc == YES) {
        self.testLabelText = @"归档成功, 点击按钮取出模型中对应的值";
    }else{
        self.testLabelText = @"归档失败";
    }
}
- (void)buttonClick:(UIButton *)sender {
    NSString *path = [ZMTools getDomainsPathWithFile:@"123"];
    ZMCoding *coding = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    coding.coderID = @"999";
    if (sender.tag == 0) {
        self.testLabelText = coding.coderID;
    } else if (sender.tag == 1) {
        self.testLabelText = coding.nickName;
    } else {
        self.testLabelText = coding.phoneNumber;
    }
}
- (NSArray *)buttonTitleArray {
    return @[@"ID", @"昵称", @"手机号"];
}


Demo点击下载



相关文章
|
29天前
|
Linux Shell Windows
Java.Runtime.exec()的使用
Java.Runtime.exec()的使用
10 0
|
Kubernetes API 调度
Container Runtime CDI与NRI介绍
CDI介绍什么是CDICDI(Container Device Interface)是Container Runtimes支持挂载第三方设备(比如:GPU、FPGA等)机制。它引入了设备作为资源的抽象概念,这类设备由一个完全限定的名称唯一指定,该名称由设备商ID,设备类别与一个设备类别下的一个唯一名称组成,格式如下:vendor.com/class=unique_name设备商ID和设备类型(ve
2990 1
Container Runtime CDI与NRI介绍
|
存储 编译器 API
Runtime的使用
Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的。
228 0
Runtime的使用
|
设计模式 Java 开发者
Runtime 相关说明 | 学习笔记
快速学习 Runtime 相关说明。
114 0
|
设计模式 Java 开发者
Runtime 类|学习笔记
快速学习 Runtime 类
Runtime 类|学习笔记
|
C++
C++ runtime sample
本文演示如何利用函数计算的自定义Runtime功能来运行C++代码
1737 0
|
缓存 iOS开发 编译器
Runtime那些事
Runtime介绍。
2004 0
|
Windows 设计模式
|
存储 JSON 数据格式