Swift -- 4.指針&內(nèi)存管理

一.為什么說指針不安全

  • 比如我們在創(chuàng)建一個對象的時候蘑斧,是需要在堆分配內(nèi)存空間的碰凶。但是這個內(nèi)存空間的聲明周期是有限的,也就意味著如果我們使用指針指向這塊內(nèi)容空間炕檩,如果當(dāng)前內(nèi)存空間的生命周期到了(引用計(jì)數(shù)為0)窖剑,那么我們當(dāng)前的指針是不是就變成了未定義的行為了坚洽,也就是成了野指針

  • 我們創(chuàng)建的內(nèi)存空間是有邊界的西土,比如我們創(chuàng)建一個大小為10的數(shù)組讶舰,這個時候我們通過指針訪問到了index = 11的位置,這個時候是不是就越界了需了,訪問了一個未知的內(nèi)存空間跳昼。

  • 指針類型和內(nèi)存的值不一致,也是不安全的肋乍。(指針的值是一個內(nèi)存地址鹅颊,也就是指針指向數(shù)據(jù)的首地址,根據(jù)這個地址只能得到指針指向的開始位置墓造,并不知道是什么類型的堪伍,因此指針的類型決定了數(shù)據(jù)的類型。比如此時的指針類型為Int8觅闽,而指向的數(shù)據(jù)為Int杠娱, 此時就會造成精度的缺失

二.typedPointer&rawPointer

Swift中的指針分為兩類,typed pointer指定數(shù)據(jù)類型指針谱煤,raw pointer未指定數(shù)據(jù)類型的指針(原生指針)摊求。

Swift Object-C 說明
UnsafePointer<T> const T * 指針可變,所指向的內(nèi)容都不可變
UnsafeMutablePointer<T> T * 指針及所指向的內(nèi)容均可變
UnsafeRawPointer const void * 指針指向的內(nèi)存區(qū)域未定
UnsafeMutableRawPointer void * 指針指向的內(nèi)存區(qū)域未定
UnsafeBufferPointer<T>
UnsafeMutableBufferPointer<T>
UnsafeRawBufferPointer
UnsafeMutableRawBufferPointer
  • BufferPointer表示申請連續(xù)的內(nèi)存空間

1.存儲4個整形的數(shù)據(jù)

使用UnsafeMutablePointer

//capacity:容量刘离, 4代表4個8字節(jié)(Int)
let p = UnsafeMutablePointer<Int>.allocate(capacity: 4)
for i in 0 ..< 4 {
    (p + i).initialize(to: i)
    
    //其他方式
//    p[i] = i
//    p.advanced(by: i).initialize(to: i)
}

for i in 0 ..< 4 {
    print((p + i).pointee) // 0,1,2,3
    
    //其他方式
//    print(p[i])
//    print(p.advanced(by: i).pointee)
}

defer {
    //將內(nèi)存空間全部摸成0
    p.deinitialize(count: 4)
    //釋放分配的內(nèi)存空間
    p.deallocate()
}

使用UnsafeMutableRawPointer

let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
for i in 0 ..< 4 {
    p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self)
}

for i in 0 ..< 4 {
    print(p.load(fromByteOffset: i * MemoryLayout<Int>.stride, as: Int.self)) // 0,1,2,3
}

defer {
    //有allocate分配內(nèi)存室叉,就有deallocate將分配的內(nèi)存釋放
    p.deallocate()
}

2.MemoryLayout

  • MemoryLayout<T>.size表示T的實(shí)際大小
  • MemoryLayout<T>.stride表示步長信息。也就是內(nèi)存對齊后的大小
  • MemoryLayout<T>.alignment表示內(nèi)存對齊大小
struct LGTeacher {
    var age = 18
    var sex = true
}

print(MemoryLayout<LGTeacher>.size) //9
print(MemoryLayout<LGTeacher>.stride) //16
print(MemoryLayout<LGTeacher>.alignment) //8

3.泛型指針

  • 泛型指針硫惕,其實(shí)就是指定類型指針

修改age的值

var age = 10

//此時這里其實(shí)是將age = age + 10, 并不是改的指針指向的內(nèi)存
age = withUnsafePointer(to: age) {
    $0.pointee + 10
}

print(age) //20

//修改指針指向的內(nèi)存
withUnsafeMutablePointer(to: &age) {
    $0.pointee += 10
}

print(age) //30

4.withUnsafePointer和Unmanaged

獲取實(shí)例的指針使用Unmanaged

//獲取實(shí)例t的指針
Unmanaged.passUnretained(t).toOpaque()

獲取內(nèi)存地址withUnsafePointer

//探究withUnsafePointer中參數(shù)是否帶&對返回值的影響
//1.如果為值類型
var age = 10

//此時對值取地址茧痕,表示age的內(nèi)存地址
let agePtr = withUnsafePointer(to: &age){$0} // 0x0000000100008270。取內(nèi)存0x100008270: 0x000000000000000a 0x0000000100008270

//已經(jīng)將值從堆區(qū)拷貝到了棧區(qū)恼除,然后返回的值在當(dāng)前棧區(qū)的內(nèi)存地址踪旷。第二個8字節(jié)為值原有的內(nèi)存地址也就是agePtr(0x0000000100008270)
let ageStackPtr = withUnsafePointer(to: age){$0} // 0x00007ff7bfeff228。取內(nèi)存0x7ff7bfeff228: 0x000000000000000a 0x0000000100008270

//2.如果值為實(shí)例類型豁辉,理解了值類型令野,指針類型也很好理解
var t = LGTeacher()

//此時的t為指針,實(shí)則是取指針的內(nèi)存地址
let tPtr = withUnsafePointer(to: &t){$0} // 0x0000000100008258徽级。0x100008258: 0x000000010b56b300 0x00007ff7bfeff280

