在阅读完property_t
相关代码之后,接下来学习和property_t
十分相似的内容Ivar
,我们就不再去做一个和上一篇文章一样的示例代码了,我们直接从Ivar
的定义开始学习相关内容。
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
从Ivar
的定义可以看到Ivar
有四个属性ivar_name
, ivar_type
, ivar_offset
分别代表了属性的名称
,类型
,内存偏移量
。
然后我们回过头来再去阅读类的定义,可以看到objc_class
中定义了一个objc_ivar_list
用于存放属性,接下来我们看看objc_ivar_list
的定义
struct objc_class {
...
// objc存在一个objc_ivar_list的指针,用于保存属性
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
...
} OBJC2_UNAVAILABLE;
// objc_ivar_list 定义
struct objc_ivar_list {
// 属性数量
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
在objc_ivar_list
的定义中,可以看到定义了ivar_count
用于存储属性数量,ivar_list
用于存储Ivar
。
然后我们继续学习Ivar
相关的方法,和property_t
一样,我们先来看获取Ivar
相关属性的方法。
ivar_getName、ivar_getTypeEncoding和ivar_getOffset
我们简单的看一下这三个方法的代码。
// 获取属性的名称
const char *
ivar_getName(Ivar ivar)
{
if (!ivar) return nil;
return ivar->name;
}
// 获取属性的类型
const char *
ivar_getTypeEncoding(Ivar ivar)
{
if (!ivar) return nil;
return ivar->type;
}
// 获取属性的偏移量
ptrdiff_t
ivar_getOffset(Ivar ivar)
{
if (!ivar) return 0;
return *ivar->offset;
}
和property_t
相较,Ivar
获取相关属性就简单许多,直接获取了相关的名称,属性等,但是,获取类型的方法,相较property_t
只返回了变量的类型,并没有返回变量的相关的修饰符,如strong
,weak
等。
class_copyIvarList
接下来我们看获取类中的变量列表。
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;
}
mutex_locker_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;
}
从上述代码可以看出ivars
的存储结构是cls->data()->ro->ivars
,然后也是和property_t
一样分配空间给result
,最后拼接成返回结果。
object_setIvar和object_setIvarWithStrongDefault
我们可以通过object_setIvar
和object_setIvarWithStrongDefault
去修改变量的值。
// 如果内存管理方式没有设置,使用weak
void object_setIvar(id obj, Ivar ivar, id value)
{
return _object_setIvar(obj, ivar, value, false /*not strong default*/);
}
// 如果内存管理方式没有设置,使用strong
void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value)
{
return _object_setIvar(obj, ivar, value, true /*strong default*/);
}
// `object_setIvar`和`object_setIvarWithStrongDefault`最后都回使用`_object_setIvar`去设置值。
static ALWAYS_INLINE
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
if (!obj || !ivar || obj->isTaggedPointer()) return;
ptrdiff_t offset;
objc_ivar_memory_management_t memoryManagement;
// 查找ivar的offset,及内存存储方式。
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
if (memoryManagement == objc_ivar_memoryUnknown) {
if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
else memoryManagement = objc_ivar_memoryUnretained;
}
//计算ivar的内存位置
id *location = (id *)((char *)obj + offset);
// 根据内存存储方式进行赋值
switch (memoryManagement) {
case objc_ivar_memoryWeak: objc_storeWeak(location, value); break;
case objc_ivar_memoryStrong: objc_storeStrong(location, value); break;
case objc_ivar_memoryUnretained: *location = value; break;
case objc_ivar_memoryUnknown: _objc_fatal("impossible");
}
}
为示例对象赋值的方法还有object_setInstanceVariable
和object_setInstanceVariableWithStrongDefault
,这两个方法可以为实例变量赋值。
object_getIvar
我们可以通过object_getIvar
获取实例对象的变量值。
id object_getIvar(id obj, Ivar ivar)
{
if (!obj || !ivar || obj->isTaggedPointer()) return nil;
ptrdiff_t offset;
objc_ivar_memory_management_t memoryManagement;
// 查找ivar的offset,及内存存储方式。
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
//计算ivar的内存位置
id *location = (id *)((char *)obj + offset);
// 根据内存管理方式返回对应的值
if (memoryManagement == objc_ivar_memoryWeak) {
return objc_loadWeak(location);
} else {
return *location;
}
}
class_addIvar
我们也可以通过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;
mutex_locker_t lock(runtimeLock);
// 检查类是否可以被识别
checkIsKnownClass(cls);
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相关属性
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;
}
总结
ivar
也可以满足我们添加和获取变量,那和property_t
有什么区别呢?
property
包含了ivar
及getter
和setter
方法,也就是说我们添加一个property
会直接为我们增加getter
和setter
的方法,而ivar
只会增加属性。
所以,我们在大多数时候都会选择property
而不是ivar
。
更好的阅读体验可以参考个人网站:https://zevwings.com