iOS-底层原理-地址指针如何通过isa关联类信息

本文的主要目的是理解类与isa是如何关联的

在介绍正文之前,首先需要理解一个概念:OC对象的本质是什么?

1.对象的本质

在探索OC对象本质前,先了解一个编译器:clang

1.Clang
Clang是一个由Apple主导变现,基于LLVM的C/C++/Object-C编译器

主要是用于底层编译,将一些文件输出c++文件,例如将main.m输出成main.cpp,其目的是为了更好的观察底层的一些结构实现逻辑,方便理解底层原理。

2.main.m里自定义LGPerson类,两个属性name和title

#import <Cocoa/Cocoa.h>

@interface LGPerson : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic, copy) NSString * title;
@end

@implementation LGPerson
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
    }
    return NSApplicationMain(argc, argv);
}

3.clang的几种终端命令

//1、将 main.m 编译成 main.cpp
clang -rewrite-objc main.m -o main.cpp

//2、将 ViewController.m 编译成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m

//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

//4、真机文件编译
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

4.通过终端进入文件夹执行clang指令,这里用的第一行指令

clang -rewrite-objc main.m -o main.cpp

文件夹里多出来一个main.cpp文件


对象本质编译成cpp文件.jpeg

5.打开main.cpp文件,找到LGPerson相关的部分

#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_title;
struct LGPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS; //isa
    NSString *_name;
    NSString *_title;
};

// @property (nonatomic, copy) NSString *name;
// @property (nonatomic, copy) NSString *title;
/* @end */


// @implementation LGPerson
// getter方法
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

// setter方法 objc_setProperty()公共函数,统一接口,工厂设计模式,适配器原则,
// 所有setter方法必然走到 objc_setProperty
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) {
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _name), (id)name, 0, 1);
}

static NSString * _I_LGPerson_title(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_title)); }
static void _I_LGPerson_setTitle_(LGPerson * self, SEL _cmd, NSString *title) {
    objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _title), (id)title, 0, 1);
}
// @end



int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    }
    return NSApplicationMain(argc, argv);
}

6.对象的本质
可以看到LGPerson被编译成了结构体,两个属性被编译成了setter和getter方法,通过clang指令编译成cpp文件后,可以得出对象的本质就是结构体

7.LGPerson与NSObject的关系,我们都知道OC里所有类的根类是NSObject,看看编译后的情况,可以看到NSObject_IMPLLGPerson_IMPL第一个成员变量都是struct NSObject_IMPL结构体,也就是Class isa

// 源码里NSObject的定义
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
// NSObject底层编译
struct NSObject_IMPL {
    Class isa;
};
// LGPerson底层编译
struct LGPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS; //isa 等价于 Class isa // c++里结构体继承会带过来所有的成员变量
    NSString *_name;
    NSString *_title;
};

LGPerson以及属性编译情况,如下图


LGPerson自定义类编译成结构体以及属性的setter和getter方法.jpeg

8.编译后属性对应的函数objc_setProperty()具体实现如下,新值retain、旧值release、atomic、copy处理
objc_setProperty ---> reallySetProperty

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); // 设置isa
        return;
    }

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

    if (copy) { // copy特殊处理
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) { // mutableCopy特殊处理
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);  // objc_retain() ---> objc_retain(newValue) ---> 新值retain 
    }

    if (!atomic) { 
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();  // atomic原子性,加锁解锁特殊处理
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue); objc_release() ---> objc_release(oldValue) ---> 旧值release
}

objc_setProperty源码截图


objc_setProperty-->reallySetProperty.jpeg

属性的不同关键字是如何起作用的?
源码如下,可以看到多个函数都调用了这个reallySetProperty()函数,不同的属性关键字通过不同的参数传递进来进行不同的处理

源码里区分了四种情况
objc_setProperty_atomic
objc_setProperty_nonatomic
objc_setProperty_atomic_copy
objc_setProperty_nonatomic_copy

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)

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);
}

void objc_setProperty_atomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, false, false);
}

void objc_setProperty_nonatomic(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, false, false);
}


void objc_setProperty_atomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, true, true, false);
}

