介绍
写这篇文章也是前几天在开发一个界面的时候突发奇想。我们经常会遇到视图懒加载的场景,例如一个 UIViewController 包含多个子 UIViewController、一个 UIView 包含多个子 UIView,但是这些子UIView/UIViewController并不会同时显示。通常会使用显示/隐藏逻辑来控制,同时为了性能考虑希望只在真正使用的时候再初始化。但是日常使用的处理方式都比较繁琐或存在不足,所以希望抽象一个简单的语法糖工具去实现这个能力。
实例
以这个商品楼层为例,
已抢光/冷藏图标/属性/标签都有可能不显示,希望在真实显示的时候再进行初始化。
常见实现方式
使用可选值
class UITableView {
private var iconImageView: UIImageView?
func bind(props: CellProps) {
// 显示
if let imageIconURL = props.imageIconURL {
// 判断是否初始化
if iconImageView == nil {
let imageView = UIImageView()
contentView.addSubview(imageView)
imageView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(5)
make.left.equalToSuperview().offset(5)
make.width.height.equalTo(20)
}
self.iconImageView = imageView
}
iconImageView?.sd_setImage(url: imageIconURL)
iconImageView?.isHidden = false
} else { // 隐藏
iconImageView?.isHidden = true
}
}
}
存在的问题
代码复杂度提高- 将视图初始化逻辑放在了绑定值的地方,增加了更新视图方法的复杂度。可选值- 属性使用可选值需要进行可选值解包不方便使用。
使用 Lazy 属性
class UITableView {
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView()
contentView.addSubview(imageView)
imageView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(5)
make.left.equalToSuperview().offset(5)
make.width.height.equalTo(20)
}
return imageView
}()
func bind(props: CellProps) {
// 显示
if let imageIconURL = props.imageIconURL {
iconImageView.sd_setImage(url: imageIconURL)
iconImageView.isHidden = false
} else { // 隐藏
iconImageView.isHidden = true // 这里会被初始化
}
}
}
存在的问题
无法避免初始化- 无法避免在不需要显示的时候初始化视图,例如对于视图设置isHidden=true
LazyView 实现
最后实现了一个简单的LazyView,它可以实现延迟初始化避免在isHidden这类场景的时候初始化,同时也可以比较优雅去处理这种场景。
LazyView 代码实现
public class LazyView<T> {
private var block: (() -> T)?
public var value: T! {
if wrappedValue == nil {
wrappedValue = block!()
block = nil
}
return wrappedValue!
}
public private(set) var wrappedValue: T?
public init(_ block: @escaping () -> T) {
self.block = block
}
}
extension LazyView {
@inlinable
func show(_ block: (T) -> Void) {
block(value)
}
@inlinable
func hide(_ block: (T) -> Void) {
if let wrappedValue = wrappedValue {
block(wrappedValue)
}
}
}
提示:使用
@inlinable提示编译器进行方法内联优化。
LazyView 改造后代码
class UITableView {
private lazy var iconImageView: LazyView<UIImageView> = LazyView { [unowned self] in
let imageView = UIImageView()
contentView.addSubview(imageView)
imageView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(5)
make.left.equalToSuperview().offset(5)
make.width.height.equalTo(20)
}
return imageView
}()
func bind(props: CellProps) {
// 显示
if let imageIconURL = props.imageIconURL {
iconImageView.show {
$0.sd_setImage(url: imageIconURL, options: [])
}
} else {
// 隐藏
iconImageView.hide { $0.isHidden = true }
}
}
}
总结
最初考虑过使用Property Wrapper,但是Property Wrapper不能支持lazy属性。只是提供了一种思路,设计的不够完善,例如需要[unowned self]解决循环引用的问题,后续希望可以优化。也希望大家可以分享一些更优秀的实现。