Ivar是什么
Ivar(instance variable),实例变量,是对象中真正存储信息的变量。
一个对象的Ivar列表是怎么存储的?
结论就是Ivar列表是顺序存储的,最底层就是一个结构体数组,可以一步步查看源代码:
单个Ivar结构如下
typedef struct ivar_t *Ivar;
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
...
};
获取一个Ivar方法如下,从中可以看出Ivar列表是顺序存储的
static ivar_t *getIvar(Class cls, const char *name)
{
runtimeLock.assertLocked();
const ivar_list_t *ivars;
assert(cls->isRealized());
if ((ivars = cls->data()->ro->ivars)) {
for (auto& ivar : *ivars) {
if (!ivar.offset) continue; // anonymous bitfield
// ivar.name may be nil for anonymous bitfields etc.
if (ivar.name && 0 == strcmp(name, ivar.name)) {
return &ivar;
}
}
}
return nil;
}
那么Ivar在类中是怎么存储的呢?
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
...
}
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
...
}
可见类中除了“ISA、superclass、cache”的数据全都存储在bits中;bits的data()返回的是class_rw_t结构,表示一个类需要读写数据;而我们寻找的Ivar存储在其中的只读数据部分,即const class_ro_t *ro;
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
...
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
...
};
终于见到我们想要的ivar_list了,那么他到底是个什么样的数据结构呢?
struct ivar_list_t : entsize_list_tt<ivar_t, ivar_list_t, 0> {
bool containsIvar(Ivar ivar) const {
return (ivar >= (Ivar)&*begin() && ivar < (Ivar)&*end());
}
};
/***********************************************************************
* entsize_list_tt<Element, List, FlagMask>
* Generic implementation of an array of non-fragile structs.
*
* Element is the struct type (e.g. method_t)
* List is the specialization of entsize_list_tt (e.g. method_list_t)
* FlagMask is used to stash extra bits in the entsize field
* (e.g. method list fixup markers)
**********************************************************************/
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
Element& getOrEnd(uint32_t i) const {
assert(i <= count);
return *(Element *)((uint8_t *)&first + i*entsize());
}
...
}
通过注释我们也能看明白entsize_list_tt是个顺序存储的结构,因此Ivar是顺序存储的,为了加深理解,我们再看一下拷贝Ivar列表时如何分配Ivar的存储空间:
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
{
const ivar_list_t *ivars;
Ivar *result = nil;
unsigned int count = 0;
if (!cls) {
if (outCount) *outCount = 0;
return nil;
}
rwlock_reader_t lock(runtimeLock);
assert(cls->isRealized());
if ((ivars = cls->data()->ro->ivars) && ivars->count) {
result = (Ivar *)malloc((ivars->count+1) * sizeof(Ivar));
for (auto& ivar : *ivars) {
if (!ivar.offset) continue; // anonymous bitfield
result[count++] = &ivar;
}
result[count] = nil;
}
if (outCount) *outCount = count;
return result;
}
为什么已注册的类不能添加Ivar,但却可以添加method?
先看一下runtime.h中关于添加Ivar的接口声明
/**
* Adds a new instance variable to a class.
*
* @return YES if the instance variable was added successfully, otherwise NO
* (for example, the class already contains an instance variable with that name).
*
* @note This function may only be called after objc_allocateClassPair and before objc_registerClassPair.
* Adding an instance variable to an existing class is not supported.
* @note The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
* @note The instance variable's minimum alignment in bytes is 1<<align. The minimum alignment of an instance
* variable depends on the ivar's type and the machine architecture.
* For variables of any pointer type, pass log2(sizeof(pointer_type)).
*/
OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size,
uint8_t alignment, const char *types)
文档中要求class_addIvar
必须在 objc_allocateClassPair
之后且objc_registerClassPair
之前调用,向一个已经注册的类添加Ivar是不支持的。
经过编译过程的类,在加载的时候已经注册了,根本没有时机让你添加实例变量;而运行时创建的新类,可以在objc_registerClassPair
之前通过class_addIvar
添加实例变量,一旦注册完成后,也不能添加实例变量了。
BOOL class_addIvar(Class cls, const char *name, size_t size,
uint8_t alignment, const char *type)
{
if (!cls) return NO;
if (!type) type = "";
if (name && 0 == strcmp(name, "")) name = nil;
rwlock_writer_t lock(runtimeLock);
assert(cls->isRealized());
// No class variables
if (cls->isMetaClass()) {
return NO;
}
// Can only add ivars to in-construction classes.
if (!(cls->data()->flags & RW_CONSTRUCTING)) {
return NO; //已经注册的类,在这里就直接返回了
}
// Check for existing ivar with this name, unless it's anonymous.
// Check for too-big ivar.
// fixme check for superclass ivar too?
if ((name && getIvar(cls, name)) || size > UINT32_MAX) {
return NO;
}
class_ro_t *ro_w = make_ro_writeable(cls->data());
// fixme allocate less memory here
ivar_list_t *oldlist, *newlist;
if ((oldlist = (ivar_list_t *)cls->data()->ro->ivars)) {
size_t oldsize = oldlist->byteSize();
newlist = (ivar_list_t *)calloc(oldsize + oldlist->entsize(), 1);
memcpy(newlist, oldlist, oldsize);
free(oldlist);
} else {
newlist = (ivar_list_t *)calloc(sizeof(ivar_list_t), 1);
newlist->entsizeAndFlags = (uint32_t)sizeof(ivar_t);
}
uint32_t offset = cls->unalignedInstanceSize();
uint32_t alignMask = (1<<alignment)-1;
offset = (offset + alignMask) & ~alignMask;
ivar_t& ivar = newlist->get(newlist->count++);
#if __x86_64__
// Deliberately over-allocate the ivar offset variable.
// Use calloc() to clear all 64 bits. See the note in struct ivar_t.
ivar.offset = (int32_t *)(int64_t *)calloc(sizeof(int64_t), 1);
#else
ivar.offset = (int32_t *)malloc(sizeof(int32_t));
#endif
*ivar.offset = offset;
ivar.name = name ? strdupIfMutable(name) : nil;
ivar.type = strdupIfMutable(type);
ivar.alignment_raw = alignment;
ivar.size = (uint32_t)size;
ro_w->ivars = newlist;
cls->setInstanceSize((uint32_t)(offset + size));
// Ivar layout updated in registerClass.
return YES;
}
注册完类后为啥不让添加实例变量呢?
网上答案说已注册类的实例对象大小和内存布局都已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的。其实还没有从逻辑上讲明白为啥不让添加实例变量。
我们知道一个类只有注册过才能用来创建对象,假设一个已经注册过的类创建了对象A,然后我们又给这个类增加了一个实例变量,并用这个类又创建了对象B,那么A和B的存储结构都不一样,那么A和B还能算是同一类对象吗?所以从逻辑上讲,也不能允许添加实例变量。
那为又啥能让添加方法呢?
因为方法列表存放在类对象中,为类对象增加一个方法,所有该类的实例对象都会拥有这个方法,因此实例对象之间没有造成差异,还是同一个类型。
网友评论