【发布时间】:2021-02-10 16:26:46
【问题描述】:
通过 swift 编译器优化,隐式展开的可选变量不会在整个范围内存活,而是在使用后立即释放。
这是我的环境:
swift --version
输出
Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28)
Target: x86_64-apple-darwin20.2.0
Xcode 版本为Version 12.3 (12C33)
考虑这个显示问题的最基本示例:
final class SomeClass {
func doSth() {}
deinit {
print("deinit")
}
}
func do() {
var someObject: SomeClass! = SomeClass()
someObject.doSth()
print("done")
}
这应该输出
done
deinit
但是,在发布版本(启用 Swift 代码优化“-O”)中,它会以相反的方式打印:
deinit
done
这仅适用于var someObject: SomeClass!。
该代码的以下更改 ALL 正确输出(意味着在离开函数范围时释放对象):
将 var 定义为常量:
func doSthSpecial() {
let someObject: SomeClass! = SomeClass()
someObject.doSth()
print("done")
}
将 var 明确定义为可选:
func doSthSpecial() {
var someObject: SomeClass? = SomeClass()
someObject.doSth()
print("done")
}
访问就像一个可选的:
func doSthSpecial() {
var someObject: SomeClass! = SomeClass()
someObject?.doSth()
print("done")
}
这最后三个实现全部输出
done
deinit
按这个顺序。
不知怎的,这让我无言以对????♂️。 我理解这种优化,这是有道理的。但作为程序员,我们习惯于函数内部的局部变量在离开作用域之前一直可用。
我在这里遇到的问题是关于存储在这种隐式展开的可选变量中的对象的生命周期。如果我的代码取决于此对象的生命周期(例如 RxSwift 及其 DisposeBags 的情况),那么我会遇到奇怪的行为,意外的行为!
我可以认为这是 Swift 中的一个错误,但你怎么看?有bug还是没有bug?
这是一个更真实的场景,RxSwift,您可以在其中使用这样的构造:
import UIKit
import RxSwift
final class SomeClass {
func doSth() {}
deinit {
print("deinit")
}
}
class ViewController: UIViewController {
let providePassword = PublishSubject<String>()
lazy var askForPassword: Observable<String> = {
return Observable.create { observer in
_ = self.providePassword.subscribe(observer)
return Disposables.create()
}
.debug(">>> ask for password signal")
}()
private func performAsyncSyncTask() {
DispatchQueue.global().async {
var disposeBag: DisposeBag! = DisposeBag()
let sema = DispatchSemaphore(value: 0)
self.askForPassword
.subscribe(onNext: { pw in
print(pw)
sema.signal()
})
.disposed(by: disposeBag)
_ = sema.wait(timeout: DispatchTime.distantFuture)
disposeBag = nil
}
}
@IBAction func startAskPassword(sender: AnyObject) {
self.performAsyncSyncTask()
}
@IBAction func sendPassword(sender: AnyObject) {
self.providePassword.on(.next("hardcoded pw"))
}
}
这里的问题是:在执行self.performAsyncSyncTask() 时,它订阅了askForPassword,但是因为在优化的构建中,隐式展开的可选变量在.disposed(by: disposeBag) 中使用后立即被清除。
这会在订阅后立即销毁信号。
【问题讨论】:
标签: swift variables scope rx-swift