美文网首页swiftSwift
Swift进阶-内存管理

Swift进阶-内存管理

作者: 顶级蜗牛 | 来源:发表于2022-01-10 20:00 被阅读0次

Swift进阶-类与结构体
Swift-函数派发
Swift进阶-属性
Swift进阶-指针
Swift进阶-内存管理
Swift进阶-TargetClassMetadata和TargetStructMetadata数据结构源码分析
Swift进阶-Mirror解析
Swift进阶-闭包
Swift进阶-协议
Swift进阶-泛型
Swift进阶-String源码解析
Swift进阶-Array源码解析

类与结构体章节讲到:通过汇编调试和swift源码知道我们的 纯swift类的对象 Teacher() 的内存分配: __allocating_init -> _swift_allocObject_ -> swift_slowAlloc -> malloc

_swift_allocObject_

其中_swift_allocObject_里面创建一个 HeapObject对象并将其返回了,所以这个HeapObject就是我们实际创建的对象,来看看其初始化函数:

  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }

了解上面内容后,来看看创建一个对象,输出它的地址,格式化输出它的内容:

class Teacher {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        let teacher = Teacher(name: "陈老师")
        // 通过Unmanaged指定内存管理,类似于OC与CF的交互方式(所有权的转换 __bridge)
        // passUnretained 不增加引用计数,即不需要获取所有权
        // passRetained 增加引用技术,即需要获取所有权
        // toOpaque 不透明的指针
        let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
        print(ptr)
        print("end")  // 断点在这里
    }
}
输出

通过控制台输出后得到上图,而x/8g输出的前16个字节就是matadatarefcounts,这里的refcounts就是本章节研究的内容。

ps: 注意此时要用Unmanaged.passUnretained去输出teacher的指针,而不能在控制台上 po teacher!因为 po teacher 会对其进行引用,就不是上面这个结果了,来看看:

po对象造成引用

本章节围绕一个问题探究引用计数:那为什么一次引用后会变成这个数值0x0000000200000002

引用后打印

1.引用计数的实质

从源码中HeapObject.h的找到refcounts的定义

refcounts 的定义 RefCounts

RefCounts其实就是对我们当前引用计数的包装类,而引用计数的具体类型取决于传递给RefCounts的参数 -> InlineRefCountBits
找到InlineRefCountBits的定义:

typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;

它同样是一个模板类RefCountBitsT

RefCountIsInline

RefCountIsInline传递的是true/false,再去看看模板类RefCountBitsT

RefCountBitsT

RefCountBitsT就是我们真实操作引用计数的类,它只有一个属性 bits,它的类型定义是 RefCountBitsInt

image.png

引用计数的实质: bits其实就是一个uint64位的位域信息,它存储了就是运行周期相关的引用计数(Swift和OC都是一样的64位的位域信息)。

1.1 提问:当创建一个引用对象的时候,它的引用计数是多少?

来看看源码中的创建对象函数_swift_allocObject_

_swift_allocObject_ HeapObject定义

引用计数的初始化赋值,传递了一个Initialized,点进去看看Initialized定义,在RefCount.h找到它是一个枚举:

可以看到源码的注释:新对象的Refcount为1。
RefCountBits就是模板类RefCountBitsT(它有一个bits属性)

找到RefCountBitsT初始化函数,很快能定位到传递的参数strongExtraCount: 0unownedCount: 1是怎么操作bits的:

RefCountBitsT初始化函数

0左移33位还是0,1左移1位是2,所以refCounts是0x2。
当声明一个引用对象,该对象没有被引用的时候,refCounts是0x0000000000000002:

let teacher = Teacher(name: "陈老师")
let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
print(ptr)
print("end")
此时格式化输出ptr的地址

当teacher被引用的时候,其refCounts是0x0000000200000002:

let teacher = Teacher(name: "陈老师")
let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
print(ptr)
print("end")
let t = teacher
print("end")

1左移33位,1左移1位是2,所以refCounts是0x0000000200000002。

0x0000000200000002

如果再被引用一次,其refCounts是0x0000000400000002
2左移33位,1左移1位是2,所以refCounts是0x0000000400000002。

0x0000000400000002

结论:强引用计数无主引用计数是通过位移的方式,存储在这64位的信息当中。

引用计数64位存储
1.2 提问:强引用是如何添加的呢?

从swift源码中找到HeapObject.cpp找到_swift_retain_函数实现:

_swift_retain_ increment

引用计数+1:

incrementStrongExtraRefCount

引用计数-1:

decrementStrongExtraRefCount

2.循环引用问题

经典案例:

class WJTeacher { 
    var age: Int = 18 
    var name: String = "林老师" 
    var subject: WJSubject?
}
class WJSubject { 
    var subjectName: String 
    var subjectTeacher: WJTeacher 
    init(_ subjectName: String, _ subjectTeacher: WJTeacher) {
        self.subjectName = subjectName 
        self.subjectTeacher = subjectTeacher 
    } 
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        let teacher = WJTeacher()
        let subject = WJSubject.init("Swift进阶", teacher)
        teacher.subject = subject
    }
}

上面这段代码两个对象相互引用导致无法释放。
Swift 提供了两种办法⽤来解决你在使 ⽤类的属性时所遇到的循环强引⽤问题:弱引⽤( weak reference )⽆主引⽤( unowned reference )

weak弱引用解决上面的问题:

class WJTeacher {
    var age: Int = 18
    var name: String = "Steven"
    weak var subject: WJSubject?  // 弱引用subject
}
class WJSubject {
    var subjectName: String
    var subjectTeacher: WJTeacher
    init(_ subjectName: String, _ subjectTeacher: WJTeacher) {
        self.subjectName = subjectName
        self.subjectTeacher = subjectTeacher
    }
}

或者使用unowned无主引用解决上面的问题

class WJTeacher {
    var age: Int = 18
    var name: String = "Steven"
    var subject: WJSubject?
}
class WJSubject {
    var subjectName: String
    unowned var subjectTeacher: WJTeacher  // 无主引用subjectTeacher
    init(_ subjectName: String, _ subjectTeacher: WJTeacher) {
        self.subjectName = subjectName
        self.subjectTeacher = subjectTeacher
    }
}

弱引用和无主引用有什么区别呢?

2.1 weak弱引用

Alaways Show Disassembly打开编调试,看看weak修饰对象在创建时调用流程

weak var t = WJTeacher()
print(t)  // 断点打在这里
汇编调试weak修饰的WJTeacher对象创建

类与结构体章节看过__allocating_init()的调用过程,没有看到相关弱引用的内容;接下来又调用了swift_weakInit函数;找到swift源码:

swift_weakInit nativeInit formWeakReference

来看看allocateSideTable做了啥事儿

allocateSideTable

来找到side: HeapObjectSideTableEntry到底是啥玩意儿,全局搜索了一下找到了源码中有这么一段注释,直接给出了结论:

HeapObjectSideTableEntry说明

在swift里面存在着两种引用计数,一种是强引用包含的是strong RC + unowned RC + flags;一种是弱引用包含的是strong RC + unowned RC + weak RC + flags

来一下HeapObjectSideTableEntry的源码:

HeapObjectSideTableEntry SideTableRefCounts image.png

可以发现弱引用与强引用共用一个模板类RefCounts 其实就是它:RefCountBitsT
来看看引用计数操作模板类RefCountBitsT通过弱引用的方式创建的源码:

weak的RefCountBitsT初始化

weak方式创建RefCountBitsT,首先是将散列表右移3位,然后将62位和63位标记为1。

通过逆反操作验证上面源码分析的的准确性:

weak引用后refCounts那8位返回的是一个内存地址(而不是引用计数64位域)

image.png

1.我们要还原,先将62位63位的标志位设置为0得到的结果:

还原62位63位

2.还要将这个结果向左移动3位:

左移动3位还原操作

将这个结果在控制台上格式化输出:

weak引用一个对象本质上就是创建一个散列表

2.2 unownedweak

上面介绍了强引用计数和无主引用计数是通过位移的方式,存储在这64位的信息当中的。

  • unowned不允许被引用的对象有nil的可能,无需要新建/维护散列表,直接可以通过64位的位域操作即可进行引用计数。
  • weak它所引用的对象允许为nil,它需要新建/维护散列表

共同点:

  • 引用对象的自动引用计数都不会加1,不会造成对引用对象的强引用。

解决block引用计数问题:

class WJTeacher {
    var age: Int = 18
    var name: String = "Steven"
    
    var closure: (()->())?
    
    deinit {
        print("WJTeacher 消失")
    }
}

func test() {
    let t = WJTeacher()
    var closure = {
        t.age = 19
    }
    t.closure = closure
    closure()
}

此时循环引用:t->closure->t
修改后:

class WJTeacher {
    var age: Int = 18
    var name: String = "Steven"
    
    var closure: (()->())?
    
    deinit {
        print("WJTeacher 消失")
    }
}

func test() {
    let t = WJTeacher()
    var closure = { [weak t] in
        t?.age = 19
    }
    t.closure = closure
    closure()
}

// 或者
/**

func test() {
    let t = WJTeacher()
    var closure = { [unowned t] in
        t.age = 19
    }
    t.closure = closure
    closure()
}
*/

相关文章

网友评论

    本文标题:Swift进阶-内存管理

    本文链接:https://www.haomeiwen.com/subject/swqmcrtx.html