//此時的t為指針气破,按照值類型的邏輯,將t指針拷貝到棧區(qū)餐抢,然后第二個8字節(jié)存放的是t指針原有的內(nèi)存地址现使。返回的是t指針在棧區(qū)的內(nèi)存地址低匙。因此第二個8字節(jié)其實(shí)就是tPtr
let tStackPtr = withUnsafePointer(to: t){$0} // 0x00007ff7bfeff1f0。0x7ff7bfeff1f0: 0x000000010b56b300 0x0000000100008258

//此時的0x000000010b56b300就是指針t

三.使用指針讀取Mach-o中類名碳锈、屬性名稱及vtable

class LGTeacher {
    var age = 18
    var name = "Kody"

    func teach() {
        print("teach")
    }

    func teach1() {
        print("teach1")
    }

    func teach2() {
        print("teach2")
    }

}

//表示__swift_types里的字節(jié)大小
var size: UInt = 0

//1.獲取__swift_types的內(nèi)存地址
var typesPtr = getsectdata("__TEXT", "__swift5_types", &size)
//print(typesPtr)


//2.獲取程序運(yùn)行的基地址
//獲取macho中header的地址顽冶,也就是程序的基地址
var machoHeaderPtr: UnsafePointer<mach_header>!
//在macOS Monterey版本下取3,在之前版本上取0就可以了
let count = _dyld_image_count()
for i in 0..<count  {
    if _dyld_get_image_header(i).pointee.filetype == MH_EXECUTE {
        machoHeaderPtr = _dyld_get_image_header(i)
    }
}

//print(machoHeaderPtr)


//3.通過__LINKEDIT拿到vmAddress(VM Address - File Offset) 或者通過__PAGEZERO拿到VM Size也就是VM Address
var vmAddress: UInt64 = 0

//方式1:通過__LINKEDIT
//var linkEditPtr = getsegbyname("__LINKEDIT")
//if let linkEdit = linkEditPtr?.pointee {
//    vmAddress = linkEdit.vmaddr - linkEdit.fileoff
//}

//方式2:通過__PAGEZERO
var pageZeroPtr = getsegbyname("__PAGEZERO")
vmAddress = pageZeroPtr?.pointee.vmsize ?? 0


//4.獲取__swift5_types的offset
var typesOffset: UInt64 = 0
//bitPattern按相同的二進(jìn)制位售碳,將內(nèi)存地址轉(zhuǎn)化為Int類型再轉(zhuǎn)化為UInt64
typesOffset = UInt64(bitPattern: Int64(Int(bitPattern: typesPtr))) - vmAddress


//5.通過基地址獲取__swift5_types中Data LO中4字節(jié)偏移信息
//將首地址轉(zhuǎn)為UInt64
let machoHeaderPtr_Int = UInt64(bitPattern: Int64(Int(bitPattern: machoHeaderPtr)))
//拿到dataLo的地址
var dataLoAddress = machoHeaderPtr_Int + typesOffset
//將dataLo地址轉(zhuǎn)為指針
let dataLoPtr = withUnsafePointer(to: &dataLoAddress){$0}
//獲取dataLo的值, 4字節(jié)
let dataLoContent = UnsafePointer<UInt32>.init(bitPattern: UInt(dataLoAddress))?.pointee
//UnsafePointer<UInt32>.init(bitPattern: Int(exactly: dataLoAddress) ?? 0)?.pointee

//6.獲取TargetClassDescriptor的offSet
let tcdOffset = UInt64(dataLoContent!) + typesOffset - vmAddress

//7.獲取TargetClassDescriptor在內(nèi)存當(dāng)中的地址
let targetClassDescriptor_Int = machoHeaderPtr_Int + tcdOffset

struct TargetClassDescriptor{
    /*
     關(guān)于原碼渗稍、反碼、補(bǔ)碼
     Int在計(jì)算機(jī)里存放的是補(bǔ)碼也就是反碼+1
    
     比如一個Int8, 1和-1
     1的原碼:00000001(存的是補(bǔ)碼)
     1的反碼:11111110
     1的補(bǔ)碼:11111111
     1在內(nèi)存:00000001
     
     -1的原碼:10000001
     -1的反碼:01111110
     -1的補(bǔ)碼:01111111
     -1在內(nèi)存:01111111(存的是補(bǔ)碼)
     
     */
    var flags: UInt32
    var parent: UInt32
    var name: UInt32
    var accessFunctionPointer: UInt32
    var fieldDescriptor: Int32
    var superClassType: Int32
    var metadataNegativeSizeInWords: UInt32
    var metadataPositiveSizeInWords: UInt32
    var numImmediateMembers: UInt32
    var numFields: UInt32
    var fieldOffsetVectorOffset: UInt32
    var Offset: UInt32
    var size: UInt32

    //vTable
}


//8.將TargetClassDescriptor地址還原成TargetClassDescriptor指針
let targetclassDescriptorPtr = UnsafePointer<TargetClassDescriptor>.init(bitPattern: UInt(targetClassDescriptor_Int))


//9.解析類名稱
if let name = targetclassDescriptorPtr?.pointee.name {
    //獲取nameOffset
    let nameOffset = Int64(name) + Int64(tcdOffset) + 8 - Int64(vmAddress)
    //name在程序中的真實(shí)地址
    let nameAddress = UInt64(nameOffset) + machoHeaderPtr_Int

    if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(nameAddress)) {
        print(String(cString: cChar))
    }
}

//10.拿到FieldDescriptor的Offset
let fieldDescriptorOffset = tcdOffset + 16 + UInt64(targetclassDescriptorPtr!.pointee.fieldDescriptor)


//11.獲取FieldDescriptor在內(nèi)存中的真實(shí)地址
let fieldDescriptor_Int = fieldDescriptorOffset + machoHeaderPtr_Int

struct FieldDescriptor  {
    var mangledTypeName: Int32
    var superclass: Int32
    var Kind: UInt16
    var fieldRecordSize: UInt16
    var numFields: UInt32
//    var fieldRecords: [FieldRecord]
}

struct FieldRecord{
    var Flags: UInt32
    var mangledTypeName: Int32
    var fieldName: UInt32
}

