白话Runtime之iVar

iVar

实例变量增加了私有属性的设置,同时直接使用实例变量比使用属性的方式提高了运行速度。

Features

iVar变量介绍

这里首先认为大家都对Runtime有了一定的了解,不清楚的建议看看 南峰子的技术博客

还是稍微介绍下Ivar的的结构,Ivar其实是一个结构体指针:

typedef struct objc_ivar *Ivar;

再来查看这个结构体的结构:

struct objc_ivar {
    char *ivar_name      // 变量名                                    
    char *ivar_type      // 变量类型                                    
    int ivar_offset      // 基地址偏移字节                                    
 #ifdef __LP64__
    int space                                                
 #endif
}                                                            

输出所有的iVar的值

首先定义一个类,包含几个属性以及变量的值:

@interface Model : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) int age;

@end

同时在extension中生命iVar变量:

@interface Model () {
    
@private
    int _value;
    NSString *_title;
}

@end

输出的方式如下:

Model *obj = [[Model alloc] init];
uint32_t outCount = 0;
Ivar *ivars = class_copyIvarList(object_getClass(obj), &outCount);
for (uint32_t i = 0; i < outCount; i++) {
    Ivar ivar = ivars[i];
    const char *name = ivar_getName(ivar);
    const char *type = ivar_getTypeEncoding(ivar);
    ptrdiff_t offset = ivar_getOffset(ivar);
    NSLog(@"name=%s---type=%s---offset=%td", name, type, offset);
}
free(ivars);

输出:

name=_value---type=i---offset=8
name=_title---type=@"NSString"---offset=16
name=_age---type=i---offset=24
name=_name---type=@"NSString"---offset=32

Type Encodings可参考 Objective-C Runtime Programming Guide

使用Runtime修改iVar的值

对于iVar的值,相当于私有属性,外部是无法访问的。不过在Runtime面前,一切都是小小case。

在外部可以通过runtime的方法来修改他:

Ivar ivar2 = class_getInstanceVariable(object_getClass(obj), "_title");
object_setIvar(obj, ivar2, @"newTiele");

可以给Model类暴露个打印的方法,在修改完数值之后调用方法观察数值是否发生了改变。

对于上面方法的原型:

void object_setIvar(id obj, Ivar ivar, id value) 

俩参数为id,所以不能对基本类型做修改,可以通过偏移量的方式来获取,修改的方式如下:

Ivar ivar1 = class_getInstanceVariable(object_getClass(obj), "_value");
int *ivarPtr2 = (int *)((uint8_t *)(long)obj + ivar_getOffset(ivar1));
*ivarPtr2 = 222;

这样我们就能随心所欲的修改各种变量的值了。

iVar vs Property

现在声明的属性,如果没有声明为dynamic,默认都是synthesize的。也就是会自动合成iVar变量。

那什么时候使用属性,什么时候直接使用实例变量呢?

首先要了解通过属性的方式来设置,其实内部还是设置实例变量的方式,无非是多了自己对象的判断以及对于引用计数的管理。所以直接访问实例变量的方式要快一些。

下面给出几种参考:

  1. 如果一个对象在它的生命周期中只在 alloc & init 时对引用计数做了 retain 操作,可直接使用实例变量的方式。

  2. 如果一个对象经常改变,使用属性的方式,而且使用点语法的方法进行访问(在本身的 getter & setter * dealloc & initializers 等方法里使用实例变量)。

  3. 私有变量的方式可以通过 extension 的方法,在 .m 文件里声明;暴露出去的方法需要在 .h 文件中以属性的方式来声明。

  4. 某些通过 setter & getter 方法访问的情形,只能使用属性。比如通过懒加载的方式来进行某些优化,设置观察者等等。

  5. 通过某些方法访问属性的时候,比如增加 KVCKeyPath,一般通过他的 getter 方法,使用为 NSStringFromSelector(@selector(isFinished)) 来访问, 而非直接设置 isFinished 的方式。

比如生命一个 NSMutableArray, 基本不对他的引用计数做修改,只是使用它做些存储操作,可直接使用实例变量。

参考