void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}

总结
通过对objc_setProperty的底层源码探索,有以下几点说明:

objc_setProperty方法的目的适用于关联 上层set方法 以及 底层set方法,其本质就是一个接口

这么设计的原因是,上层set方法有很多,如果直接调用底层set方法中,会产生很多的临时变量,当你想查找一个sel时,会非常麻烦

基于上述原因,苹果采用了适配器设计模式(即将底层接口适配为客户端需要的接口对外提供一个接口,供上层的set方法使用,对内调用底层的set方法,使其相互不受影响,即无论上层怎么变,下层都是不变的,或者下层的变化也无法影响上层,主要是达到上下层接口隔离的目的

下图是上层、隔离层、底层之间的关系


上层、隔离层、底层之间的关系.png

9.总结

OC对象的本质其实就是结构体
LGPerson中的isa是继承自NSObject中的isa

2.isa与类的关联原理

今天来探索alloc,三个核心方法initInstanceIsa()函数是如何将isa类信息关联起来的,即calloc()开辟的地址指针如何通过isa关联到对应的类

alloc三个核心方法,源码如下,下面来探讨这个initInstanceIsa()

static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
                              int construct_flags = OBJECT_CONSTRUCT_NONE,
                              bool cxxConstruct = true,
                              size_t *outAllocatedSize = nil)
{
    ASSERT(cls->isRealized());

    // Read class's info bits all at once for performance
    bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    bool fast = cls->canAllocNonpointer();
    size_t size;

    // 1.计算需要开辟的内存空间大小
    size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (zone) {
        obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        // 2.向系统申请开辟内存,并返回指针
        obj = (id)calloc(1, size);
    }
    if (slowpath(!obj)) {
        if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
            return _objc_callBadAllocHandler(cls);
        }
        return nil;
    }

    if (!zone && fast) {
        obj->initInstanceIsa(cls, hasCxxDtor);
    } else {
        // Use raw pointer isa on the assumption that they might be
        // doing something weird with the zone or RR.
        // 3.指针关联cls,设置isa指针
        obj->initIsa(cls);
    }

    if (fastpath(!hasCxxCtor)) {
        return obj;
    }

    construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
    return object_cxxConstructFromClass(obj, cls, construct_flags);
}

isa的类型isa_t

以下是源码里isa指针的类型isa_t的定义,从定义中可以看出是通过联合体(union)定义的

union isa_t { // 联合体
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits; //bits typedef unsigned long           uintptr_t;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h //位域 类似联合体位域里面的_direction
    };

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

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

ISA_BITFIELD宏定义的具体情况,源码(M1芯片下,objc4-818.2版本,如无特殊说iOS底层系列博客源码都是来自于此版本,iOS不同版本会做调整,但是基本思路原理是不变的)如下,可以出大的区分是arm64x86_64两种架构,arm64架构又细分出了两种情况

无论有哪些分支思路是一样的,比如uintptr_t nonpointer : 1; 类型、变量名、占用bit数量,从低位到高位依次类推

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     define ISA_MASK        0x007ffffffffffff8ULL
#     define ISA_MAGIC_MASK  0x0000000000000001ULL
#     define ISA_MAGIC_VALUE 0x0000000000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 0
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;                                       \
        uintptr_t has_assoc         : 1;                                       \
        uintptr_t weakly_referenced : 1;                                       \
        uintptr_t shiftcls_and_sig  : 52;                                      \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 8
#     define RC_ONE   (1ULL<<56)
#     define RC_HALF  (1ULL<<7)
#   else
#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     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 unused            : 1;                                       \
        uintptr_t has_sidetable_rc  : 1;                                       \
        uintptr_t extra_rc          : 19
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
#   endif

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_HAS_CXX_DTOR_BIT 1
#   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;                                         \
      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

// SUPPORT_PACKED_ISA
#endif

isa位域的含义


isa 位域宏定义的含义.jpeg

nonpointer有两个值,表示自定义的类等,占1
0纯isa指针
1:不只是类对象地址,isa中包含了类信息、对象的引用计数