//12.將FiledDescriptor真實(shí)內(nèi)存地址綁定到結(jié)構(gòu)體指針上
let fileDescriptorPtr = UnsafePointer<FieldDescriptor>.init(bitPattern: UInt(fieldDescriptor_Int))

//13.獲取FieldRecords在內(nèi)存中的真實(shí)地址并綁定到結(jié)構(gòu)體上
let fileRecord_Int = fieldDescriptor_Int + 16

//14.便利打印屬性名稱
for i in 0..<fileDescriptorPtr!.pointee.numFields {
    let fileRecordPtr = UnsafePointer<FieldRecord>.init(bitPattern: UInt(fileRecord_Int + UInt64(i) * 12))
    
    let fileName_Int = UInt64(fileRecord_Int + UInt64(i) * 12) + 8 + UInt64(fileRecordPtr!.pointee.fieldName) - vmAddress

    if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(fileName_Int)) {
        print(String(cString: cChar))
    }
}

//疑問1:通過FileOffsetVectorOffset獲取屬性的值团滥,后續(xù)補(bǔ)充


//通過源碼得出的vTable結(jié)構(gòu)體
struct vTable {
    var flags: TargetMethodDescriptor
    var methodImpOffset: UInt32
}

struct TargetMethodDescriptor {
    var flagType: UInt32
    
    //從源碼恢復(fù)的
    enum KindType: UInt32 {
        case Method,
        Init,
        Getter,
        Setter,
        ModifyCoroutine,
        ReadCoroutine
    }
    
    enum FlagTypeMask: UInt32 {
        case  KindMask = 0x0F,                // 16 kinds should be enough for anybody
        IsInstanceMask = 0x10,
        IsDynamicMask = 0x20,
        IsAsyncMask = 0x40,
//        ExtraDiscriminatorShift = 16, //這個16與0x10沖突了
        ExtraDiscriminatorMask = 0xFFFF0000
      }
    
    func kindType() -> KindType {
        return KindType(rawValue: flagType & FlagTypeMask.KindMask.rawValue)!
    }
    
}

//15.獲取方法(vtable)
//vtable
let vtable_Int = targetClassDescriptor_Int + 13 * 4
let vtableCount = targetclassDescriptorPtr!.pointee.size

//vtable偏移量
var vtableOffset = tcdOffset + 13 * 4

for i in 0 ..< vtableCount {
    let vtablePtr = UnsafePointer<vTable>.init(bitPattern: UInt(vtable_Int + UInt64(i) * 8))
    
    //排除添加屬性生成的Getter竿屹、Setter、Modify方法灸姊,init方法
    //這里只拿到teach相關(guān)方法
    if vtablePtr!.pointee.flags.kindType() == .Method {
        let methodImp_Int = vtable_Int + UInt64(i) * 8 + UInt64(vtablePtr!.pointee.methodImpOffset) + 4 - vmAddress

        let methodImp = UnsafePointer<UInt64>.init(bitPattern: UInt(methodImp_Int))
        
        //將Imp指針轉(zhuǎn)化為方法
        typealias Function = @convention(c) () -> Void
        //unsafeBitCast,將一個指針指向的內(nèi)存強(qiáng)制按位轉(zhuǎn)化為目標(biāo)的類型拱燃。也就是將Imp強(qiáng)制轉(zhuǎn)化為Swift中的Function,從而完成調(diào)用力惯。否則在Swift里是不能操作Imp的
        let function = unsafeBitCast(methodImp, to: Function.self)
        function()
    }
}

疑問這里的vtable怎么沒有方法的名稱

/*
 方法的名稱稱之為符號碗誉。
 方法的名稱是放在Mach-o中的String Table中的。通過Symbol Table去查找String Table
 關(guān)于Symbol Table:1.String Table Index:存放了符號在字符串表的偏移量父晶。2.Value:存放了當(dāng)前函數(shù)的地址(也就是函數(shù)的Imp地址)
 根據(jù)偏移量去String Table就可以找到函數(shù)的方法名稱
 
 在上架的過程中是會將Symbol Table給抹除掉哮缺。
 原因也是因?yàn)槌绦虻陌踩裕谖覀兂绦蜻\(yùn)行的過程中會把符號表給剔除甲喝,mach-o會保留動態(tài)符號表(Dynamic Symbol Table)尝苇。將剔除的符號表放在了.dSYM文件下,也就是使用dSYM還原符號表的由來埠胖。
 因?yàn)槲覀兂绦蚓幾g完成后糠溜,函數(shù)的地址就確定了,保留符號是沒有必要的直撤,如果我們保留了符號表非竿,別人可以通過符號表通過符號很輕易的拿到函數(shù)地址信息,從而對這個函數(shù)進(jìn)行hock谋竖,顯然這是不安全的红柱。
 */

驗(yàn)證猜想及結(jié)論

1.從上面的代碼拿到一個方法teach的Imp0x0000000100003de0

2.Symbol Table找到該Imp指向的內(nèi)容

Symbol Table

3.通過拿到的0x58FCString Table拿到方法名稱

String Table

此時的函數(shù)名稱應(yīng)該存放在0x180A8 + 0x58FC = 0x1D9A4

4.查看0x1D9A4

此時可以發(fā)現(xiàn)這個字符串信息其實(shí)就是在第二步圖中的_$s9swiftTest9LGTeacherC5teachyyF,此時信息是混淆后的

5.通過終端還原混淆代碼

命令xcrun swift-demangle xxx

  • 至此蓖乘,成功還原出teach符號化信息

四.內(nèi)存綁定

Swift提供了三種不同的Api來綁定/重新綁定指針

  • assumingMemoryBound(to:)
func testPointer(_ p: UnsafePointer<Int>) {
    print(p[0]) // 10
    print(p[1]) // 20
    
}

let tuple = (10, 20)

withUnsafePointer(to: tuple) {
    testPointer(UnsafeRawPointer($0).assumingMemoryBound(to: Int.self))
}

