Runtime开发使用实例

当我们学习和使用Objective-C时,Objective-C Runtime往往被我们自动忽略了。因为就算不知道它的存在,似乎对我们平时写代码并没有多大的影响。既然没影响,那我们还学它干嘛,就像不会造车但我会开车不就可以了?

但是当你在这行逐步深入进去,你至少应该知道一些Runtime的工作细节,知道Objective-C正在做的事情,能够帮助你更深层次的理解Objective-C和你的App,不管你有没有好奇心,对你个人来说也是一种收获和提升。了解车的性能和构造又何尝不是为了能更好的驾驭这辆车,成为一名资深的赛车手。

什么是runtime

概念这东西,都是老生常谈,引用网上的话:Objective-C的Runtime是一个运行时库(Runtime Library),主要使用 C 和汇编编写,为C添加了面相对象的能力并创造了 Objective-C。

runtime的作用

通过runtime,你可以动态的产生、修改和删除一个类,或者一个方法和成员变量。

runtime的使用

使用之前,首先包含头文件。

#import <objc/runtime.h>    // 包含对类、成员变量、属性、方法的操作
#import <objc/message.h>    // 包含消息机制

以下是runtime经常用到的方法。

class_copyIvarList()    // 返回一个指向类的成员变量数组的指针
class_copyPropertyList()    //返回一个指向类的属性数组的指针
ivar_getName()    // 获取成员变量名(C类型的字符串)
property_getName()    // 获取属性名(C类型的字符串)
---------------------------------------------------
typedef struct objc_method *Method;
class_getInstanceMethod()
class_getClassMethod()    // 以上两个函数传入返回Method类型
---------------------------------------------------
method_exchangeImplementations(Method m1, Method m2)    // 交换两个方法的实现

获取类成员变量

通过runtime可以获取某个类的所有成员变量,代码如下,使用时需要添加头文件#import <objc/runtime.h>

/**
 *  获取某个类的所有成员变量
 *
 *  @param object 某个类的实例对象
 *
 *  @return NSArray(成员变量名和值)
 */
- (NSArray *)getAllIvar:(id)object
{
    NSMutableArray *array = [NSMutableArray array];
    unsigned int count;
    // 获得一个指向该类成员变量的指针
    Ivar *ivars = class_copyIvarList([object class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        // 根据ivar获得其成员变量的名称(C语言字符串)
        const char *keyChar = ivar_getName(ivar);
        NSString *keyStr = [NSString stringWithCString:keyChar encoding:NSUTF8StringEncoding];
        @try {
            id valueStr = [object valueForKey:keyStr];
            NSDictionary *dic = nil;
            if (valueStr) {
                dic = @{keyStr : valueStr};
            } else {
                dic = @{keyStr : @"值为nil"};
            }
            [array addObject:dic];
        }
        @catch (NSException *exception) {}
    }
    return [array copy];
}

我们以UILabel为例:

获取类属性

获取类属性的方式跟获取类成员变量的方式类似,代码如下。

/**
 *  获取某个类的所有属性
 *
 *  @param object 某个类的实例对象
 *
 *  @return NSArray(所有属性名)
 */
- (NSArray *)getAllProperty:(id)object
{
    NSMutableArray *array = [NSMutableArray array];
    unsigned int count;
    // 获得指向该类所有属性的指针
    objc_property_t *propertys = class_copyPropertyList([object class], &count);
    for (int i = 0; i < count; i++) {
        objc_property_t property = propertys[i];
        // 通过objc_property_t获得属性名称(C语言字符串)
        const char *nameChar = property_getName(property);
        NSString *nameStr = [NSString stringWithCString:nameChar encoding:NSUTF8StringEncoding];
        [array addObject:nameStr];
    }
    return [array copy];
}

同样以UILabel为例:

应用实例

我们可以通过runtime来获得某个类的所有属性和所有成员变量,便可以通过这种方式实现对任意类的快速归档和解档,同样的,还能实现字典转模型的操作。

归档解档

关于自定义类的归档需要遵守协议,使用runtime同时也别忘记包含头文件。

#import "MZTestModel.h"
#import <objc/runtime.h>
@interface MZTestModel () <NSCoding>
@end

然后实现NSCoding的协议方法,这里我们通过runtime动态获取该类的属性,通过这种方式,我们不用关心类里到底有多少属性,属性名称是什么,一套代码就能搞定自定义类的归档和解档。

- (void)encodeWithCoder:(NSCoder *)encoder{
    //归档存储自定义对象
    unsigned int count = 0;
    //获得指向该类所有属性的指针
    objc_property_t *properties =     class_copyPropertyList([self class], &count);
    for (int i =0; i < count; i ++) {
        objc_property_t property = properties[i];
        // 根据objc_property_t获得其属性的名称(C语言的字符串)
        const char *name = property_getName(property);
        NSString *key = [NSString   stringWithUTF8String:name];
        // 编码每个属性,利用kVC取出每个属性对应的数值
        [encoder encodeObject:[self valueForKeyPath:key] forKey:key];
    }}

- (instancetype)initWithCoder:(NSCoder *)decoder{
    // 归档存储自定义对象
    unsigned int count = 0;
    // 获得指向该类所有属性的指针
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    for (int i =0; i < count; i ++) {
        objc_property_t property = properties[i];
        // 根据objc_property_t获得其属性的名称(C语言的字符串)
        const char *name = property_getName(property);
        NSString *key = [NSString stringWithUTF8String:name];
        // 解码每个属性,利用kVC取出每个属性对应的数值
        [self setValue:[decoder decodeObjectForKey:key] forKeyPath:key];
    }
    return self;
}

交换方法

交换方法是通过method_exchangeImplementations(Method m1, Method m2)实现的。对于苹果系统提供的方法,我们看不到其内部实现,当然也无法去修改里面的代码。但是却可以使用runtime去替换它,这样就能处理某些使用系统方法而导致程序崩溃的问题(当然使用继承也是可以解决这个问题的)。

举个例子,我们在使用字典和数组的时候,是不能向里面写入nil的。这样会导致程序崩溃。

在进行后台接口调试的时候,经常会遇到这个问题。比如登陆注册,我们需要将用户的账号和密码以字典的形式传给后台进行比对,如果账号密码为空,前端不进行判断,直接将该值写入字典,那么程序就直接崩溃了。

有些接口的参数比较多,同时部分字段又不是必传字段。我们需要每次对传入的数据进行判断是否为空,然后才写入字典,很麻烦,因为你不判断程序就直接崩溃了,而一堆if语句看起来又是那么的恶心。。。。

那么如何解决?我们以NSMutableArray为例。

我在数组里加了一个nil,代码直接崩了。

然后我们给NSMutableArray新建一个类别Extension,然后在类别里添加以下方法。

// 该方法在类或者分类第一次加载内存时会自动调用
+ (void)load {
    // __NSArrayM为NSMutableArray对象的类型,通过以下方式获取。
    // NSMutableArray *testArray = [[NSMutableArray alloc] init];
    // NSLog(@"%@", NSStringFromClass([testArray class]));
    Method oldMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:));
    Method newMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(mz_addObject:));
    method_exchangeImplementations(oldMethod, newMethod);
}

// 自定义方法
- (void)mz_addObject:(id)object {
    if (object != nil) {
        [self mz_addObject:object];
    }
}

再一次运行程序,一切正常,没有崩溃。

参考

理解 Objective-C Runtime