has_assoc表示关联对象标志位,占1
0没有关联对象
1存在关联对象

has_cxx_dtor表示该对象是否有C++/OC的析构器(类似于dealloc),占1
如果析构函数,则需要做析构逻辑
如果没有,则可以更快的释放对象

shiftcls表示存储类的指针的值(类的地址), 即类信息,即arm64架构中cls转换为二进制33位的数据值
arm64中占 33位,开启指针优化的情况下,在arm64架构中有33位用来存储类指针
x86_64中占 44

magic 用于调试器判断当前对象是真的对象 还是 没有初始化的空间,占6

weakly_refrenced是 指对象是否被指向 或者 曾经指向一个ARC的弱变量
没有弱引用的对象可以更快释放

deallocating 标志对象是是否正在释放内存

has_sidetable_rc表示 当对象引用计数大于10时,则需要借用该变量存储进位

extra_rc(额外的引用计数) ,表示该对象的引用计数值,实际上是引用计数值减1

如果对象的引用计数为10,那么extra_rc为9(这个仅为举例说明),实际上iPhone 真机上的 extra_rc 是使用 19位来存储引用计数的

针对iOS和Mac两种不同的平台,其isa的存储情况如如图所示


isa存储情况.png

initInstanceIsa()源码流程
通过alloc --> _objc_rootAlloc --> callAlloc --> _objc_rootAllocWithZone --> _class_createInstanceFromZone方法路径,查找到initInstanceIsa() --> initIsa() --> setClass(),相关源码如下

首先在_class_createInstanceFromZone函数里打印cls转换为16进制值,看看有哪些特征,其实整个流程都可以打印cls 转换的16进制数值的

特征
iOS系统类都是占用4个字节4个bit,也就是8 * 4 + 4 = 36bits,并且最后的4bit不是8就是0

8: ---> 1000
0: ---> 0000

也就是36位二进制数,后3位都是0

那么如果我想存储这个cls转换过来的十六进制数值,先去掉后面的3个0,存储有效的33位即可,读取的时候后面再补上3个0就完全恢复了cls的真实数值,鉴于此来分析一下地址指针通过isa存储类信息,实现地址指针关联到具体的类的过程

iOS系统创建类的16进制转换数值特征.jpeg

initInstanceIsa()

核心逻辑
initIsa()函数调用

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    ASSERT(!cls->instancesRequireRawIsa());
    ASSERT(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

initIsa()

核心逻辑
isa_t newisa(0); 初始化isa
newisa.bits = ISA_MAGIC_VALUE; //bits赋初值 这里是 0x000001a000000001ULL
newisa.setClass(cls, this) 函数调用,赋值shiftcls保存类信息

inline void 
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{ 
    ASSERT(!isTaggedPointer()); 
    
    // 初始化isa,全部赋0,什么事情都没做
    isa_t newisa(0);

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


#if SUPPORT_INDEXED_ISA     // !nonpointer 执行流程,即is通过cls定义
        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                                     // bits执行流程 大部分都是nopointer isa
        newisa.bits = ISA_MAGIC_VALUE; //bits赋初值
        // 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); // 核心函数,赋值shiftcls
#endif
        newisa.extra_rc = 1;
    }

    // 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;
}

setClass()

核心逻辑
uintptr_t signedCls = (uintptr_t)newCls; cls转换为uintptr_t类型的数值
shiftcls = (uintptr_t)newCls >> 3; cls转换的数值右移3位,对shiftcls赋值,保存类信息,为什么是右移3位?上面已经分析了,这里shiftcls33位,要存储的是36位且后3位都是0,所以,存储有效的33位,读取的时候后面补齐3个0即可

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
// 符合条件下重新设置obj内存地址和newCls(cls)的关系,通过uintptr_t类型的数据表示
    // 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
// 符合条件下重新设置obj内存地址和newCls(cls)的关系,通过uintptr_t类型的数据表示
    // 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
    // cls转换为uintptr_t类型的数据值,右移3位,存储类的有效信息
    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
// cls转换为uintptr_t类型的数据值,右移3位,存储类的有效信息
    shiftcls = (uintptr_t)newCls >> 3;
