# 03.对象本质探究与isa

## 一、结构分析

我们知道，OC底层是C++，我们先将下面的代码还原成C++代码再进行下一步。

新建一个空项目在`main`函数中写入测试代码。

```cpp
@interface RKObject : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

@end

@implementation RKObject

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}
```

### 1.1 还原成C++

执行`clang -rewrite-objc main.m -o main.cpp`，就能得到C++文件。

或者：

```
// 模拟器
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
// 真机
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mainarm64.cpp
```

我们找到和我们定义的对象相关的地方：

### 1.2 一些关键内容

#### a. 定义

这里是一些类型定义

```cpp
#ifndef _REWRITER_typedef_RKObject
#define _REWRITER_typedef_RKObject
typedef struct objc_object RKObject;
typedef struct {} _objc_exc_RKObject;
#endif
```

#### b. 结构体

这里我们可以看到，是一个结构体。

而我们知道，结构体是不能继承的。这里通过一个`NSObject_IVARS`，实现了OC的`继承`。

同样也可以看到我们定义的属性相对应的成员变量。

```cpp
extern "C" unsigned long OBJC_IVAR_$_RKObject$_name;
extern "C" unsigned long OBJC_IVAR_$_RKObject$_age;
struct RKObject_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    NSString *_name;
};


// @property (nonatomic, copy) NSString *name;
// @property (nonatomic, assign) int age;

/* @end */
```

#### c. 函数

这里是成员变量相关的`set/get`方法：

```cpp
// @implementation RKObject


static NSString * _I_RKObject_name(RKObject * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_RKObject$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

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

static int _I_RKObject_age(RKObject * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_RKObject$_age)); }
static void _I_RKObject_setAge_(RKObject * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_RKObject$_age)) = age; }
// @end
```

**静态的实例方法**

* 我们发现它是`static`的，他不是实例方法么？
* 我们再看，它包含了两个参数，这两个参数在我们使用的时候是隐含的：
  * 对象
  * Selector
* 这样我们知道，实例方法本身也是静态的，只不过有两个隐藏的参数罢了。

**get方法如何获取成员变量的**

* 在`name`的get方法的实现中看到
  * `return (*(NSString **)((char *)self + OBJC_IVAR_$_RKObject$_name));`
* 实例首地址 + 成员变量的offset = 成员变量的指针

#### d. isa

在上面我们知道，`struct NSObject_IMPL NSObject_IVARS`，是OC继承关系的核心。

那么`NSObject_IMPL`是什么呢？

我们很快就能在文件中找到：

```cpp
struct NSObject_IMPL {
    Class isa;
};
```

它就是`isa`！

#### e. Class

在文件中我们找到，`Class`的定义。本质是一个`objc_class`结构体指针的别名。

```cpp
typedef struct objc_class *Class;

struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};
```

#### f. id类型

再往下我们还能发现一个，它也是一个`objc_object *`，结构体指针。

这也解答了，为什么我们平时使用`id`类型的时候没有`*`。

```cpp
typedef struct objc_object *id;
```

## 二、isa

在[alloc与字节对齐](/wiki/ios/di-ceng/01.alloc-yu-zi-jie-dui-qi.md)一文中我们有聊到`initInstanceIsa`，将isa和class进行绑定。

我们从这里开始继续研究isa

### 2.1 源码

```cpp
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
        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;
}
```

### 2.2 isa的本质 isa\_t

从源码中我们看到isa的类型为`isa_t`，我们找到`isa_t`的源码：