/*
 assumingMemoryBound使用場景
 比如一個方法需要一個類型指針锤悄,但是現(xiàn)有的類型指針或原生指針與之不匹配,并且現(xiàn)有類型指針或原生指針指向的數(shù)據(jù)類型與需要的類型指針一致驱敲,
 因此可以使用該方法assumingMemoryBound(UnsafeRaPointer)告訴編譯器當(dāng)前指針指向的數(shù)據(jù)的類型是Int類型铁蹈,不需要再檢查我了宽闲,也就是繞過編譯器众眨,并沒有發(fā)生實(shí)際的類型轉(zhuǎn)換
 */
  • bindMemory
func testPointer(_ p: UnsafePointer<Int>) {
    print(p[0]) // 10
    print(p[1]) // 20
    
}

let tuple = (10, 20)

withUnsafePointer(to: tuple) {
//    testPointer(UnsafeRawPointer($0).assumingMemoryBound(to: Int.self))
    testPointer(UnsafeRawPointer($0).bindMemory(to: Int.self, capacity: 1)) // UnsafePointer<T>
}

/*
 bindMemory
 發(fā)生了實(shí)際類型的轉(zhuǎn)化握牧,把當(dāng)前的原生指針轉(zhuǎn)換成了UnsafePointer<T>,改定當(dāng)前內(nèi)存綁定的類型
 */
  • withMemoryRebound
func testPointer(_ p: UnsafePointer<Int>) {
    print(p[0]) // 10
    print(p[1]) // 20

}

let tuple = (10, 20)

withUnsafePointer(to: tuple) {
//    testPointer(UnsafeRawPointer($0).assumingMemoryBound(to: Int.self))
//    testPointer(UnsafeRawPointer($0).bindMemory(to: Int.self, capacity: 1)) // UnsafePointer<T>
    $0.withMemoryRebound(to: Int.self, capacity: 1) {
        testPointer($0)
    }
}

/*
 withMemoryRebound
 用于臨時更改當(dāng)前指針綁定的內(nèi)存娩梨,出了閉包作用域就失效了
 */

查看源碼通過內(nèi)存綁定還原實(shí)例

//查看源碼總結(jié)出的MetaData及HeapObject結(jié)構(gòu)

struct MetaData {
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int,Int)
    var data: Int
    var flags: UInt32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var description: UInt32
    var iVarDestroyer: UInt32
}

struct HeapObject {
    var metadata: UnsafePointer<MetaData>
    var refCount: UInt
}

class LGTeacher {
    var age = 18
    var name = "Kody"
}

//此時的heapObject就是還原后的數(shù)據(jù)
let heapObject = Unmanaged.passUnretained(t).toOpaque().bindMemory(to: HeapObject.self, capacity: 1)

五.內(nèi)存管理中的引用計(jì)數(shù)(RefCount)

Swift中使用自動引用計(jì)數(shù)(ARC)

class LGTeacher{
    var age: Int = 18
    var name: String = "Kody"
}

var t = LGTeacher()

//獲取內(nèi)存指針
print(Unmanaged.passUnretained(t).toOpaque())
斷點(diǎn)下在print
0x000000010b50d2f0
(lldb) x/8g 0x000000010b50d2f0
0x10b50d2f0: 0x00000001000081a8 0x0000000000000003
0x10b50d300: 0x0000000000000012 0x0000000079646f4b
0x10b50d310: 0xe400000000000000 0x0000000000000000
0x10b50d320: 0x0000000000000006 0x00007ff850b6d198
(lldb) 
  • 0x0000000000000003這個就是refCount

1.源碼分析refCount

1.進(jìn)入HeapObject.h找到RefCount

// The members of the HeapObject header that are not shared by a
// standard Objective-C instance
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *__ptrauth_objc_isa_pointer metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
  ...
}

2.進(jìn)入InlineRefCounts

typedef RefCounts<InlineRefCountBits> InlineRefCounts;
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;

template <typename RefCountBits>
class RefCounts {
  std::atomic<RefCountBits> refCounts;

  // Out-of-line slow paths.

  SWIFT_NOINLINE
  void incrementSlow(RefCountBits oldbits, uint32_t inc) SWIFT_CC(PreserveMost);

  SWIFT_NOINLINE
  void incrementNonAtomicSlow(RefCountBits oldbits, uint32_t inc);

  SWIFT_NOINLINE
  bool tryIncrementSlow(RefCountBits oldbits);

  SWIFT_NOINLINE
  bool tryIncrementNonAtomicSlow(RefCountBits oldbits);

  SWIFT_NOINLINE
  void incrementUnownedSlow(uint32_t inc);

  ...
}
  • 此時的InlineRefCountsRefCounts<InlineRefCountBits>的模板類沿腰。有一個泛型參數(shù)InlineRefCountBits

  • 在RefCounts類中我們可以發(fā)現(xiàn)其實(shí)都是在操作傳進(jìn)來的泛型參數(shù)InlineRefCountBits

  • 因此RefCounts其實(shí)是對引用計(jì)數(shù)的一個包裝,引用計(jì)數(shù)的具體類型取決于傳入進(jìn)來的具體參數(shù)狈定。

3.探究泛型參數(shù)InlineRefCountBits

//InlineRefCountBits是RefCountBitsT的模板函數(shù)
// RefCountIsInline泛型參數(shù)true或false
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;

// RefCountIsInline: refcount stored in an object
// RefCountNotInline: refcount stored in an object's side table entry
enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };

//找到RedCountBitsT
class RefCountBitsT {

  friend class RefCountBitsT<RefCountIsInline>;
  friend class RefCountBitsT<RefCountNotInline>;
  
  static const RefCountInlinedness Inlinedness = refcountIsInline;

  //BitsType是RefCountBitsInt的模板函數(shù)
  typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;
  typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::SignedType
    SignedBitsType;
  typedef RefCountBitOffsets<sizeof(BitsType)>
    Offsets;

  //只有一個屬性bits
  BitsType bits;
  ...
}

//找到BitsType的模板函數(shù)RefCountBitsInt
//發(fā)現(xiàn)本質(zhì)就是一個64位的位域信息