#endif
}

总结
isa 保存类信息的核心逻辑

isa_t newisa(0); ---> 初始化isa
newisa.bits = ISA_MAGIC_VALUE; ---> bits赋初值 这里是 0x000001a000000001ULL
uintptr_t signedCls = (uintptr_t)newCls; ---> cls转换为uintptr_t类型的数值
shiftcls = (uintptr_t)newCls >> 3; --->cls转换的数值右移3位,对shiftcls赋值,保存类信息

由此可以看出cls转换uintptr_t数值类型的规则与shiftcls存储位数是一一对应的关系。右移3位并不是因为shiftcls本身从第3位开始的

2.下面通过打印调试来看看具体的isa存储值的过程
函数流程
_class_createInstanceFromZone ---> initInstanceIsa ---> initIsa --->setClass ---> _class_createInstanceFromZone

LLDB调试流程

主要流程
初始化newisa ---> newisa 赋初值(bits与cls赋初值,一致的) ---> setClass()函数 ---> shiftcls赋值(newCls16进制右移3位,存储有效的33位) ---> bits更新值cls = LGPerson关联类

验证方式
1.bits按位与 ISA_MASK,等于cls的16进制转换的数值
2.bits位移后,取出shiftcls的值,等于cls的16进制转换的数值
3.shiftcls 补齐后面30,等于cls的16进制转换的数值
4.LGPerson 内存段第一个8字节是 isa 按位与 ISA_MASK,等于cls的16进制转换的数值
5.LGPerson 内存段第一个8字节是 isa 位移后,取出shiftcls的值,等于cls的16进制转换的数值
6.LGPerson 内存段第一个8字节是 isa 等于bits的数值
7.cls = LGPerson

