当 Swift 中的 lazy、weak 碰上 NSObject
2023-10-27 06:11:05
前言
Hi Coder,我是 CoderStar!
今天给大家介绍一个我遇到的小坑。过程大概是这样的,一个复用页面通过不同的入口进入,等返回时,有的正常,有的却出现了 Crash,log 信息如下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString weakObjectValue]: unrecognized selector sent to instance 0x7fff61b64cf0'
一开始看到这个问题,我是一脸懵的,啥情况?String
怎么会发送了 weakObjectValue
这个消息?通过断点调试发现,崩溃发生在以下代码:
class MyClass {
weak var delegate: AnyObject?
func doSomething() {
delegate?.doSomething() // 崩溃点
}
}
delegate
是一个 weak
修饰的属性,正常情况下,当 delegate
为 nil
时,就不会发送 doSomething
消息了,但是这里却崩溃了,这又是咋回事?
原因分析
经过一番排查,我发现问题出在 lazy
属性上。在我创建 MyClass
实例的时候,使用了 lazy
属性来初始化 delegate
:
class MyClass {
lazy var delegate: AnyObject? = {
return WeakWrapper(object: ... /* 这里省略了具体的初始化逻辑 */)
}()
func doSomething() {
delegate?.doSomething() // 崩溃点
}
}
WeakWrapper
是一个自定义的弱引用包装器,它可以保证在 delegate
为 nil
时,不会发送 doSomething
消息。
然而,问题就出在 lazy
属性上。lazy
属性的初始化是在第一次访问该属性时才进行的。而在我们这个例子中,delegate
属性第一次被访问是在调用 doSomething
方法的时候。但是此时,MyClass
实例已经释放了,导致 delegate
为 nil
,从而引发了崩溃。
解决方案
知道了问题的原因,解决方法也就很简单了,只需要将 lazy
属性改成普通属性即可:
class MyClass {
var delegate: AnyObject? = {
return WeakWrapper(object: ... /* 这里省略了具体的初始化逻辑 */)
}()
func doSomething() {
delegate?.doSomething() // 不再崩溃
}
}
这样,delegate
属性就会在 MyClass
实例创建的时候就初始化了,就不会出现 delegate
为 nil
的情况了。
总结
需要注意的是,lazy
属性在使用时是有条件限制的,不能用于修饰 weak
属性,否则会引发崩溃。在使用 lazy
属性时,要确保在第一次访问该属性之前,其所属的实例不会被释放。