// 64-bit inline
// 64-bit out of line
template <RefCountInlinedness refcountIsInline>
struct RefCountBitsInt<refcountIsInline, 8> {
  typedef uint64_t Type;
  typedef int64_t SignedType;
};
  • 實(shí)際上Swift中的RefCounts其實(shí)也是存放的64位 的位域信息颂龙,存放了運(yùn)行聲明周期相關(guān)的引用計(jì)數(shù)

  • 為什么一個簡單的UInt64需要繞這么大一圈來存儲?
    因?yàn)槌橄蟪鰜淼哪0搴瘮?shù)后續(xù)還要兼容弱引用纽什,提高代碼復(fù)用率

4.創(chuàng)建一個實(shí)例對象措嵌,當(dāng)前的引用計(jì)數(shù)是多少?

  //之前我們在探究Metadata時芦缰,在_swift_allocObject中有實(shí)例的初始化方法
  new (object) HeapObject(metadata);
  
  //HeapObject的初始化代碼
  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
  //傳入的RedCount是Initialized

  //找到Initialzed的定義企巢,在Class RefCount中
class RefCounts {
  std::atomic<RefCountBits> refCounts;
  ...
  public:
  //Initialized其實(shí)是一個枚舉Initialized_t
  enum Initialized_t { Initialized };
  enum Immortal_t { Immortal };

  // Refcount of a new object is 1.
  constexpr RefCounts(Initialized_t)
    //其實(shí)就是傳入0,1
    //RefCountBits也就是RefCountBitsT這個類
    : refCounts(RefCountBits(0, 1)) {}
  ...
}

  /*
    RefCountBits(0, 1)
    strongExtraCount - 0
    unownedCount - 1

    根據(jù)RefCountBitOffsets,得出
    StrongExtraRefCountShift = 33
    PureSwiftDeallocShift = 0
    UnownedRefCountShift = 1

    static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
    static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
    # define shiftAfterField(name) (name##Shift + name##BitCount)

    把強(qiáng)引用計(jì)數(shù)和無主引用計(jì)數(shù)通過位移的方法存放在64位位域信息當(dāng)中
    強(qiáng)引用左偏移33位让蕾,0
    是否是純Swift析構(gòu)函數(shù)的標(biāo)志位浪规,左偏移0位,也就是低1位1探孝。因?yàn)檫@里是由swift_allocObject執(zhí)行過來的笋婿,肯定是Swift的析構(gòu)
    無主引用左偏移1位,低2位上是1顿颅。也就是0x2
  */
  SWIFT_ALWAYS_INLINE
  constexpr
  RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
    : bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
           (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
           (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
  { }


位移結(jié)構(gòu)體RefCountBitOffsetsshiftAfterField

// Layout of refcount bits.
// field value = (bits & mask) >> shift
// FIXME: redo this abstraction more cleanly
  
# define maskForField(name) (((uint64_t(1)<<name##BitCount)-1) << name##Shift)
# define shiftAfterField(name) (name##Shift + name##BitCount)

template <size_t sizeofPointer>
struct RefCountBitOffsets;

// 64-bit inline
// 64-bit out of line
// 32-bit out of line
template <>
struct RefCountBitOffsets<8> {
  /*
   The bottom 32 bits (on 64 bit architectures, fewer on 32 bit) of the refcount
   field are effectively a union of two different configurations:
   
   ---Normal case---
   Bit 0: Does this object need to call out to the ObjC runtime for deallocation
   Bits 1-31: Unowned refcount
   
   ---Immortal case---
   All bits set, the object does not deallocate or have a refcount
   */
  static const size_t PureSwiftDeallocShift = 0;
  static const size_t PureSwiftDeallocBitCount = 1;
  static const uint64_t PureSwiftDeallocMask = maskForField(PureSwiftDealloc);

  static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
  static const size_t UnownedRefCountBitCount = 31;
  static const uint64_t UnownedRefCountMask = maskForField(UnownedRefCount);

  static const size_t IsImmortalShift = 0; // overlaps PureSwiftDealloc and UnownedRefCount
  static const size_t IsImmortalBitCount = 32;
  static const uint64_t IsImmortalMask = maskForField(IsImmortal);

  static const size_t IsDeinitingShift = shiftAfterField(UnownedRefCount);
  static const size_t IsDeinitingBitCount = 1;
  static const uint64_t IsDeinitingMask = maskForField(IsDeiniting);

  static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
  static const size_t StrongExtraRefCountBitCount = 30;
  static const uint64_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
  
  static const size_t UseSlowRCShift = shiftAfterField(StrongExtraRefCount);
  static const size_t UseSlowRCBitCount = 1;
  static const uint64_t UseSlowRCMask = maskForField(UseSlowRC);

  static const size_t SideTableShift = 0;
  static const size_t SideTableBitCount = 62;
  static const uint64_t SideTableMask = maskForField(SideTable);
  static const size_t SideTableUnusedLowBits = 3;

  static const size_t SideTableMarkShift = SideTableBitCount;
  static const size_t SideTableMarkBitCount = 1;
  static const uint64_t SideTableMarkMask = maskForField(SideTableMark);
};

2.總結(jié)出當(dāng)沒有SideTable時的位域信息

源碼版本Swift5.5.2

Swift中RefCount的64位域信息
占位 名稱 含義
0 PureSwiftDealloc 是否使用Swift執(zhí)行Dealloc析構(gòu)函數(shù)缸濒,false表示使用objc-Runtime執(zhí)行析構(gòu)函數(shù)
1~31 UnownedRefCount 無主引用計(jì)數(shù)
32 isDeiniting 是否正在析構(gòu)函數(shù),也可以稱為是否正在釋放
33~62 StrongExtraRefCount 強(qiáng)引用計(jì)數(shù)
63 UseSlowRC 是否使用緩慢RC
0~32 isImmortal 是否是不朽對象粱腻。由之前的0變?yōu)?~32
  • 對于之前的isImmortal
`overlaps PureSwiftDealloc and UnownedRefCount`
根據(jù)绍填,這段是覆蓋在`PureSwiftDealloc`和`UnOwnedRefCount`上的

---Immortal case---
All bits set, the object does not deallocate or have a refcount

如果將所有bits設(shè)置后,這個對象不會釋放或者有一個引用計(jì)數(shù)

3.代碼驗(yàn)證位域信息

  1. 驗(yàn)證isDeiniting
class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
}