KCObjcBuild was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) p/x cls
(Class) $0 = 0x00000001000081c8 LGPerson //initIsa里打印cls的16进制数值,等于0x00000001000081c8
(lldb) p newisa // 初始化isa,对应源码isa_t newisa(0);,全部为0或者nil
(isa_t) $1 = {
  bits = 0
  cls = nil
   = {
    nonpointer = 0
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 0
    magic = 0
    weakly_referenced = 0
    unused = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
}
(lldb) p newisa  // newisa bits赋初始值 define ISA_MAGIC_VALUE 0x000001a000000001ULL
(isa_t) $2 = {
  bits = 1786706395137  // bits = cls   1786706395137 = 0x1a000000001
  cls = 0x000001a000000001
   = {
    nonpointer = 1 // nonpointer isa 
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 0
    magic = 26 // 0x1a = 26
    weakly_referenced = 0
    unused = 0
    has_sidetable_rc = 0
    extra_rc = 0
  }
}
(lldb) p/x newCls // 进入setClass函数
(Class) $3 = 0x00000001000081c8 LGPerson // LGPerson
(lldb) p 0x00000001000081c8 >> 3 // 打印newCls16进制,并且右移3位
(long) $4 = 536875065 // newCls 10进制数值为536875065,存储到shiftcls
(lldb) p newisa
(isa_t) $5 = {
  bits = 36975373484489
  cls = LGPerson
   = {
    nonpointer = 1
    has_assoc = 0
    has_cxx_dtor = 0
    shiftcls = 536875065 // 536875065,存储到shiftcls
    magic = 26
    weakly_referenced = 0
    unused = 0
    has_sidetable_rc = 0
    extra_rc = 1
  }
}
(lldb) p/x 36975373484489 // 验证开始
(long) $6 = 0x000021a1000081c9
(lldb) p/x 36975373484489 & 0x0000000ffffffff8ULL // bits按位与上 0x0000000ffffffff8ULL --> ISA_MASK  
(unsigned long long) $7 = 0x00000001000081c8 // 结果与0x00000001000081c8 LGPerson一致
(lldb) p/x cls // 返回 initIsa函数 打印 cls 
(Class) $8 = 0x00000001000081c8 LGPerson
(lldb) p/x 536875065 // 16进制打印shiftcls
(int) $9 = 0x20001039
(lldb) p/t 0x20001039
(int) $10 = 0b00100000000000000001000000111001 2进制打印shiftcls
(lldb) p/x 0b0000000000000000000000000000000100000000000000001000000111001000 // 补齐后3个0和64位,16进制 打印补齐后的shiftcls,结果与0x00000001000081c8 LGPerson一致
(long) $11 = 0x00000001000081c8 
(lldb) x/4gx obj // 返回到_class_createInstanceFromZone函数,打印4段内存数据,0x000021a1000081c9 是isa
0x10160ab80: 0x000021a1000081c9 0x0000000000000000
0x10160ab90: 0x0000000000000000 0x0000000000000000
(lldb) p 0x000021a1000081c9 
(long) $13 = 36975373484489  // 10进制打印isa,与  bits = 36975373484489一致
(lldb) p/x 0x000021a1000081c9 & 0x0000000ffffffff8ULL 
(unsigned long long) $14 = 0x00000001000081c8 // isa与按位与上 0x0000000ffffffff8ULL与0x00000001000081c8 LGPerson一致
(lldb) p/x 0b00100000000000000001000000111001000
(long) $15 = 0x00000001000081c8  // shiftcls直接后面补3个零,16进制打印cls与0x00000001000081c8 LGPerson一致
(lldb) p/x 0x000021a1000081c9 >> 3 // 右移3位抹掉,后3位
(long) $16 = 0x0000043420001039
(lldb) p/x 0x0000043420001039 << 31 3 // 左侧移31位抹掉,28+3,抹掉前28
(long) $17 = 0x1000081c80000000
(lldb) p/x 0x1000081c80000000 >> 28 // 右移28位复原
(long) $18 = 0x00000001000081c8 // 位移后与16进制打印cls与0x00000001000081c8 LGPerson一致
(lldb) 

Xcode 调试流程截图说明,与上面流程完全一致


isa关联类信息1.jpeg
isa关联类信息2.jpeg

3.通过 object_getClass
通过查看object_getClass 源码实现,同样可以验证isa与类关联的原理
导入#import <objc/runtime.h>

源码如下

核心逻辑
联合体isa_t里面的bits,按位与ISA_MASK
clsbits &= ISA_MASK;,按位与ISA_MASK
return (Class)clsbits;,强制转换为Class

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
inline Class
objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA();

    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;
}
inline Class
objc_object::ISA(bool authenticated)
{
    ASSERT(!isTaggedPointer());
    return isa.getDecodedClass(authenticated);
}
inline Class
isa_t::getDecodedClass(bool authenticated) {
#if SUPPORT_INDEXED_ISA
    if (nonpointer) {
        return classForIndex(indexcls);
    }
    return (Class)cls;
#else
    return getClass(authenticated);
#endif
}

inline Class
isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
#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;  // 按位与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));  // clsbits 获取clsbits
    } else {
        // If not authenticating, strip using the precomputed class mask.
        clsbits &= objc_debug_isa_class_mask;  // clsbits 获取clsbits
    }
#       else
    // If not authenticating, strip using the precomputed class mask.
    clsbits &= objc_debug_isa_class_mask;  // clsbits 获取clsbits
#       endif

#   else
    clsbits &= ISA_MASK;  // 按位与ISA_MASK
#   endif

    return (Class)clsbits;  // clsbits 墙砖为Class
#endif
}

Xcode截图object_getClass 核心源码分析


object_getClass核心源码分析.jpeg

1.总结

1.对象的本质是结构体
2.对象指针所指向的第一个8字节isa指针,isa指针是64位,其中的shiftcls存储类信息的16进制转换数值,这里是占用33位,具体架构下见对应的宏定义
4.isa_t结构体里面的cls 存储类名
5.shiftcls = (uintptr_t)newCls >> 3;
右移3位不是因为存储位置是从低位3位开始的,而是(uintptr_t)newCls有效位是33位,后3位是0,由此可以看出cls转换uintptr_t数值类型的规则与shiftcls存储位数是一一对应的关系。右移3位并不是因为shiftcls本身从第3位开始的
6.联合体isa_t里面的bits,按位与ISA_MASK,不是联合体本身

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容