Skip to content

Instantly share code, notes, and snippets.

@eonil
Last active July 7, 2020 03:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eonil/4285ecf20d901fce851478f36bbc92aa to your computer and use it in GitHub Desktop.
Save eonil/4285ecf20d901fce851478f36bbc92aa to your computer and use it in GitHub Desktop.
An Opinion about How to Implement UI Apps for Apple Frameworks

An Opinion about How to Implement UI Apps for Apple Frameworks

Eonil, 2020.

This explains how I organize code of interactive programs.

Overview

  • As a one word, this approach is Mostly REPLized, a little more functional-styled MVC.

Opinions

  • The simplest and easiest interactive app implementation strategy is REPL. (e.g. Elm, Redux)
  • “eval” is a function which converts input (state + user control) to output (state + rendering)
  • REPL is a sort of origin of all interactive apps.
  • REPL is easy to design, implement and test.
  • REPL is oneway data flow.
  • But Apple frameworks are all designed in MVC. (including SwiftUI due to data binding)
  • Apple frameworks do not guarantee oneway data flow.
    • Although, they are slowly moving to REPL…
    • Bidirectional data flow can remain and involve at any time.
    • Strict REPL structure would make things painful and difficult to maintain.
  • Therefore, we must keep MVC structure at the base.
  • But construct details in REPL manner on MVC base.

Functional-Style

  • Instead of calling command methods of model, sends command packed in an immutable value.
  • Instead of calling rendering methods of views, pass immutable state snapshot packed in a value.
  • Some command value can be executed at controller level to support MVC.
    • This has to be minimized. Use this only absolutely needed.
  • Record versions of each state to avoid full diff cost if possible.
  • Perform diff only if needed. See “Assumptions for Performance” section about diff performance.

Inversion of Control

  • Model is the business logic.
  • Therefore, model is isolated and independent.
    • View and controller are only implementation details.
    • Model don’t depend on them at all.
    • Model shouldn’t even know their existence. (isolated model)
  • Model is a collection of discrete states and defines clear input and output.
  • View is an implementation detail.
  • Every isolated component should be abstracted with interfaces.
  • Expose only minimum interface that are absolutely required.
  • Hide all the details that are not necessary to public users.
  • Large apps are complex. Reducing overall complexity is very important.

REPL Model

  • Model is fully REPL based. (e.g. Elm, Redux)
  • There’s no need for anything else.
  • Model can be divided into several subsystems.
    • Each subsystem can work like a smaller model. (sub-REPL)
  • Model accepts and queues action commands and executes them serially.
  • Model updates model states and sends appropriate commands to subsystem components.
  • Model processes outputs of subsystem components and update its state again.
  • Model announces updates in state to public at consistent moment.
  • Owner of model (controller) should scan model state and pass it to view.

View Depends on Model

  • Inversion-of-Control principal wants view to have dedicated, isolated and abstracted interfaces.
  • In other words, MVVM.
  • But in many cases, view interfaces are exact replication of model states.
  • In that case, why should I copy same interface?
  • We don’t need to replicate them. Just use model state types as-is.
  • View can depend on model.

But what about reusability?

  • UI of a UI app is dedicated only for single app.
  • UI is not re-usable. Only a few of low level components of UI are re-usable.
  • Such re-usable low level components need to have isolated and dedicated interface.
    • Such components are mostly already defined in platform UI frameworks.
    • You usually don’t need to write reusable view component.
      • Unless you are a platform vendor like Apple or Microsoft…
    • Even in this case, re-usable components are supposed to be defined at design level.
  • Composition of such low level components are usually coupled with specific model features.
  • It’s natural to use model state types as-is.
  • Sticking to reusability seriously limits flexibility of UI design.
  • Reduced flexibility in UI design produces lower quality UX.

What about testability?

  • In this case, model output becomes view input.
    • In MVVM term, a view-model.
  • So, testing model output is testing view-input.

How about testing of view internals?

  • That should be done at view level without involving model or controller.
  • Testing of view state (model output, view input or view-model) to final rendering would require manual test.
  • In most cases, there’ no good way to test highly interactive custom UI.

Shape of Model

  • Shape of model is bound to shape of the app.
    • Don’t forget that model of a UI app is mostly designed to represent UI.

Single Global State Rendering

  • No one can predict business requirements.
  • Any part of an app must be able to access and render any part of model state.
  • Therefore we need to pass whole model state to each screens.
  • Therefore we always pass whole model state snapshot to view.

Assumptions for Performance

  • GUI apps are designed for human users.
  • Humans cannot handle large amount of data at once.
  • About <100 would be the maximum. (Or a few hundreds)
  • Listing >100 items in GUI is meaningless.
  • You are supposed to render only small portion of total data.
  • Therefore, such cases should be prevented at design stage.
  • Therefore, most collection-like data for GUI is limited 100 elements.
  • This is O(100), and we can consider copy/diff as O(1) in most mobile devices.
  • We can deal with exceptional cases using several tricks.
    • B-Tree, version/op recording.
    • But this needs far more efforts to solve.
    • Anyway, exceptional cases are rare.
    • Take benefit of simplification in most cases.
  • Design becomes more important.

Anyway we still can have large amount of data. For example, complex graph points.

  • Usually, such large data is provided for snapshot based rendering.
  • Such cases usually does not require diff. Just render them all as a snapshot.
    • And visibility cullig if needed.
  • Logically, this is single item with large attached data.
  • Well, some data can be divided into several pieces, but number of pieces will be in human-recognizable range.

Migration to REPL

  • If we can sure that depending frameworks are all MVC-free, we can easily convert program into REPL.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment