introduce

Writing this article is also a few days ago in the development of an interface when a whim. We often encounter lazy view loading scenarios, such as a UIViewController containing multiple child UIViewControllers and a UIView containing multiple child UIViews, but these child UIViews /UIViewController will not be displayed at the same time. You usually use show/hide logic for control, and you want to initialize only when you actually use it for performance reasons. However, the daily use of processing methods are tedious or inadequate, so I hope to abstract a simple syntax sugar tool to achieve this ability.

The instance

Take this merchandise floor for example,Has gone/Cold storage icon/attribute/The labelMay not display, you want to display the actual initialization.

Common implementations

Use optional values

class UITableView {
    
    private var iconImageView: UIImageView?
    
    func bind(props: CellProps) {
        / / show
        if let imageIconURL = props.imageIconURL {
            // Determine whether to initialize
            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 { / / hide
            iconImageView?.isHidden = true}}}Copy the code

Existing problems

  • Improved code complexityView initializationThe logic is placed in place of the binding values, increasing the complexity of updating view methods.
  • An optional value– Attribute use optional value Unpack optional value is not convenient to use.

Using the Lazy attribute

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) {
        / / show
        if let imageIconURL = props.imageIconURL {
            iconImageView.sd_setImage(url: imageIconURL)
            iconImageView.isHidden = false
        } else { / / hide
            iconImageView.isHidden = true // This will be initialized}}}Copy the code

Existing problems

  • Initialization cannot be avoided– It is impossible to avoid initializing views when they are not needed, such as for view SettingsisHidden=true

LazyView implementation

The result is a simple LazyView that implements lazy initialization to avoid initialization in scenarios like isHidden, while also handling such scenarios elegantly.

LazyView code implementation

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)
        }
    }
}
Copy the code

Tip: Use @inlinable to prompt the compiler for method inlining optimization.

LazyView code after transformation

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) {
        / / show
        if let imageIconURL = props.imageIconURL {
            iconImageView.show {
                $0.sd_setImage(url: imageIconURL, options: [])
            }
        } else {
            / / hide
            iconImageView.hide { $0.isHidden = true}}}}Copy the code

conclusion

Property Wrapper was initially considered, but the Property Wrapper cannot support lazy properties. It only provides an idea, but the design is not perfect enough. For example, [unowned self] needs to solve the problem of circular reference. I hope it can be optimized later. I also hope you can share some better implementations.