最近项目需要用到链表,随手写了一个简单的实现,未想到遇到了一个奇怪的栈溢出的问题。 先上链表的实现,非常的简单,会有什么问题呢?
open class SwiftDataQueue {
var identifier: Int
var data: UnsafeMutableRawPointer
var next: SwiftDataQueue?
init(size: Int, identifier id: Int) {
data = malloc(size)
identifier = id
next = nil
}
deinit {
free(data)
}
}
业务逻辑有很多操作这个队列的地方,偶然的情况下就会出现一个崩溃的情况,崩溃的堆栈是这样的:
#174407 0x00007fff2ff7da30 in _swift_release_dealloc ()
#174408 0x00000001021be677 in outlined destroy of SwiftDataQueue? ()
#174409 0x00000001021be645 in SwiftDataQueue.deinit at SwiftDataQueue.swift:23
#174410 0x00000001021be6a9 in SwiftDataQueue.__deallocating_deinit ()
#174411 0x00007fff2ff7da30 in _swift_release_dealloc ()
#174412 0x00000001021be677 in outlined destroy of SwiftDataQueue? ()
#174413 0x00000001021be645 in SwiftDataQueue.deinit at SwiftDataQueue.swift:23
#174414 0x00000001021be6a9 in SwiftDataQueue.__deallocating_deinit ()
#174415 0x00007fff2ff7da30 in _swift_release_dealloc ()
#174416 0x00000001021be9a1 in static AudioPlayer.stop() at SwiftDataQueue.swift:37
看到这样的堆栈的,我的第一反应是死循环了么?是循环引用导致的么? 在反复检查代码逻辑之后,发现并不存在循环引用,理论上也不应该是循环引用导致死循环,因为,若是循环引用,应该不释放才是。
经过了一阵冷静的思考之后,结合stop时候做的操作是释放链表头,有点怀疑是deinit的递归导致的。
于是,把问题的模型简化为如下逻辑:
public static func test() {
var head: SwiftDataQueue? = SwiftDataQueue(size: 1024, identifier: 0)
var current = head
for i in 1 ..< 102400 {
let next = SwiftDataQueue(size: 1024, identifier: i)
current?.next = next
current = next
}
head = nil
}
你看出来是什么问题了吗? 问题就在这里: head = nil 链表的head置为nil时,这个item的引用计数为0,调用deinit,deinit发现next的引用计数也要为0了,于是调用next的deinit,以此类推。。。
解决办法也很简单:
var current = head?.next
head = nil
while current != nil {
let next = current?.next
current = next
}
更近一步的思考:既然这里存在大量的deinit,会不会导致析构耗时太多卡到主线程了呢?如果卡到主线程可以怎么优化呢?