Conceptual Overview

Like most user interface libraries, Matcha works on a tree of views which correspond roughly to elements on the screen. When the user launches the app, Matcha calls build() on the root view, which returns the view’s properties and children. This gets performed recursively on the children, generating a view hierarchy.

Updates

Views all implement the Notifier interface, which lets them signal to the framework when they have updated and would like to be redisplayed. If this happens, the framework marks the view and all of its children as out of date, and on the next display cycle, build() is called again on the flagged views, updating sections of the view hierarchy. It is important to note that signaling through the Notifier interface is the only way to update a view. Like React, views should not keep references to their children or parents and modify them directly.

Diffing and Reconciliation

When a view gets rebuilt, it creates and returns a new list of children. This keeps the function declarative and easier to reason about. However, the previous child may have internal state that the user might not want reset. This could be the location of their text cursor or the current scroll position. To get around this Matcha tries to match elements from the updated list of children to the previous one. When comparing views, Matcha checks two attributes, the view’s type and the return value of the ViewKey() method. These must be equal for the views to be considered matching. If there are multiple views that match, the index of the view in the array will also be used.

If two Views are found to match, all public struct fields will be copied from the newer view on to the previous view, and the previous view will be reused. This means that at the time build() is called on a view, any of its public fields may have changed, and views must take this into account.

Layout and Paint

In addition to the view hierachy, there is also a tree of Layouter and Painter that mirrors the view tree. These perform view positioning and styling through the LayoutChild() and PaintStyle() methods, respectively.

Similar to views, the two interfaces also implement Notifier and can independently mark themselves as needing update. By having separate trees, we can animate a view’s position and background color, without making expensive calls on the view itself. However there is a dependency between the trees. Any time the view updates the corresponding Layouter and Painter will automatically update. And anytime the Layouter updates, the Painter will also update.

Bridge

Once Matcha has these trees, it serializes them into a Protobuf and sends it to the iOS side. This is deserialized and rendered using UIViews and UIViewControllers as needed. The gomatcha.io/bridge package handles the interface between Objective-C and Go. Go values become MatchaGoValue in Objective-C, and Objective-C values become *bridge.Value in Go. Methods and functions can then be called on these objects using reflection.

Threading

Matcha is for the most part not a multi-threaded framework. All exposed callbacks and APIs will be called on the iOS main thread. Correspondingly if a view/layouter/painter does work on a separate goroutine and needs to read or modify itself, it must lock the matcha.MainLocker() mutex.