反射根据变量的引用获取变量名

使用介绍

项目有的时候,会遇到一些特殊的处理,想要根据一个实例的引用,获取这个实例在代码中的名称。比如在处理View的坐标的时候,我们将UIView的坐标信息配置到plist文件中,我们可以设置一个key,再通过这个key来获取配置文件中的坐标等信息。有没有更简单的方法呢,或者我只想简单的用实例变量的变量名做为key。下面就介绍一种简单的,根据实例变量的引用获取实例变量名的办法。

转载请保留原本链接:http://my.oschina.net/taptale/blog/110626

引用文件

第一步,我们需要引入我们需要的头文件,在需要使用的类中引用下面代码

#import <objc/runtime.h>

运行原理

我们可以从苹果官方的开发文档中查看到详细的运行时的使用方法及API,官方并没有直接提供根据实例的引用获取实例变量名称的办法,所以我们需要自己去实现。

在官方的API中我们可以找到以下几个方法

(1) Describes the instance variables declared by a class.

Ivar * class_copyIvarList(Class cls, unsigned int *outCount)

(2) Reads the value of an instance variable in an object.

id object_getIvar(id object, Ivar ivar)

(3) Returns the name of an instance variable.

const char * ivar_getName(Ivar ivar)

根据以上的API,我们可以根据变量的拥有者获取所有变量的Ivar,再迭代所有Ivar,每一次迭代做如下操作
根据(2)中的API,我们可以获取到当前迭代中的Ivar对应的实例变量的引用
将获取到的实例变量与传递过来的实例变量的地址比较
如果地址相同,说明当前的Ivar为传递过来实例变量的Ivar,可以通过(3)获取变量的名称并返回

代码

根据上面的原理我们可以得到第一版本的代码,如下:

- (NSString *)nameWithInstance:(id)instance
{
unsigned int numIvars = 0;
NSString *key=nil;
Ivar * ivars = class_copyIvarList([self.target class], &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
if ((object_getIvar(self.target, thisIvar) == instance)) {
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
break;
}
}
free(ivars);
return key;
}

在测试中发现到达上面的if语句的时候,程序有的时候就会crash,经详细测试发现,每次迭代到非objective-c对象的时候,如基本数据类型,BOOL、int、float就会报错。

原因出在object_getIvar这个方法中,当遇到非objective-c对象时,并直接crash,后来查看官方解释

The value of the instance variable specified by ivar, or nil if object is nil.

并没有明确的给出遇到非对象时会crash,也并不会返回nil

我们需要进行一下修正,当遇到非objective-c的时候,需要跳过执行。最终代码如下:

- (NSString *)nameWithInstance:(id)instance
{
unsigned int numIvars = 0;
NSString *key=nil;
Ivar * ivars = class_copyIvarList([self.target class], &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
const char *type = ivar_getTypeEncoding(thisIvar);
NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
if (![stringType hasPrefix:@"@"]) {
continue;
}
if ((object_getIvar(self.target, thisIvar) == instance)) {
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
break;
}
}
free(ivars);
return key;
}