Objective-C运行时编程 - 实现自动化description方法的思路及代码示例
发布自米高 | Michael - 博客园,源地址:http://www.cnblogs.com/michaellfx/p/4232205.html,转载请注明。
本文结构
关键字:Objective-C OC description函数 自动打印属性及属性值 运行时枚举成员变量
基础实现
使用NSLog或po,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的load与initialize之间的区别,下次再作讲解。
参考
Objective-C Runtime Reference
Objective-C运行时编程 - 实现自动化description方法的思路及代码示例的更多相关文章
- iOS运行时编程(Runtime Programming)和Java的反射机制对比
运行时进行编程,类似Java的反射.运行时编程和Java反射的对比如下: 1.相同点 都可以实现的功能:获取类信息.属性设置获取.类的动态加载(NSClassFromString(@“clas ...
- Objective-C 2.0的运行时编程
Objective-C 2.0 的运行时环境叫做Morden Runtime,iOS 和Mac OS X 64-bit 的程序都运行在这个环境,也就是说Mac OS X 32-bit 的程序运行在旧的 ...
- Objective-C运行时编程 - 方法混写 Method Swizzling
摘要: 本文描述方法混写对实例.类.父类.不存在的方法等情况处理,属于Objective-C(oc)运行时(runtime)编程范围. 编程环境:Xcode 6.1.1, Yosemite,iOS 8 ...
- iOS运行时使用(动态添加方法)
1 举例 我们实现一个Person类 然后Person 其实是没得对象方法eat:的 下面调用person的eat方法 程序是会奔溃的 那么需要借助运行时动态的添加方法 Person *p = [[ ...
- [转] Java运行时动态生成class的方法
[From] http://www.liaoxuefeng.com/article/0014617596492474eea2227bf04477e83e6d094683e0536000 廖雪峰 / 编 ...
- iOS 运行时使用(交换两个方法)
举例 在创建了如下代码 NSString *str=nil; NSURL *url =[NSURL URLWithString:str]; NSLog(@"%@",url); 但是 ...
- jvm入门及理解(四)——运行时数据区(堆+方法区)
一.堆 定义: Heap,通过new关键字创建的对象,都存放在堆内存中. 特点 线程共享,堆中的对象都存在线程安全的问题 垃圾回收,垃圾回收机制重点区域. jvm内存的划分: JVM内存划分为堆内存和 ...
- mxnet运行时遇到问题及解决方法
1.训练好模型之后,进行预测时出现这种错误: mxnet.::] src/ndarray/ndarray.cc:: Check failed: ,) to.shape=(,) 这种问题的解决方法,在全 ...
- loadrunner运行时设置中清空缓存方法
用函数web_cache_clearup()或run-time settings---browser emulation 把clear cache on each iteration打勾 W v\] ...
随机推荐
- linux 下查看系统内存使用情况的方法
在Windows系统中查看内存的使用情况很简单,想必大家都已经耳熟能详了,那么在linux系统如何查看内存使用情况呢?下面和大家分享在Linux 下查看内存使用情况的free命令: [root@scs ...
- shell脚本基础——常用的sed命令举例
一般在实际使用编辑器的过程中 , 常需要执行替换文件中的字符串.移动.删除.与搜寻数据行等等动作.当然 , 一般交互式编辑器(如 vi.emacs)都能做得到上述功能 , 但文件一旦有大量上述编辑需求 ...
- Android开发之旅:环境搭建及HelloWorld
引言 本系列适合0基础的人员,因为我就是从0开始的,此系列记录我步入Android开发的一些经验分享,望与君共勉!作为Android队伍中的一个新人的我,如果有什么不对的地方,还望不吝赐教. 在开始A ...
- linux笔记_20150825_linux下的软件工具唠叨下
这些都是书上看到的,有些工具我也没有完全用过.先记下来再说.闲着也是闲着. 1.linux下常见的语言及编程环境:c/c++/java/perl/fortan等. 2.图形环境:gnome/kde/g ...
- Struts2注解 特别注意
1 Struts2注解的作用 使用注解可以用来替换struts.xml配置文件!!! 2 导包 必须导入struts2-convention-plugin-2.3.15.jar包,它在struts2安 ...
- 常用SQL整理
整理了日常用到的一些sqls 1.插入表 insert into table_B select * from table_A 2.清空表 truncate table test #清空表,结构还存在d ...
- HDOJ-ACM1009(JAVA) (传说中的贪心算法)分为数组实现 和 封装类实现
转载声明:原文转自:http://www.cnblogs.com/xiezie/p/5564311.html 这个道题有几点要注意的: 数组存放的类型:float或double 打印的格式:(如果只是 ...
- 如何在Fedora 22上面配置Apache的Docker容器
在这篇文章中,我们将会学习关于Docker的一些知识,如何使用Docker部署Apache httpd服务,并且共享到Docker Hub上面去.首先,我们学习怎样拉取和使用Docker Hub里面的 ...
- ControlsFX8.0.2中对话框无法判断是否显示的修改
在org.controlsfx.dialog.FXDialog.java中加入 public abstract boolean isShowing(); 在org.controlsfx.dialog. ...
- oracle max()函数和min()函数
当需要了解一列中的最大值时,可以使用MAX()函数:同样,当需要了解一列中的最小值时,可以使用MIN()函数.语法如下. SELECT MAX (column_name) / MIN ...