Swift 内存管理简介5

309 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情

接下来我们来看一下当我们做一个强引用的时候它的引用计数是怎么变化的哪?

class ZGTeacher {
    var age: Int = 18
    var name: String = "Zhang"
}

var t = ZGTeacher()
///固定写法,打印这个t实例的内存指针
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())

var t1 = t
var t2 = t
print("end")

image.png

我们逐步放开断点,并用x/8g指令打印输出,看到它的强引用计数变为了2。通过位移运算左移**33位,高34位,每次加2**。
我们来看一下位域布局图\

  • 1 ~31位存储的是无主引用
  • 32位存储的是当前的类是否正在析构
  • 33 ~ 62位存储的是强引用
    我们简单用代码来验证一下第32位信息
class ZGTeacher {
    var age: Int = 18
    var name: String = "Zhang"
}

var t: ZGTeacher? = ZGTeacher()
///固定写法,打印这个t实例的内存指针
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())

t = nil

print("end")

image.png

32位为1,表示正在析构。
那么引用计数是如何操作强引用的哪,我们也可以到源码里看一下。

SWIFT_ALWAYS_INLINE
 static HeapObject *_swift_retain_(HeapObject *object) {
   SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
   if (isValidPointerForNativeRetain(object))
     object->refCounts.increment(1);
   return object;
 }
SWIFT_ALWAYS_INLINE
   void increment(uint32_t inc = 1) {
     auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
     
     // constant propagation will remove this in swift_retain, it should only
     // be present in swift_retain_n
     if (inc != 1 && oldbits.isImmortal(true)) {
       return;
     }
     
     RefCountBits newbits;
     do {
       newbits = oldbits;
       bool fast = newbits.incrementStrongExtraRefCount(inc);
       if (SWIFT_UNLIKELY(!fast)) {
         if (oldbits.isImmortal(false))
           return;
         return incrementSlow(oldbits, inc);
       }
     } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                               std::memory_order_relaxed));
   }
SWIFT_NODISCARD SWIFT_ALWAYS_INLINE bool
   incrementStrongExtraRefCount(uint32_t inc) {
     // This deliberately overflows into the UseSlowRC field.
     bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
     return (SignedBitsType(bits) >= 0);
   }

这里也是通过位域运算得来的。使用强引用就会造成一个问题:循环引用。我们来看一下以下案例:

class ZGTeacher {
    var age: Int = 18
    var name: String = "zhang"
    var subject: ZGTeacher?
}

class ZGSubject {
    var subjectName: String
    var subjectTeacher: ZGTeacher
    init(_ subjectName: String, _ subjectTeacher: ZGTeacher) {
        self.subjectName = subjectName
        self.subjectTeacher = subjectTeacher
    }
}

var t = ZGTeacher()

var subject = ZGSubject.init("swift", t)

t.subject = subject

这里实例对象相互持有,造成对象无法释放,产生了循环引用。那么我们怎么解决这一问题哪?在swit中有两种方式,第一种是我们的弱引用,第二种方式是无主引用Unowned。

2.1 弱引用

弱引⽤不会对其引⽤的实例保持强引⽤,因⽽不会阻⽌ ARC 释放被引⽤的实例。这个特性阻⽌了引⽤变为循环强引⽤。声明属性或者变量时,在前⾯加上 weak 关键字表明这是⼀个弱引⽤。 由于弱引⽤不会强保持对实例的引⽤,所以说实例被释放了弱引⽤仍旧引⽤着这个实例也是有可能的。 因此,ARC 会在被引⽤的实例被释放时⾃动地设置弱引⽤为 nil 。由于弱引⽤需要允许它们的值为 nil , 它们⼀定得是可选类型。

class ZGTeacher {
    var age: Int = 18
    var name: String = "zhang"
}

weak var t = ZGTeacher()

print(Unmanaged.passUnretained(t as AnyObject).toOpaque())

print("end")

weak var t = ZGTeacher() 加上断点,并打开Xcode的汇编调试,编译运行一下

image.png

发现它明显执行了一个swift_weakInit。我们到swift源码里来搜索一下这个方法。

WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return ref;
}

声明一个weak变量相当于定义了一个WeakReference对象。

void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
  }
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
  
  // Preflight failures before allocating a new side table.
  if (oldbits.hasSideTable()) {
    // Already have a side table. Return it.
    return oldbits.getSideTable();
  } 
  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;
    }
    else if (failIfDeiniting && oldbits.getIsDeiniting()) {
      // Already past the start of deinit. Do nothing.
      return nullptr;
    }
    
    side->initRefCounts(oldbits);
    
  } while (! refCounts.compare_exchange_weak(oldbits, newbits,
                                             std::memory_order_release,
                                             std::memory_order_relaxed));
  return side;
}
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
      }
    }   
  }

在我们的swift里面,本质上存在着两种引用计数,一种是InlineRefCounts,里面就是**strong RC + unowned RC + flags,如果我们当前是有我们的引用计数,此时它就存储了HeapObjectSideTableEntry,里面包含了strong RC + unowned RC + weak RC + flags。