I’m participating in nuggets Creators Camp # 4, click here to learn more and learn together!
In iOS development, BOTH OC and Swift use ARC for memory management, and in this article we’ll explore reference counting in Swift.
Three reference counts
In the Swift object article, we explored the nature of a Swift object, which has the following data structure: InlineRefCounts counts references to the object.
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *metadata; // 1
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS; // 2
}
Copy the code
Here’s how Apple’s reference counting rule works:
In general, a Swift object has three kinds of reference counts, stored either next to the object ISA or on the side table entry that ISA points to.
-
1, stong RC: strong reference count, calculates the count of strong references. When strong RC equals 0, the object is deinit, accessed with an unreferenced reference, and an error occurs. Accessing the object using a weak reference returns nil.
-
2, unowned RC: no primary reference count, calculate no primary reference count, no primary reference count plus 1 to represent strong reference count, after the object deinit is complete, will automatically -1. When the unreferenced reference count equals zero, the object’s memory space has been freed.
-
Weak RC: Weak reference count counts weak references. The weak reference count will add +1, indicating the value of the unreferenced reference count. When the memory space of the object is freed, the weak reference count is automatically -1, and when the weak reference count value is 0, the side table entry of the object is freed.
When an object is initialized, it does not have a side table.
- 1, there’s one
A weak reference
Pointing to it. - 2,
Strong reference counting or unheaded reference counting
In the numericalThe overflow
Is less than 32 bits. - 3. The object has
associations
.
Getting the side table entry is a one-way operation, which ensures that it is not lost and prevents some thread contention issues.
Its memory structure is as follows
HeapObject {
isa
InlineRefCounts {
atomic<InlineRefCountBits> {
strong RC + unowned RC + flags
OR
HeapObjectSideTableEntry*
}
}
}
HeapObjectSideTableEntry {
SideTableRefCounts {
object pointer
atomic<SideTableRefCountBits> {
strong RC + unowned RC + weak RC + flags
}
}
}
Copy the code
No side table entry is used, called linerefCount. Those that use side Table Entry to store reference counts are called SideTableRefCounts
This is an excerpt from the Swift source code comment.
Next, let’s explore Swift’s reference count management using source code.
Source code analysis
Strong reference counting and undirected reference counting
When the Swift object is initialized, the referential count is initialized in the refCounts function
LLVM_ATTRIBUTE_ALWAYS_INLINE constexpr RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount) : bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) | (BitsType(1) << Offsets::PureSwiftDeallocShift) | (BitsType(unownedCount) << Offsets::UnownedRefCountShift)) { }Copy the code
Pass in two arguments, strong reference count (0) and unreferenced reference (1), and store the value in the corresponding space by bit operation.
The corresponding offset value is shown below
According to the memory offset, we can analyze the storage rule of reference count as shown in the figure
- 1, the first
0
Bit to indicate whether or not the object is pure Swift. - 2, the first
1 to 31
And storageNo master reference count
. - 3, the first
32
Bit: indicates whether or notdeiniting
State. - 4, the first
33~62
Bit to store strong reference counts. - 5, the first
63
Bit, flags whether a hash table exists, and if soA weak reference
The bit value is 1.
Next, we use LLDB to dynamically view its memory distribution.
LLDB debugging
There is the following code
import Foundation
class Person {
var age = 10
}
var p: Person? = Person(a)/ / 1
var p1 = p
/ / 2
var p2 = p
/ / 3
unowned var p3 = p
/ / 4
print(p1?.age)
Copy the code
At breakpoint 1, we print the memory address of the p variable, and then look at the memory distribution
(lldb)p Unmanaged.passUnretained(p!)
(Unmanaged<LYCSwift.Person>) $R0 = {
_value = 0x0000000101f1d180 (age = 10)
}
(lldb)x/8gx 0x0000000101f1d180
0x101f1d180: 0x00000001000081f8 0x0000000000000003
0x101f1d190: 0x000000000000000a 0x0000000000000023
0x101f1d1a0: 0x0000000000000000 0x0000000000000000
0x101f1d1b0: 0x0000000000000002 0x00020000101f1f3e
Copy the code
At this point the refCounts value is 0x0000000000000003, which is converted to binary by the calculator
The first two bits are 1’s, and the other bits are 0’s, which means this timeIs a pure Swift object with a strong reference count of 0 and an undirected reference count of 1
Yi? And so on…
The extra_rc value is 1 and the strong reference count is 1. The extra_rc value is 0.
If you look at apple’s comments, it’s already mentioned that an empty reference has an extra +1 for a strong reference count, so the stored strong reference count is 0.
Let’s move on: at break point 3
(lldb)x/8gx 0x0000000101f1d180
0x101f1d180: 0x00000001000081f8 0x0000000400000003
0x101f1d190: 0x000000000000000a 0x0000000000000023
0x101f1d1a0: 0x0000000000000000 0x0000000000000000
0x101f1d1b0: 0x0000000000000002 0x00020000101f1f3e
Copy the code
After two strong references P1 and P2, the value of the strong reference count is 0x10 or 2, and that of the unreferenced count is 0x1 or 1.
We’re at break point four
(lldb)x/8gx 0x0000000101f1d180
0x101f1d180: 0x00000001000081f8 0x0000000400000005
0x101f1d190: 0x000000000000000a 0x0000000000000023
0x101f1d1a0: 0x0000000000000000 0x0000000000000000
0x101f1d1b0: 0x0000000000000002 0x00020000101f1f3e
Copy the code
afterunown
Modifier variable after reference will makeUnreferenced count value +1
At this time,No master reference count
for2
, the strong reference count is2
.
Weak reference counting
When weak var p4 = p is used, a WeakReference object will be generated, and the reference count type of the object will change from InlineRefCounts to SideTableRefCounts. In this case, the structure of HeapObject is
HeapObject {
isa
InlineRefCounts {
atomic<InlineRefCountBits> {
HeapObjectSideTableEntry*}}}class HeapObjectSideTableEntry {
std::atomic<HeapObject* > object;
SideTableRefCounts refCounts;
}
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
uint32_t weakBits;
}
Copy the code
When we look at the assembly code at runtime, we see that swift_weakInit is implemented to handle weak references
Its internal implementation is as follows
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
Copy the code
Main implementation code
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME); / / 1
// Preflight failures before allocating a new side table.
if (oldbits.hasSideTable()) {
// Already have a side table. Return it.
return oldbits.getSideTable(); / / 2
}else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
// Preflight passed. Allocate a side table.
// FIXME: custom side table allocator**
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
auto newbits = InlineRefCountBits(side);
do {
if (oldbits.hasSideTable()) {
// Already have a side table. Return it and delete ours.
// Read before delete to streamline barriers.
auto result = oldbits.getSideTable();
delete side;
return result;
failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
side->initRefCounts(oldbits); / / 3
} while (! refCounts.compare_exchange_weak(oldbits, newbits,std::memory_order_release,
std::memory_order_relaxed));
return side;
}
// Get Side Table
LLVM_ATTRIBUTE_ALWAYS_INLINE
HeapObjectSideTableEntry *getSideTable() const {
assert(hasSideTable());
// Stored value is a shifted pointer.
return reinterpret_cast<HeapObjectSideTableEntry * >
(uintptr_t(getField(SideTable)) << Offsets::SideTableUnusedLowBits);
}
Copy the code
- 1, get
InlineRefcounts oldbit
Through theSign a
Check whether it already existsHeapObjectSideTableEntry
. - 2, if there is
SideTable
Through theHeapObjectSideTableEntry *
Address pointer,Shift to the left
, can be obtainedHeapObjectSideTableEntry
Object value. - 3,
InlineRefCounts
In theStrong reference counting
andNo master reference count
Store it in a hash table.
After getting the HeapObjectSideTableEntry, go to ide->incrementWeak(); +1 for 32-bit weak reference counts. The following figure shows the relationship between HeapObjectSideTableEntry and HeapObject
LLDB debugging
Let’s look at the reference count change in the following code
// Strong reference count weak reference var s: Person? = Person() // 0 1 var s1 = s // 1 1 var s2 = s1 // 2 1 print(2) weak var s3 = s2 // 2 1 2 print(4) weak var s4 = s3 // 2 1 3 print(s4? .age)Copy the code
// The following values represent: strong reference count No main reference count and weak reference count
At print(2), we look at the reference count
(lldb)p Unmanaged.passUnretained(s!)
(Unmanaged<LYCSwift.Person>) $R0 = {
_value = 0x000000010384e8f0 (age = 10)
}
(lldb)x/8gx 0x000000010384e8f0
0x10384e8f0: 0x0000000100008200 0x0000000400000003
0x10384e900: 0x000000000000000a 0x0000000000000023
0x10384e910: 0x0000000080080000 0x0000000100a4f3a8
0x10384e920: 0x0000000000000000 0x0000000100a3dce8
Copy the code
At this point,The strong reference count is 2 and the undirected reference count is 1
.
In print (4)
(lldb) x/8gx 0x000000010384e8f0
0x10384e8f0: 0x0000000100008200 0xc000000020748016
0x10384e900: 0x000000000000000a 0x0000000000000023
0x10384e910: 0x0000000080080000 0x0000000100a4f3a8
0x10384e920: 0x0000000000000000 0x0000000100a3dce8
Copy the code
0xC000000020748016 is the pointer address of HeapObjectSideTableEntry. Move it 3 bits to the left to get the address 0x103A400B0 and get the value of HeapObjectSideTableEntry, which we read
(lldb)x/8gx 0x103A400B0
0x103a400b0: 0x000000010384e8f0 0x0000000000000000
0x103a400c0: 0x0000000400000003 0x0000000000000002
0x103a400d0: 0x0000000000000000 0x0000000000000000
0x103a400e0: 0x00007fff84990008 0x00007fff84995200
Copy the code
0x000000010384E8F0 is the memory pointer to HeapObject, 0x0000000400000003 stores strong reference count and weak reference count. 0x0000000000000002 The first 32 bits store the weak reference count. At this point, the strong reference, unreferenced reference and weak reference count values are 2, 1, 2 respectively. The weak reference count will have an extra +1, representing the unreferenced reference count value.
⚠️⚠️⚠️ Note: see the Apple comment at the beginning of this article for an additional +1.
In print (s4? The age)
(lldb) x/8gx 0x103A400B0
0x103a400b0: 0x000000010384e8f0 0x0000000000000000
0x103a400c0: 0x0000000400000003 0x0000000000000003
0x103a400d0: 0x0000000100a4f2e8 0x0000000100000003
0x103a400e0: 0x08d0eef4ea1dadab 0x08d0eef4ea1dadab
Copy the code
At this point, strong reference, no reference and weak reference count values are 2, 1 and 3 respectively
conclusion
Swift has three types of reference counts, strong reference count, undirected reference count, and weak reference count. The strong reference count is 0 when the object is initialized, the unclaimed reference count is 1, and the weak reference count is 2 when the weak modifier is first used, for reasons explained at the beginning of this article.
If you feel that there is a harvest please follow the way to a love three even: 👍 : a praise to encourage. 🌟 : Collect articles, easy to look back! . 💬 : Comment exchange, mutual progress! .