今天是 WWDC 23 的第三天,依旧是在疯狂刷讲座视频。今天在一个讲座中说到了一个 API 引起了我的留意,算是处理了咱们在编写视图代码时的一个痛点,尤其是对强迫症而言非常友好。
编写视图代码时的问题
曩昔咱们在写 UIViewControlller
的代码时,总是会把一些 View 作为 UIViewControlller
的特点界说在头部。此刻咱们就遇到了一个问题。关于部分简单的 View 可以直接在界说特点的时分初始化,还可以把特点符号成 let 。强迫症标明非常满意。
但有的时分,View 的初始化依赖于其他配置或者参数,没办法直接在界说时完结初始化。遇到这种状况,咱们一般有两种办法处理:
- 运用
lazy
关键字推迟初始化,此刻上下文可以拿到一切参数。(如dateLabel3
) - 运用 Optional 符号参数并赋初始值
nil
,之后再初始化。(如dateLabel2
)
class DateViewController: UIViewController {
private let dateLabel1 = UILabel()
private var dateLabel2: UILabel? = nil
private var lazy dateLabel3: UILabel = {
return UILabel()
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dateLabel2 = UILabel(frame: self.view.bounds)
}
}
实际上,运用 lazy
关键字是很多人的选择。乃至很多人会将一切 View 的初始化作业都用 lazy
来完成。一来可以将一切 View 相关的初始化和配置代码都放在一起,二来一切 View 也都支持了按需加载,关于部分后呈现的 View 会有功能优势。
我个人对
lazy
不是那么喜爱,由于一方面它会导致很多逻辑代码堆积在文件头部,不利于查看。另一方面绝大部分 View 其实都是跟着 ViewController 同步加载的,懒加载的含义不大,而且关于后续不会更改的 View 运用 var 关键字也很奇怪。
运用 Optional 的问题是之后一切用到该特点的时分都需求判空,这样的代码非常不高雅。
@ViewLoading
不论怎么样,Apple 推出了一个新的特点包装器 @ViewLoading
,可以协助咱们处理上面说到的第二种状况。当运用 @ViewLoading
符号参数时,参数可以被界说成 var 同时没有初始值,此刻编译器不会报错,而是以为该参数在此刻没有值但在稍后调用时一定会有值(类似于在类型后加了强解包!)。当调用这个参数时,假如体系发现这个值此刻是 nil,会先现调用 viewDidLoad ,然后再调用这个参数。这样就能保证这个参数在调用的时分一定有值。
理解上面这个原理的人应该发现了,要真实满意这个条件的话,一定要在 viewDidLoad 中初始化对应参数,由于体系默认被符号的特点在 viewDidLoad 中完结了初始化。假如没有这么做,履行到调用参数的地方时仍是会报错。
class DateViewController: UIViewController {
@ViewLoading private var dateLabel: UILabel
var date: Date? {
didSet {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .full
let dateString = dateFormatter.string(from: self.date ?? Date())
// 假如履行下面这段代码时发现 dateLabel 为 nil,
// 会先履行 viewDidLoad ,然后再履行下面的代码
self.dateLabel.text = dateString
}
}
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: self.view.bounds)
self.view.addSubview(label)
self.dateLabel = label
}
}
那么什么条件下会呈现 viewDidLoad 没有走但直接调用参数的状况呢?官方给了一个比如。先初始化了一个 ViewController ,在没有 push 或 present 的状况下间接调用了 dateLabel
。
let dateViewController = DateViewController()
dateViewController.date = Date()
@ViewLoading
不只可以协助咱们优化视图编码,关于 StoryBoard 的参数也适用(乃至我觉得这个新特性便是为了 IBOutlet
而生的)。以往咱们从 StoryBoard 或 XIB 拖出来的参数都是强解包的。这是说的通的,由于这些参数在界说时还没有被初始化,但在运用这些参数时(viewDidLoad
)必定现已初始化好了。运用强解包也是为了方便咱们调用,不然一切运用到参数的地方都得判别一下是否为空。但强解包是苹果非常不主张的行为,曩昔这种写法是打苹果的脸。如今 @ViewLoading
呈现可以愈加高雅的处理这个代码层面的问题。
// old
@IBOutlet private var label: UILabel!
// now
@IBOutlet @ViewLoading private var dateLabel: UILabel
最终供给一个好消息和一个坏消息:
- 好消息是这个 API 同时供给了 UIKit 版 和 APPKit 版,macOS 也能用上。
- 坏消息是 iOS 16.4+ ,几乎是不可用的状态。。。
总结
假如熟悉 Android 的同学应该知道,@ViewLoading
和 Kotlin 的 LateInit
非常类似,LateInit
其实也是为了优化 Android XML 参数在代码层面的标明问题。而且 LateInit
的语义愈加易懂,标明修饰的参数现在没有被初始化,但保证会在稍后初始化。不同的是 @ViewLoading
把运用场景限定在了视图相关的内容上,而且增加了在调用参数前判别是否为空,为空的话主动调用 viewDidLoad 的逻辑。而 Kotlin 的 LateInit
则愈加通用。
相比起
@ViewLoading
, 我其实更想要lazy let
。