var t: LGTeacher? = LGTeacher()

//獲取內(nèi)存指針
//為什么沒有用po t栖疑,而是使用代碼獲取內(nèi)存地址讨永。是因?yàn)閜o t會影響當(dāng)前的引用計(jì)數(shù)
print(Unmanaged.passUnretained(t!).toOpaque())

//breakpoint 0x10b10f6b0: 0x00000001000081a8 0x0000000000000003
t = nil
//breakpoint 0x10b10f6b0: 0x00000001000081a8 0x0000000100000003
  • 當(dāng)執(zhí)行完t = nil后,refCount的32位變?yōu)榱?遇革,表示實(shí)例對象正在執(zhí)行析構(gòu)函數(shù)

2.源碼查看強(qiáng)引用的添加_swift_retain_

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;
}

  //進(jìn)入object->refCounts.increment(1)

  // Increment the reference count.
  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));
  }

  //增加強(qiáng)引用計(jì)數(shù)newbits.incrementStrongExtraRefCount(inc)

  // Returns true if the increment is a fast-path result.
  // Returns false if the increment should fall back to some slow path
  // (for example, because UseSlowRC is set or because the refcount overflowed).
  SWIFT_NODISCARD SWIFT_ALWAYS_INLINE bool
  incrementStrongExtraRefCount(uint32_t inc) {
    // This deliberately overflows into the UseSlowRC field.
    //這里的inc為1卿闹,相當(dāng)于bits += 1<< 33
    //StrongExtraRefCountShift = 33
    bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
    return (SignedBitsType(bits) >= 0);
  }
  • 調(diào)用swift_retain相當(dāng)于執(zhí)行bits += inc << 33,也印證了初始化的邏輯

3.代碼驗(yàn)證強(qiáng)引用計(jì)數(shù)

class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
}

var t = LGTeacher()

//獲取內(nèi)存指針
//為什么沒有用po t萝快,而是使用代碼獲取內(nèi)存地址锻霎。是因?yàn)閜o t會影響當(dāng)前的引用計(jì)數(shù)
print(Unmanaged.passUnretained(t).toOpaque())

var t1 = t //breakpoint 0x101423ed0: 0x00000001000081a8 0x0000000000000003(源碼分析0位為1,1位為1存放無主引用,剛好為0x3)
var t2 = t //breakpoint 0x101423ed0: 0x00000001000081a8 0x0000000200000003(源碼分析揪漩,1偏移33位存放強(qiáng)引用旋恼,加上之前的無主引用0x3,為0x200000003)
var t3 = t //breakpoint 0x101423ed0: 0x00000001000081a8 0x0000000400000003(源碼分析奄容,2偏移33位存放強(qiáng)引用冰更,加上之前的無主引用0x3产徊,為0x400000003)
var t4 = t //breakpoint 0x101423ed0: 0x00000001000081a8 0x0000000600000003(源碼分析,3偏移33位存放強(qiáng)引用蜀细,加上之前的無主引用0x3舟铜,為0x600000003)

六.內(nèi)存管理中的弱引用

弱引用不會對其引用的實(shí)例保持強(qiáng)引用,因而不會阻止ARC釋放被引用的實(shí)例奠衔。這個特性阻止了引用變?yōu)檠h(huán)強(qiáng)引用谆刨。聲明屬性或變量時,在前面加上weak關(guān)鍵字表明這是一個弱引用归斤。

由于弱引用不會強(qiáng)保持對實(shí)例的引用痊夭,所以說實(shí)例被釋放了弱引用仍舊引用著這個實(shí)例也是有可能的。因此脏里,ARC會在被引用的實(shí)例被釋放時自動地設(shè)置弱引用為nil.由于弱引用需要允許它們的值為nil生兆,它們一定得是可選類型。

簡而言之就是 weak var t = LGTeacher()膝宁,這里t因?yàn)槭强勺兊模ㄡ尫艜r會變?yōu)閚il)鸦难,所以必須是由var修飾

1.通過匯編探究使用weak后,程序是怎么執(zhí)行的

class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
}

weak var t = LGTeacher() // 添加一個斷點(diǎn)
swift_weakInit
  • 發(fā)現(xiàn)使用了weak后會調(diào)用swift_weakInit函數(shù)

2.通過源碼分析swift_weakInit

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

//nativeInit
  void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
  }

//formWeakReference

// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  //創(chuàng)建了一個SideTable
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}

  • 聲明一個var變量相當(dāng)于重新定義了WeakReference對象
  • 實(shí)際上就是創(chuàng)建了一個散列表SideTable

3.SideTable相關(guān)分析

進(jìn)入到allocateSideTable

// Return an object's side table, allocating it if necessary.
// Returns null if the object is deiniting.
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
  //取出原先的RefCount
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
  
  // Preflight failures before allocating a new side table.
  //判斷原先的refCount是否有SideTable员淫,如果有直接返回SideTable
  if (oldbits.hasSideTable()) {
    // Already have a side table. Return it.
    return oldbits.getSideTable();
  } 
  //如果當(dāng)前實(shí)例正在執(zhí)行析構(gòu)函數(shù)合蔽,直接返回null。也就沒有必要做其它操作了
  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創(chuàng)建一個SideTable
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  
  //通過side初始化RefCount
  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存入之前的RefCount的信息
    side->initRefCounts(oldbits);
    
  } while (! refCounts.compare_exchange_weak(oldbits, newbits,
                                             std::memory_order_release,
                                             std::memory_order_relaxed));
  return side;
}

進(jìn)入HeapObjectSideTableEntry

//這里的InlineRefCounts是我們在分析介返,執(zhí)行swift_allocObject時拴事,在HeapObject中的refCount類型就是InlineRefCounts。
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
//這里的SideTableRefCounts圣蝎,與InlineRefCounts共用一個模板函數(shù)RefCounts刃宵。
//并且這里的泛型參數(shù)SideTableRefCountBits也是共用一個父類RefCountBitsT
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;

