Objective-C运行时编程 - 实现自动化description方法的思路及代码示例

发布自米高 | Michael - 博客园,源地址:http://www.cnblogs.com/michaellfx/p/4232205.html,转载请注明。

本文结构

关键字:Objective-C OC description函数 自动打印属性及属性值 运行时枚举成员变量

基础实现

使用NSLogpo,Xcode默认调用对象的description方法,若没实现,则打印对象的地址,不方便查看对象的状态。特别地,在RESTful编程中,服务器返回的JSON对象往往具有较多属性,若每个对象建立一个类,并为这些类一一实现description方法,工作量大且是重复性工作,对我们码农没实质帮助,还容易漏掉部分属性。像这种重复性工作,还是由计算机去做更合适。

实现自动化description的基本思路是,基类实现此方法,子类只需按需定义属性即可。

基类实现description的算法是,通过运行时读取对象运行时所属的类(注:当使用KVO时,在有观察者的情况下,运行时将为被观察的类生成一个新类,再返回新类的类型,这是ISA混写的一种具体应用。)对象及所有成员变量,再由KVC读写成员变量的值。

BaseModel.m

- (NSDictionary *)mapPropertiesToDictionary {
    // 用以存储属性(key)及其值(value)
    NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
    // 获取当前类对象类型
    Class cls = [self class];
    // 获取类对象的成员变量列表,ivarsCount为成员个数
    uint ivarsCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &ivarsCount);
    // 遍历成员变量列表,其中每个变量为Ivar类型的结构体
    const Ivar *ivarsEnd = ivars + ivarsCount;
    for (const Ivar *ivarsBegin = ivars; ivarsBegin < ivarsEnd; ivarsBegin++) {
        Ivar const ivar = *ivarsBegin;
        // 获取变量名
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        /*
        若此变量声明为属性,则变量名带下划线前缀'_'
        比如 @property (nonatomic, copy) NSString *name;则 key = _name;
        为方便查看属性变量,在此特殊处理掉下划线前缀
        */
        if ([key hasPrefix:@"_"]) key = [key substringFromIndex:1];
        // 获取变量值
        id value = [self valueForKey:key];
        // 处理属性未赋值属性,将其转换为null,若为nil,插入将导致程序异常
        [dictionary setObject:value ? value : [NSNull null]
                       forKey:key];
    }
    if (ivars) {
        free(ivars);
    }
    return dictionary;
}

枚举属性完成了。需要说明的是,由于业务中类层次只有两层,故上述代码不处理父类属性。若有需要,可通过class_getSuperclass()方法枚举父类成员变量,在递归父类时,递归出口为当前枚举的类等于根类NSObject,即cls == [NSObject class]。剩下的是实现基类的description方法。

BaseModel.m

- (NSString *)description {
    NSMutableString *str = [NSMutableString string];
    NSString *className = NSStringFromClass([self class]);
    NSDictionary *dic = [self mapPropertiesToDictionary];
    [dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        [str appendFormat:@"%@ = %@\n", key, obj];
    }];
    return str;
}

至此,功能基本完成。子类只需继承基类,在.h文件中声明属性即可。

User.h  

#import "BaseModel.h"

@interface UserState : BaseModel

@property (nonatomic, copy) NSString *name;

@end

虽然功能实现了,前面的实现还有性能优化空间。

性能优化

每次调用description,都要调用mapPropertiesToDictionary,显然无此必要。故,优化思路是,在基类中维护一个静态哈希表,子类第一次使用description方法才调用mapPropertiesToDictionary,往后都从哈希表中检索已构造的属性值字典。下面给出一种参考实现。

BaseModel.m

static NSMutableDictionary *modelsDescription = nil;

// 在load或initialize方法中初始化哈希表,在此为字典。
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        modelsDescription = [NSMutableDictionary dictionary];
    });
}

// 修改description构造字典处理
- (NSString *)description {
    //...
    if (value) {
        dic = (NSDictionary *)value;
    } else {
        dic = [self mapPropertiesToDictionary];
        [modelsDescription setObject:dic forKey:className];
    }
    //...
}

关于根类NSObject的loadinitialize之间的区别,下次再作讲解。

参考

Objective-C Runtime Reference

转载于:https://www.cnblogs.com/michaellfx/p/4232205.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值