#NONPOINTER_ISA Is similar to TaggetPointer in that ISA is not just a pointer. Some of these bits still encode classes that point to objects. But instead of actually using all of the address space, the Objective-C runtime uses these extra bits to store each object’s data like its reference count and whether it’s been weakly referenced. From the source:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
Copy the code

Isa isa federation of definitions. #### What is a consortium?

When multiple data need to share memory or multiple data can be taken only for a moment at a time, you can use union to store data types of different types with the same storage space, thus saving memory space.

Let’s write an example of a union

#define DirectionLeftMask (1 << 0) // 0000 0001
#define DirectionRightMask (1 << 0) // 0000 0010

@interface Car() {// union {char bits; }_direction; } @end @implementation Car - (instancetype)init { self = [super init];if(self) { _direction.bits = 0b00000000; // Initialize binary}return self;
}

- (void)moveLeft {
    _direction.bits |= DirectionLeftMask;  // 0000 0000 | 0000 0001 = 0000 0001
}

- (void)moveRight {
    _direction.bits |= DirectionRightMask; // 0000 0000 | 0000 0010 = 0000 0010
}
Copy the code

So we’re using the same piece of address to represent both values. This is a bit of a hassle, so the concept of bit-fields is introduced. After introducing bitfields, the following code is used:

@interface Car() {// union {char bits; Struct {// char left: 1; Char right: 1; char front : 1; char back : 1; }; }_direction; } @end @implementation Car - (instancetype)init { self = [super init];if(self) { _direction.bits = 0b00000000; // Initialize binary}return self;
}

- (void)moveLeft {
    _direction.left = 1;
}

- (void)moveRight {
    _direction.right = 1;
}

- (void)moveFront {
    _direction.front = 1;
}

- (void)moveBack {
    _direction.back = 1;
}
Copy the code

Next, let’s look at isa’s definition, which defines a bit field: ISA_BITFIELD. Click on this macro to view it:

# if __arm64__
# 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
# elif __x86_64__
# 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

Copy the code

__x86_64__ (1+1+1+33+6+1+1+1+1 +19 = 64, __x86_64__ (1+1+1+1 +19 = 64)); Indicates whether pointer optimization is enabled for ISA. 0 indicates a pure ISA pointer, and 1 indicates that in addition to the address, it contains some information about the class, the reference count of the object, and so on. Has_cxx_dtor: does the object have a destructor for C++ or Objc? If it does, it needs to do some destructor logic. If not, it can release the object shiftcls faster: Weakly_referenced: If the current object is a real object or a segment of uninitialized space. Weakly_referenced: if the current object is a real object or a segment of uninitialized space. Deallocating: Is freeing has_SIDETable_RC: When the object reference count is greater than 10, it needs to carry extra_rc: Represents the reference count value of the object, which is actually the reference count minus one. For example: if the reference count is 10, extra_rc is 9. If the reference count is greater than 10, has_sideTABLE_rc is used

In the Runtime memory space, SideTables is a hash array containing the SideTable. The hash key of SideTables is the address of an object obj. So we can say that an OBj corresponds to a SideTable. But one SideTable is going to have multiple OBJs. Because there are a limited number of sidetables, many OBJs share the same SideTable.

Let’s look at the structure of the SideTable

struct SideTable { spinlock_t slock; // spinlock RefcountMap refcnts; Weak_table_t Weak_table; // Reference count Map table key-value weak_table_t weak_table; // Weak reference tableCopy the code

To raise the question, why not just use one SideTable, but use SideTables to manage multiple SideTables? In SideTable, there is a spin lock. If all classes are placed in the same SideTable, any change to the table will be performed on the whole table. In addition, when operating on one class, other classes will be locked and wait, resulting in low operation efficiency and query efficiency. If there are multiple sidetables, all operations on a single Table will not affect other tables.

Continuing with SideTables, let’s look at the hash Table data structure (array + linked list). For example, we need to put items less than 100 into the first Table and items greater than 900 into the sixth Table:

How do you find a particular sideTable from sideTables? That’s where you use the hash function. Runtime uses a function to retrieve the corresponding sideTable:

table = &SideTables()[obj]; Static StripedMap<SideTable>&SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
Copy the code

If you don’t understand, let’s look at the StripedMap definition:

template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && ! TARGET_OS_SIMULATORenum { StripeCount = 8 }; // On iPhone, the value is 8#elseenum { StripeCount = 64 }; // Otherwise 64#endifstruct PaddedT { T value alignas(CacheLineSize); }; PaddedT array[StripeCount]; Uintptr_t addr = reinterpret_cast<uintptr_t>(p); // This is the hash algorithmreturn((addr >> 4) ^ (addr >> 9)) % StripeCount; } public: T& operator[] (const void *p) {// Return sideTablereturn array[indexForPointer(p)].value; 
    }
Copy the code

As you can see, after mod StripeCount, the value will be between 0 and 7 or 0 and 63, depending on the machine. This is to get the subscript of the sideTable by hash function, and then get the required sideTable based on value.

To verify this, compile objc source code and set a breakpoint to see what is called:

Execute table = &SideTables()[obj]; Array [indexForPointer(p)].value; Hash algorithm to get the subscript, and return the desired sideTable