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 oneA weak referencePointing to it.
  • 2,Strong reference counting or unheaded reference countingIn the numericalThe overflowIs less than 32 bits.
  • 3. The object hasassociations.

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 first0Bit to indicate whether or not the object is pure Swift.
  • 2, the first1 to 31And storageNo master reference count.
  • 3, the first32Bit: indicates whether or notdeinitingState.
  • 4, the first33~62Bit to store strong reference counts.
  • 5, the first63Bit, flags whether a hash table exists, and if soA weak referenceThe 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

afterunownModifier variable after reference will makeUnreferenced count value +1At this time,No master reference countfor2, 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, getInlineRefcounts oldbitThrough theSign aCheck whether it already existsHeapObjectSideTableEntry.
  • 2, if there isSideTableThrough theHeapObjectSideTableEntry *Address pointer,Shift to the left, can be obtainedHeapObjectSideTableEntryObject value.
  • 3,InlineRefCountsIn theStrong reference countingandNo master reference countStore 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! .