一、Objective-C 对象的本质
Objective-C 源代码
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSInteger age;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
1. 请问 Person 这个类被编译成什么?
在终端中使用clang编译代码
clang -arch x86_64 -rewrite-objc main.m -o main.cpp
编译结果部门为:
typedef struct objc_class *Class;
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
NSInteger _age;
};
相当于 Person_IMPL 结构体继承了NSObject_IMPL相关属性, 每个类第一个属性都是 Class isa
2. Person 属性 set方法根本实现
// name 的 get 方法
// self + name_offset
static NSString * _I_Person_name(Person * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name));
}
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1);
}
static NSInteger _I_Person_age(Person * self, SEL _cmd) {
return (*(NSInteger *)((char *)self + OBJC_IVAR_$_Person$_age));
}
static void _I_Person_setAge_(Person * self, SEL _cmd, NSInteger age) {
(*(NSInteger *)((char *)self + OBJC_IVAR_$_Person$_age)) = age;
}
其中 void objc_setProperty (id, SEL, long, id, bool, bool) 是全局的Set 方法,所有类的对象属性的Set 方法都会调用这个方法, 但基础数据类型属性是直接赋值的
objc_setProperty 源码具体实现
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
{
bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
bool mutableCopy = (shouldCopy == MUTABLE_COPY);
reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
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);
}
3. union 联合体位域
union 共用体名{
成员列表
};
结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存。共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉, 所以节省了内存空间
union data{
int n;
char ch;
double f;
};
union data a, b, c;
printf("size of union = %lu",sizeof(a)); // size of union = 8
有时候为了节省内存占用可以使用的技术
@interface Car : NSObject
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL back;
@property (nonatomic, assign) BOOL right;
@end
@implementation Car
@end
四个 BOOL属性占用内存为 4 字节(sizeof(BOOL)= 1), 因为每次只能选择一个方向,所以有点内存浪费,直接用 1 bit 表示一个方向也是可以的
union direction_t {
char bits; // 1 字节
struct {
char front: 1; // 1 bit
char left: 1; // 1 bit
char back: 1; // 1 bit
char right: 1; // 1 bit
};
};
printf("size of union direction_t = %lu",sizeof(_direction));
//size of union direction_t = 1
4. ISA 指针也是 64 位,总共有 64 bit
#define __x86_64__ 1
typedef unsigned long uintptr_t;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# 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 deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
union m_isa_t {
m_isa_t(){ }
m_isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
isa 指针初始化,并且和类关联
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
// 1. 如果是纯粹的指针
isa = isa_t((uintptr_t)cls);
} else {
// 2. 如果是 位域
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#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
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#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;
}
}
5. isa 详解
-
nonpointer: 表示是否对 isa 指针开启指针优化
- 0:纯isa指针,
- 1:不止是类对象地址**, isa** 中包含了类信息、对象的引用计数等
-
has_assoc: 关联对象标志位,0没有,1存在
-
has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器**,如果有析构函数,则需要做析构逻辑,** 如果没有**,**则可以更快的释放对象
-
**shiftcls:**存储类指针的值。开启指针优化的情况下,在 arm64 架构中有 33 位用来存储类指针。
-
magic:用于调试器判断当前对象是真的对象还是没有初始化的空间
-
weakly_referenced:志对象是否被指向或者曾经指向一个 ARC 的弱变量,
-
没有弱引用的对象可以更快释放。 deallocating:标志对象是否正在释放内存
-
has_sidetable_rc:当对象引用技术大于 10 时,则需要借用该变量存储进位
测试 shiftcls 怎么存储类信息了
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
}
debug 调试 initIsa 方法
// 当前类
(lldb) p cls
(Class) $4 = Person
// 当前类
(lldb) p/x cls
(Class) $5 = 0x00000001000020f8 Person
// 当前类信息 向右 移 3位
(lldb) p 0x00000001000020f8 >> 3
(long) $6 = 536871967
// 当前类信息 向右 移 3位
(lldb) p/x 0x00000001000020f8 >> 3
(long) $7 = 0x000000002000041f
// 生成的 ISA 指针
(lldb) p newisa
(isa_t) $8 = {
cls = Person
bits = 8303516107940089
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 536871967
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
// 打印生成的对象
(lldb) p/x obj
(Person *) $10 = 0x000000010073bfe0
// 打印生成的对象的isa 指针
(lldb) p/x obj->isa
(Class) $14 = 0x001d8001000020f9 Person
// 打印生成的对象的isa 指针 & Mask 即为当前对象的类对象地址
(lldb) p/x 0x001d8001000020f9 & 0x00007ffffffffff8ULL
(unsigned long long) $15 = 0x00000001000020f8
打印生成的对象的isa 指针
(lldb) p/x obj->isa
(Class) $14 = 0x001d8001000020f9 Person
获取当前对象的类对象
Mask 0x00007ffffffffff8ULL 具体是什么呢? 中间的44位的都为1 ,进行一个简答的 & 运算
(lldb) p/t 0x00007ffffffffff8ULL
(unsigned long long) $21 = 0b0000000000000000011111111111111111111111111111111111111111111000
6. isa 在赋值过magicValue发生了什么?
// 赋值前
p newisa
(isa_t) $1 = {
cls = nil
bits = 0
= {
nonpointer = 0
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 0
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
// 赋值后
newisa.bits = ISA_MAGIC_VALUE;
p newisa
(isa_t) $2 = {
cls = 0x001d800000000001
bits = 8303511812964353
= {
nonpointer = 1
has_assoc = 0
has_cxx_dtor = 0
shiftcls = 0
magic = 59
weakly_referenced = 0
deallocating = 0
has_sidetable_rc = 0
extra_rc = 0
}
}
为什么magic 变成了 59?
0x001d800000000001 转换成二进制位
p/t 0x001d800000000001
(long) $11 = 0b0000000000011101100000000000000000000000000000000000000000000001
magic 的位域是 48位开始 ,并且占6位
p 0b00111011
(int) $10 = 59
7. object_getClass 实现
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
// 这边是计算 TaggedPointer的类信息
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
// 计算非 TaggedPointer的类信息
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
8. 通过位运算来验证 类信息存储
// 打印当前对象
(lldb) po obj
<Person: 0x1006982f0>
// 方法一: 读取当前对象的isa 指针
(lldb) x/4gx 0x1006982f0
0x1006982f0: 0x001d8001000020f9 0x0000000000000000
0x100698300: 0x75726353534e5b2d 0x6d65744972656262
// 方法二: 读取当前对象的isa 指针
(lldb) po obj->isa
Person
(lldb) p/x obj->isa
(Class) $35 = 0x001d8001000020f9 Person
// 进行位运算
(lldb) p/x 0x001d8001000020f9 >> 3 << 20 >> 17
(long) $36 = 0x00000001000020f8
// 获取当前对象的类信息
(lldb) p object_getClass(obj)
(Class) $37 = Person
(lldb) p/x object_getClass(obj)
(Class) $38 = 0x00000001000020f8 Person
最后得到的类地址都为 0x00000001000020f8
9. isa_t 和 Class 的转化
inline Class
objc_object::ISA() {
return (Class)(isa.bits & ISA_MASK);
}
10. 判断对象指针是不是TaggedPointer 待续
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}