Objc类与isa

类与isa关联原理

Objc对象的本质

oc定义的类在经过Clang编译转为C++后,我们就可以看到oc中的类实际上是结构体,如下:

1
2
3
4
5
6
7
8
9
10
// NSObject的定义
struct NSObject_IMPL {
Class isa;
};

// 继承自NSObject的TestClass的定义
struct TestClass_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
};

上面转译出来的代码中,可以看到TestClass结构体里面包含它所继承的NSObject的完整结构。而oc对象的本质其实就是结构体,子类中包含完整的父类结构体,从而达到继承的目的。

objc_setProperty流程

转译出来的代码中,在类结构体的定义下面还能看到属性的settergetter,这里我们着重了解setter的实现,因为与本文后续的内容有关。

1
static void _I_TestClass_setName_(TestClass * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct TestClass, _name), (id)name, 0, 1); }

OC给属性生成的setter其实都是对objc_setProperty这个方法的封装。入参里面值得注意的是它要求外层调用时,传入类对象实例本身和属性的偏移,偏移通过__OFFSETOFIVAR__传入类和属性名来计算。完整的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
// 设置isa信息
object_setClass(self, newValue);
return;
}

id oldValue;
id *slot = (id*) ((char*)self + offset);

if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}

if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}

objc_release(oldValue);
}

reallySetProperty接口可以关联上层set方法和底层set方法,上层定义的属性类型有很多,如果都直接调用底层set方法,会产生很多临时变量,影响代码可读性和可维护性。这里用到了适配器模式的设计思想,上下层接口隔离,想互不影响。

cls与类关联原理

下面是isa_t的定义,它是isa指针的类型,从定义中可以看到它用到了union。这里简单介绍一下union这种结构,它也是由不同数据类型组成的,但所有成员共同占用一段内存,成员间是互斥的关系,即同一时刻只保留一个成员的值,所以包容性弱,但能带来内存占用上的优势。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }

uintptr_t bits;

private:
// 出于安全性的考虑,防止对象类信息被恶意篡改,访问cls需要进行指针验证
// 因此cls变量定义成私有变量,只能通过setClass和getClass进行访问
Class cls;

public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};

#if ISA_HAS_INLINE_RC
bool isDeallocating() const {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif // ISA_HAS_INLINE_RC

#endif

void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated) const;
Class getDecodedClass(bool authenticated) const;
};

isa_t定义了两个互斥的成员变量clsbits,还提供了一个匿名的内嵌结构体,里面的宏定义ISA_BITFIELD用于描述bits各段所代表的含义,在isa.h中可以看到定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
#       define ISA_BITFIELD                                                        \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
/*存储类信息*/
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
/*使用用到sidetable来存引用计数*/
uintptr_t has_sidetable_rc : 1; \
/*额外的引用计数*/
uintptr_t extra_rc : 8

initIsa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());

isa_t newisa(0);

if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());


#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
#if ISA_HAS_INLINE_RC
newisa.extra_rc = 1;
#endif
}

// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa() = newisa;
}

cls与类关联的核心逻辑,根据nonpointer这个变量划分成两部分,通过cls初始化和通过bits初始化。在与类信息相关的部分逻辑是相似的,都是通过setClassshiftcls字段赋值,setClass方法内部包含一些宏定义,主要区分是否开启isa签名,根据签名模式的不同决定是否需要进行指针验证(ptrauth)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// Set the class field in an isa. Takes both the class to set and
// a pointer to the object where the isa will ultimately be used.
// This is necessary to get the pointer signing right.
//
// Note: this method does not support setting an indexed isa. When
// indexed isas are in use, it can only be used to set the class of a
// raw isa.
inline void
isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{
// Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE
// No signing, just use the raw pointer.
uintptr_t signedCls = (uintptr_t)newCls;

# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT
// We're only signing Swift classes. Non-Swift classes just use
// the raw pointer
uintptr_t signedCls = (uintptr_t)newCls;
if (newCls->isSwiftStable())
signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

# elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL
// We're signing everything
uintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));

# else
# error Unknown isa signing mode.
# endif

shiftcls_and_sig = signedCls >> 3;

#elif SUPPORT_INDEXED_ISA
// Indexed isa only uses this method to set a raw pointer class.
// Setting an indexed class is handled separately.
cls = newCls;

#else // Nonpointer isa, no ptrauth
shiftcls = (uintptr_t)newCls >> 3;
#endif
}

getClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) const {
#if SUPPORT_INDEXED_ISA
return cls;
#else

uintptr_t clsbits = bits;

# if __has_feature(ptrauth_calls)
# if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
// Most callers aren't security critical, so skip the
// authentication unless they ask for it. Message sending and
// cache filling are protected by the auth code in msgSend.
if (authenticated) {
// Mask off all bits besides the class pointer and signature.
clsbits &= ISA_MASK;
if (clsbits == 0)
return Nil;
clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
} else {
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
}
# else
// If not authenticating, strip using the precomputed class mask.
clsbits &= objc_debug_isa_class_mask;
# endif

# else
clsbits &= ISA_MASK;
# endif

return (Class)clsbits;
#endif
}

理解了设置cls的流程,获取的流程就很好理解了,拿到isa中的所有字节,与ISA_MASK进行按位与,即可拿到cls字段对应的字节。