class HeapObjectSideTableEntry {
  // FIXME: does object need to be atomic?
  //存儲了當(dāng)前的實(shí)例對象
  std::atomic<HeapObject*> object;
  //存儲了當(dāng)前的refCount
  SideTableRefCounts refCounts;

  ...
}

關(guān)于SideTableRefCountBits

//與InlineRefCountBits共同繼承自RefCountBits
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
  //多了一個32字節(jié)存儲weakBits
  uint32_t weakBits;

  public:
    SWIFT_ALWAYS_INLINE
    SideTableRefCountBits() = default;

    SWIFT_ALWAYS_INLINE
    constexpr SideTableRefCountBits(uint32_t strongExtraCount,
                                    uint32_t unownedCount)
        : RefCountBitsT<RefCountNotInline>(strongExtraCount, unownedCount)
          // weak refcount starts at 1 on behalf of the unowned count
          ,
          weakBits(1) {}

    SWIFT_ALWAYS_INLINE
    SideTableRefCountBits(HeapObjectSideTableEntry *side) = delete;

    SWIFT_ALWAYS_INLINE
    SideTableRefCountBits(InlineRefCountBits newbits)
        : RefCountBitsT<RefCountNotInline>(&newbits), weakBits(1) {}

    SWIFT_ALWAYS_INLINE
    void incrementWeakRefCount() { weakBits++; }

    SWIFT_ALWAYS_INLINE
    bool decrementWeakRefCount() {
      assert(weakBits > 0);
      weakBits--;
      return weakBits == 0;
  }

  SWIFT_ALWAYS_INLINE
  uint32_t getWeakRefCount() {
    return weakBits;
  }

  // Side table ref count never has a side table of its own.
  SWIFT_ALWAYS_INLINE
  bool hasSideTable() {
    return false;
  }
};

關(guān)于源碼對InlineRefCountSideTableRefCount的總結(jié)

  Storage layout:

  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中存在兩種引用計(jì)數(shù)
不存在弱引用,InlineRefCounts
存在弱引用徘公,HeapObjectSideTableEntry

在源碼中找到關(guān)于SideTableRefCountBits的初始化方法

  SWIFT_ALWAYS_INLINE
  RefCountBitsT(HeapObjectSideTableEntry* side)
    //通過RefCountOffsets中可知SideTableUnusedLowBits = 3牲证,也就是將HeapObjectSideTableEntry右移3位存入
    //(BitsType(1) << Offsets::UseSlowRCShift),1右移63位关面,還是標(biāo)識是否慢速RC
    //(BitsType(1) << Offsets::SideTableMarkShift))坦袍,1右移62位,散列表標(biāo)記
    : bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
           | (BitsType(1) << Offsets::UseSlowRCShift)
           | (BitsType(1) << Offsets::SideTableMarkShift))
  {
    assert(refcountIsInline);
  }

當(dāng)存在SideTable時等太,也就是有弱引用的時候捂齐,64位位域分布情況

SideTableRefCount

4.代碼驗(yàn)證

1.獲取refCount相關(guān)數(shù)據(jù)


class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
}

var t = LGTeacher()

//獲取內(nèi)存指針
//為什么沒有用po t,而是使用代碼獲取內(nèi)存地址缩抡。是因?yàn)閜o t會影響當(dāng)前的引用計(jì)數(shù)
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())

//加入斷點(diǎn)
weak var t1 = t

//加入斷點(diǎn)

InlineRefCounts

2.獲取HeapObjectSideTableEntry

  • 將62位(SideTableMark)和63位(UseSlowRC)置為0
  • 然后左移3位還原出HeapObjectSideTableEntry 奠宜,得出0x10075F790

3.查看HeapObjectSideTableEntry信息

HeapObjectSideTableEntry

4.為什么添加一個弱引用是2?

這時在代碼里再添加一個weak引用當(dāng)前t,發(fā)現(xiàn)此時的弱引用計(jì)數(shù)為0x3压真。猜想大概率是因?yàn)樵?code>SideTableRefBits創(chuàng)建時給了一個初始值1娩嚼。

通過源碼分析

//1.找到添加弱引用時執(zhí)行的代碼,使用incrementWeak增加弱引用

// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}

  //2.HeapObejectSideTableEntry中的incrementWeak()

  // WEAK

  SWIFT_NODISCARD
  HeapObjectSideTableEntry* incrementWeak() {
    // incrementWeak need not be atomic w.r.t. concurrent deinit initiation.
    // The client can't actually get a reference to the object without
    // going through tryRetain(). tryRetain is the one that needs to be
    // atomic w.r.t. concurrent deinit initiation.
    // The check here is merely an optimization.
    if (refCounts.isDeiniting())
      return nullptr;
    refCounts.incrementWeak();
    return this;
  }

  //3.找到RefCount中的incrementWeak()

  // Increment the weak reference count.
  void incrementWeak() {
    auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
    RefCountBits newbits;
    do {
      newbits = oldbits;
      assert(newbits.getWeakRefCount() != 0);
      newbits.incrementWeakRefCount();
      
      if (newbits.getWeakRefCount() < oldbits.getWeakRefCount())
        swift_abortWeakRetainOverflow();
    } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                              std::memory_order_relaxed));
  }

  //4.找到incrementWeakRefCount()

    SWIFT_ALWAYS_INLINE
    void incrementWeakRefCount() { weakBits++; }

  //5.weakBits就是存放弱引用的uint32_t類型榴都。 因此就解釋了多一個引用計(jì)數(shù)就變?yōu)榱?

  //6.根據(jù) HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());待锈,
  //找到HeapObjectSideTableEntry的初始化方法
  //函數(shù)內(nèi)創(chuàng)建了refCounts漠其,這里的refCounts是SideTableRefCount嘴高,也就是RefCount<SideTableRefCountBits>.

    public:
  HeapObjectSideTableEntry(HeapObject *newObject)
    : object(newObject), refCounts()
  { }  

  //7.找到SideTableRefCountBits的初始化方法
  //在SideTableRefCountBits創(chuàng)建的時候?qū)eakBits設(shè)置為1

  SWIFT_ALWAYS_INLINE
    constexpr SideTableRefCountBits(uint32_t strongExtraCount,
                                    uint32_t unownedCount)
        : RefCountBitsT<RefCountNotInline>(strongExtraCount, unownedCount)
          // weak refcount starts at 1 on behalf of the unowned count
          ,
          weakBits(1) {}
