阅读 311

如何更好处理视图懒加载

介绍

写这篇文章也是前几天在开发一个界面的时候突发奇想。我们经常会遇到视图懒加载的场景,例如一个 UIViewController 包含多个子 UIViewController、一个 UIView 包含多个子 UIView,但是这些子UIView/UIViewController并不会同时显示。通常会使用显示/隐藏逻辑来控制,同时为了性能考虑希望只在真正使用的时候再初始化。但是日常使用的处理方式都比较繁琐或存在不足,所以希望抽象一个简单的语法糖工具去实现这个能力。

实例

截屏2021-07-23 20.36.09.png 以这个商品楼层为例,已抢光/冷藏图标/属性/标签都有可能不显示,希望在真实显示的时候再进行初始化。

常见实现方式

使用可选值

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]解决循环引用的问题,后续希望可以优化。也希望大家可以分享一些更优秀的实现。

文章分类
iOS
文章标签