runtime使用篇: object_getIvar、class_copyIvarList、ivar_getName、ivar_getTypeEncoding 和 class_getInstanceVariable

前言:
  • 本篇文章将介绍以下几个和实例变量ivar相关的runtime函数的使用
    id object_getIvar(id obj, Ivar ivar)
    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
    const char *ivar_getName(Ivar v)
    const char *ivar_getTypeEncoding(Ivar v)
    Ivar class_getInstanceVariable(Class cls, const char *name)
1. id object_getIvar(id obj, Ivar ivar)

分析: Ivar,即InstanceVariable(实例变量)。runtime对该函数的说明为:
Reads the value of an instance variable in an object.
即获取一个对象obj的实例变量ivar的值。要使用这个函数,首先需要一个Ivar,我们使用class_copyIvarList函数获取一个Ivar数组从而获取一个Ivar,现在先看看class_copyIvarList函数是怎么使用的

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

说明:该函数的作用是获取传入类的所有实例变量,返回的是实例变量数组
UITextField类为例,代码示例如下:

unsigned int outCount;
Ivar *ivars = class_copyIvarList([UITextField class], &outCount);

for (int i = 0; i < outCount; i++) {
    Ivar ivar = ivars[i];
}

free(ivars);

说明:由于ARC只适用于Foundation等框架,对于Core Foundationruntime 等并不适用,所以在使用带有copyretain等字样的函数或方法时需要手动释放free()
获取到Ivar后可以利用 ivar_getName 函数获取 Ivar 的名称,用 ivar_getTypeEncoding 函数获取 Ivar 的类型编码,通过类型编码就可以知道该 Ivar 是何种类型的。
关于类型编码,这里稍微说一点:

为了协助runtime系统,编译器用字符串为每个方法的返回值和参数的类型进行了编码,并把该编码和方法选择器进行绑定。这种编码方案在其他上下文环境中也是有用的,因此它可以使用编译器指令@encode()进行获取。当给定一种类型时,@encode()就会返回对这种类型进行编码后的字符串,类型可以是基本类型比如int、指针、结构、并集、类的名称等等,事实上,它们也都可以作为C语言sizeof()运算符的参数。(每种基本类型和编码的对应关系详见苹果官方文档以及这篇译文)

整体代码如下:

unsigned int outCount; // 1
Ivar *ivars = class_copyIvarList([UITextField class], &outCount); // 2

for (int i = 0; i < outCount; i++) { // 3
    Ivar ivar = ivars[i]; // 4
    const char *ivarName = ivar_getName(ivar); // 5
    const char *ivarType = ivar_getTypeEncoding(ivar); // 6
    NSLog(@"实例变量名为:%s 字符串类型为:%s", ivarName, ivarType); // 7
} // 8
free(ivars); // 9

打印结果如下:

runtime[6630:406123] 实例变量名为:_textStorage 字符串类型为:@"_UICascadingTextStorage"
runtime[6630:406123] 实例变量名为:_borderStyle 字符串类型为:q
runtime[6630:406123] 实例变量名为:_minimumFontSize 字符串类型为:d
runtime[6630:406123] 实例变量名为:_delegate 字符串类型为:@
runtime[6630:406123] 实例变量名为:_background 字符串类型为:@"UIImage"
runtime[6630:406123] 实例变量名为:_disabledBackground 字符串类型为:@"UIImage"
runtime[6630:406123] 实例变量名为:_clearButtonMode 字符串类型为:q
runtime[6630:406123] 实例变量名为:_leftView 字符串类型为:@"UIView"
runtime[6630:406123] 实例变量名为:_leftViewMode 字符串类型为:q
runtime[6630:406123] 实例变量名为:_rightView 字符串类型为:@"UIView"
// 省略大部分
runtime[6630:406123] 实例变量名为:_placeholderLabel 字符串类型为:@"UITextFieldLabel"
runtime[6630:406123] 实例变量名为:_suffixLabel 字符串类型为:@"UITextFieldLabel"
// 省略大部分

现在就获取到了UITextField类的所有实例变量,包括私有的。

使用场景:

有了这些实例变量就可以更方便地更改UITextField了,比如更改UITextField的占位字体颜色,占位字体默认的颜色偏向于亮灰色,如下图:

默认的占位字体.png

由于 UITextField没有提供直接修改占位字体颜色的属性,现在我们用其他方法尝试。从刚才打印的实例变量名称可以推测实例变量 _placeholderLabel是显示占位字体的一个 UILabel,现在验证一下,在刚才代码的第9行后添加如下代码:

// self.textField就是上图显示的UITextField
id value = [self.textField valueForKey:@"_placeholderLabel"]; // 10
NSLog(@"value class:%@, value superclass:%@", NSStringFromClass([value class]), NSStringFromClass([value superclass])); // 11

打印结果如下:

runtime[44760:4568971] value class:UITextFieldLabel, value superclass:UILabel

可以看到,实例变量_placeholderLabel的类和刚才用类型编码获取到的相同。
既然实例变量_placeholderLabelUILabel子类的实例,那就可以根据UILabeltextColor属性利用KVCUITextField更改字体颜色了:

[self.textField setValue:[UIColor orangeColor] forKeyPath:@"_placeholderLabel.textColor"]; // 12

重新进行后效果如下图:

修改后的占位字体.png
3. Ivar class_getInstanceVariable(Class cls, const char *name)

此时再回到第一个函数id object_getIvar(id obj, Ivar ivar)的使用方法,有了Ivar名称后如果需要再用到该Ivar,就需要用到class_getInstanceVariable函数,该函数是的作用是获取指定类的指定名称的实例变量。现在在刚才的代码后添加如下代码:

Ivar ivar = class_getInstanceVariable([UITextField class], "_placeholderLabel");
id getIvar = object_getIvar(self.textField, ivar);
NSLog(@"%@", getIvar);

打印结果如下:

runtime[45217:4596711] >

得到的即是对象self.textField的ivar名称为_placeholderLabel的值。

你可能感兴趣的:(runtime使用篇: object_getIvar、class_copyIvarList、ivar_getName、ivar_getTypeEncoding 和 class_getInstanceVariable)