Objective-C ISA 和类关联

225 阅读6分钟

一、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;
}

11. cache 待续

LLDB

sizeof