```cpp
union isa_t {
    // 构造方法
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

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

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

破案了，他是个联合体！

### 2.3 ISA\_BITFIELD isa的位域

`arm64`下的`ISA_BITFIELD`:

```cpp
#     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
```

* nonpointer
  * 是否是纯isa指针
    * 0：纯isa指针，只包含类对象地址
    * 1：还包含了类的信息、对象的引用计数
* has\_assoc
  * 是否存在关联对象的标志位
* has\_cxx\_dtor
  * 该对象是否有C++或者Objc的析构器
    * 如果有析构函数，则需要做析构逻辑
    * 如果没有，就可以快速的释放对象
* shiftcls
  * 类指针的值
    * 开启指针优化的情况下，arm64中用33位来存
* magic
  * 用于判断当前对象是已经初始化的对象
  * 还是没有初始化的空间
* weakly\_referenced
  * 是否被弱引用
  * 没有弱引用的对象可以更快的释放
* unused

  \*&#x20;
* has\_sidetable\_rc
  * 当引用计数大于10时，需要借用该变量存储进位
* extra\_rc
  * 引用计数
  * 实际的值是`引用计数-1`，即：
    * 引用计数为10，`extra_rc = 9`

### 2.4 获取class对象: getClass

```cpp
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;
        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
}
```

分析源码实现，我们知道了。是通过isa的`bits`再与上一个掩码`ISA_MASK`来获取到类对象的地址的。

* `ARM64`: `define ISA_MASK  0x0000000ffffffff8ULL`
* `__x86_64__`: `define ISA_MASK        0x00007ffffffffff8ULL`

#### a.验证

用我们上面的demo进行验证，注意是Mac环境，不是arm64。

![1](/files/-Mc8jzksDoU8lf1xfrJg)

```cpp
(lldb) p/x RKObject.class
(Class) $0 = 0x00000001000081f8 RKObject
(lldb) x/4gx obj
0x1007576a0: 0x011d8001000081fd 0x0000000000000000
0x1007576b0: 0x0000000000000000 0x0000000000000000
(lldb) p/x 0x011d8001000081fd & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 0x00000001000081f8
```

这里我们发现计算的结果和直接获取的结果一致。

#### b. 不使用掩码，通过位运算计算出Class

以Mac下为例，isa指针的结构是一定的。在一个isa指针的空间内，class的位置是相对固定的。

```cpp
#   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
```

结构如下： B + Class + A（小端模式，从右向左）

* A：1+1+1 = 3
* Class：44
* B：6+1+1+1+8 = 17

```cpp
(lldb) x/4gx obj
0x1007576a0: 0x011d8001000081fd 0x0000000000000000
0x1007576b0: 0x0000000000000000 0x0000000000000000

(lldb) p/t 0x011d8001000081fd
(long) $11 = 0b00000001000111011 00000000000000100000000000000001000000111111 101
```

计算：

* 左移3位，使A从右边溢出，左边多的位会补0
  * 现在的结构：`000 + B + Class`

```cpp
(lldb) p/x 0x011d8001000081fd >> 3
(long) $7 = 0x0023b0002000103f

(lldb) p/t 0x0023b0002000103f
(long) $12 = 0b000 00000001000111011 00000000000000100000000000000001000000111111
```

* 现在需要去掉B，右移`3+17=20`位
  * 现在的结构：`Class + 20个0`

```cpp
(lldb) p/x 0x0023b0002000103f << 20
(long) $8 = 0x0002000103f00000

(lldb) p/t 0x0002000103f00000
(long) $13 = 0b00000000000000100000000000000001000000111111 00000000000000000000
```

* 回到原位，右移17位：
  * 现在的结构：17个0 + Class + 3个0

```cpp
(lldb) p/x 0x0002000103f00000 >> 17
(long) $9 = 0x00000001000081f8

(lldb) p/t 0x00000001000081f8
(long) $14 = 0b00000000000000000 00000000000000100000000000000001000000111111 000
```

## 三、isa和类对象的绑定

> 这里是`objc_object::initIsa`

```cpp
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) { // 如果是纯isa就直接setClass
        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
        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;
}
```

### 3.1 class绑定流程图

![1](/files/-Mc8jzkuP8zYPcySN7yV)

### 3.2 中间有个判断 Nonpointer isa

下面我们具体研究下

## 四、Nonpointer isa

在上面我们有多次看到`Nonpointer isa`出现。现在我们深入的研究一下

### 4.1 isa\_t

在上面我们知道`isa`是一个联合体，而联合体成员是互斥的。

这个就是`Nonpointer isa`区别于纯`isa`的核心！

### 4.2 isa\_t::setClass

这里是设置Class的核心实现

```cpp
// 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
}
```

### 4.3 Nonpointer isa 和 纯isa的区别

* `Nonpointer isa`
  * 类对象会被赋值到`isa_t`的`BITFIELD`中的`shiftcls`
* 纯`isa`
  * 会被直接赋值到`isa_t`的`cls`
* 两者互斥
* 默认创建的对象都是`Nonpointer isa`
  * 可通过修改Xcode环境变量改成使用纯isa


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/ios/di-ceng/03.-dui-xiang-ben-zhi-tan-jiu-yu-isa.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
