“This is the 23rd day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Memory management

Swift language continues the same idea of memory management as Objective-C language, which uses reference counting to manage the memory space of instances.

Automatic reference counting

Automatic Reference Counting, also known as ARC, is a means to solve memory management problems in Objective-C and Swift languages. During development, improper reference to class instances will cause memory leaks, which will bring disastrous consequences to applications when accumulated to a certain extent.

Reference counting operations in Swift

We create a Person class like this:

class Person {
    var age: Int = 20
    var name: String = "zhangSan"
}
Copy the code

Next, let’s print and parse the memory pointer to the instance of Person:

As we already know from our previous analysis, the second 8-byte store is refCounts, which is 0x3; In the heapObject. h file, we can find the definition of refCounts in HeapObject:

RefCounts is a template class that accepts the InlineRefCountBits generic argument:

typedef RefCounts<InlineRefCountBits> InlineRefCounts;
Copy the code

RefCounts essentially operate on the generic parameter we passed, RefCountsBits, which is a wrapper around the reference count, depending on the parameter type InlineRefCountBits;

Let’s continue with the definition of InlineRefCountBits:

typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
Copy the code

This is also a template function, and RefCountIsInline is true:

enum RefCountInlinedness { RefCountNotInline = false.RefCountIsInline = true };
Copy the code

So, we know that the class for the final reference-counting operation is actually RefCountBitsT:

When we analyze RefCountBitsT, we find that there is only one bits property, which is defined by the Type property of RefCountBitsInt; According to the definition of Type, we find that Type is actually a uint64_t Type, which is a 64-bit bit field information:

typedef uint64_t Type;
Copy the code

So far, we can draw a conclusion:

Reference counting is essentially a 64-bit bit field information in which reference counting information is stored. The reason for doing this is to abstract it out and improve code reuse;

So, the question is, when we create a new instance object, what is its reference count? Initialize HeapObject ();

From the HeapObject initialization implementation, we find the refCounts assignment:

Initialized is an enumerated type Initialized_t: Initialized_t: Initialized_t: Initialized_t: Initialized_t

RefCountBits(0,1); RefCountBits(RefCountBitsT); RefCountBitsT (RefCountBitsT); RefCountBitsT (RefCountBitsT);

StrongExtraCount is 0 and unownedCount is 1. StrongExtraCount (strong reference count) and unownedCount(no primary reference count) are stored in 64 bits by displacement. In the source code we can analyze as follows:

# define shiftAfterField(name) (name##Shift + name##BitCount)
static const size_t PureSwiftDeallocShift = 0;
static const size_t PureSwiftDeallocBitCount = 1;
static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
static const size_t PureSwiftDeallocShift = 0;
static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
static const size_t IsDeinitingShift = shiftAfterField(UnownedRefCount);
static const size_t IsDeinitingBitCount = 1;
static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
static const size_t UnownedRefCountBitCount = 31;

// Result analysis
StrongExtraRefCountShift = shiftAfterField(IsDeiniting)
						 = IsDeinitingShift + IsDeinitingBitCount
						 = shiftAfterField(UnownedRefCount) + 1
						 = UnownedRefCountShift + UnownedRefCountBitCount + 1
						 = shiftAfterField(PureSwiftDealloc) + 31 + 1
						 = PureSwiftDeallocShift + PureSwiftDeallocBitCount + 31 + 1
						 = 0 + 1 + 31 + 1 = 33
Copy the code

Through the above calculation, we can analyze as follows:

bits( (0 << 33) | (1 << 0) | (1 << 1))

bits( 0 | 1 | 2)

bit(3)

Matches our final print of 0x3;

64-bit stored information is as follows:

Bit 0: identifies whether it is permanent bit 1-31: stores no primary references Bit 32: identifies whether the current class is being destructed Bit 33-62: identifies strong references bit 63: identifies whether SlowRC is used