5.圖解總結(jié)InlineRefCounts和HeapObjectSideTableEntry

六.內(nèi)存管理中的無主引用

關(guān)鍵字unowned。和弱引用類似和屎,無主引用不會牢牢保持住引用的實(shí)例拴驮。但是不像弱引用,總之引用假定是永遠(yuǎn)有值的柴信。因此無主引用并不是類型安全的

class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
}

var t: LGTeacher? = LGTeacher()

unowned let t2 = t

t = nil

print(t2)

//Fatal error: Attempted to read an unowned reference but object 0x10142c9c0 was already deallocated2022-01-13 17:46:36.434502+0800 swiftTest[2358:153642] Fatal error: Attempted to read an unowned reference but object 0x10142c9c0 was already deallocated
//Fatal error: Attempted to read an unowned reference but object 0x10142c9c0 was already deallocated

根據(jù)蘋果官方文檔的建議套啤。
當(dāng)我們知道兩個對象的生命周期并不相關(guān),那么我們必須使用weak随常。例如delegate
反正潜沦,非強(qiáng)引用對象擁有和強(qiáng)引用對象擁有同樣的聲明周期的話,我們應(yīng)該使用unowned

unowned的性能更好绪氛,不需要創(chuàng)建SideTable

七.內(nèi)存管理中的循環(huán)引用&捕獲列表

class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
    
    deinit {
        print("LGTeacher deinit")
    }
}

let t = LGTeacher()

let closure = {
    print(t.age)
}

closure()

//18
//并沒有執(zhí)行deinit唆鸡,此時不執(zhí)行的原因并不是因?yàn)檠h(huán)引用,而是因?yàn)閠是一個全局變量

循環(huán)引用例子

class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
    
    var closure: (() -> Void)?
    
    deinit {
        print("LGTeacher deinit")
    }
}

func test() {
    let t = LGTeacher()
    //這里就形成了循環(huán)引用
    t.closure = {
        print(t.age)
    }
    t.closure?()
}

test()

//18

解決方案

class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
    
    var closure: (() -> Void)?
    
    deinit {
        print("LGTeacher deinit")
    }
}

func test() {
    let t = LGTeacher()
    //這里就形成了循環(huán)引用
    weak var t1 = t //使用弱引用解決循環(huán)引用
    unowned var t2 = t //使用無主引用解決循環(huán)引用
    t.closure = {
        print(t1?.age)
    }
    t.closure?()
}

test()

//Optional(18)
//LGTeacher deinit

在Swift中還可以使用捕獲列表來解決循環(huán)引用

class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"
    
    var closure: (() -> Void)?
    
    deinit {
        print("LGTeacher deinit")
    }
}

func test() {
    let t = LGTeacher()
    //這里就形成了循環(huán)引用
    t.closure = { [weak t] in
        //也可以使用[unowned t]
        print(t?.age)
    }
    t.closure?()
}

test()

//Optional(18)
//LGTeacher deinit

關(guān)于捕獲列表

var age = 0

var height = 1.85

/*
 對編譯器來說枣察,當(dāng)執(zhí)行閉包時(捕獲列表發(fā)生在閉包調(diào)用的時候)争占,
如果有捕獲列表,就會去函數(shù)上下文找到與之對應(yīng)的值序目,復(fù)制到捕獲列表當(dāng)中臂痕,
 因此無論后面怎么修改,在閉包內(nèi)部訪問的值都不會改變.
 */
let closure = { [age] in
    
    print(age) //0
    print(height) //1.85
}

age = 10

height = 1.85

closure()

在Switf閉包內(nèi)使用strong dance

class LGTeacher {
    var age: Int = 18
    var name: String = "Kody"

    var closure: (() -> Void)?

    deinit {
        print("LGTeacher deinit")
    }
}

func test() {
    let t = LGTeacher()
    t.closure = { [weak t] in
        //使用strong dance猿涨,在Swift里就是一個解包操作
        if let strongT = t {
            print(strongT.age)
        }
        
        //延遲實(shí)例變量的生命周期
        withExtendedLifetime(t) {
        }
    }
    t.closure?()
}

test()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末握童,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子叛赚,更是在濱河造成了極大的恐慌舆瘪,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件红伦,死亡現(xiàn)場離奇詭異英古,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)昙读,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門召调,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事唠叛≈幌” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵艺沼,是天一觀的道長册舞。 經(jīng)常有香客問我,道長障般,這世上最難降的妖魔是什么调鲸? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮挽荡,結(jié)果婚禮上藐石,老公的妹妹穿的比我還像新娘。我一直安慰自己定拟,他們只是感情好于微,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著青自,像睡著了一般株依。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上延窜,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天恋腕,我揣著相機(jī)與錄音,去河邊找鬼需曾。 笑死吗坚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的呆万。 我是一名探鬼主播商源,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谋减!你這毒婦竟也來了牡彻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤出爹,失蹤者是張志新(化名)和其女友劉穎庄吼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體严就,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡总寻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了梢为。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渐行。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡轰坊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祟印,到底是詐尸還是另有隱情肴沫,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布蕴忆,位于F島的核電站颤芬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏套鹅。R本人自食惡果不足惜站蝠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望芋哭。 院中可真熱鬧沉衣,春花似錦郁副、人聲如沸减牺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拔疚。三九已至,卻和暖如春既荚,著一層夾襖步出監(jiān)牢的瞬間稚失,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工恰聘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留句各,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓晴叨,卻偏偏與公主長得像凿宾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子兼蕊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容