vmalakhovskiy / data-driven-vc

Swift Data-Driven UI examples
38 stars 4 forks source link

Just question #1 #1

Open Koshub opened 5 years ago

Koshub commented 5 years ago

Just saw https://www.youtube.com/watch?v=i3EBlRjXCvY I like it. Efficient reuse of ReactJS patterns (or whatever). I have one question. What if rather then:

///....
    var props: Props = .initial {
        didSet {
            view.setNeedsLayout()
        }
    }
//....
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        label.text = props.title
        leftButton.isEnabled = props.onLeft != nil
        centerButton.isEnabled = props.onCenter != nil
        rightButton.isEnabled = props.onRight != nil
    }

do next:

    var props: Props = .initial {
        didSet { apply(props) }
    }
//...
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        apply(props)
    }
//...
    private func apply(_ props: Props)  {
        label.text = props.title
        leftButton.isEnabled = props.onLeft != nil
        centerButton.isEnabled = props.onCenter != nil
        rightButton.isEnabled = props.onRight != nil
    }

I think it is more UIKit-friendly way. Or not?

vmalakhovskiy commented 4 years ago

@Koshub thanks for reaching out!

I think it is more UIKit-friendly way. Or not? Short answer - No.


There is major optimization there.

var props: Props = .initial {
    didSet { view.setNeedsLayout() }
}

We're using that approach in couple with Redux architecture, which assumes that every state update should render new props. Because there may be a lot of these props - we don't want to force screen update, setNeedsLayout just notifying UIKit that it should redraw view next time. UIKit decides when to do that. That's why such approach likely to be more UIKit-friendly.

Koshub commented 4 years ago

@vmalakhovskiy Thank you very much for response. I completely understand your point of view and it is a comfortable way to update UI using this approach in 99% of the cases. Especially with a lots of frequently state updates. Allowing UIKit to decide when to render props is a good idea but it doesn't cover the case when you have a state update but not props. In this case it will ask to check layout when it is not necessary. And if we are considering described optimization (which is why this all makes some sense) - forcing the layout check even if the props has not been changed will cost some CPU time on main thread. The next sample on my iPhone will take more than 3% CPU main thread time to force layout engine to check the layout. Although this is the edge case example it clearly displays the optimization trade offs topic we are trying to achieve with view.setNeedsLayout.

final class ViewController: UIViewController {

    @IBOutlet private weak var titleLabel: UILabel?

    struct Props: Equatable {
        var title = "0"
    }

    var props = Props() {
        didSet {
//             updateView(with: props) // 1-2% CPU load on my iPhone
             view.setNeedsLayout() // 3-4% CPU load on my iPhone
        }
    }

    private var timer: Timer?

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in
            self.props = Props()
        }
    }

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        updateView(with: props)
    }

    private func updateView(with props: Props) {
        titleLabel?.text = props.title
    }
}

I think one needs to compare props at least so in case they are the same it will not proceed with layout check.

    var props = Props() {
        didSet {
            if oldValue != props {
                view.setNeedsLayout()
            }
        }
    }

I hope I do not bother you too much, since we all are trying to find some silver bullets )) Anyway thank you again for sharing your ideas with the community and for this response. Kind regards.