Objective-C运行时编程

发布自米高 | 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:@"%@ = %@
", 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