# YEO iOS Architecture This document explains some of the core architectural designs & patterns in version 2.0.0+ of the YEO iOS client. ## Overview With v2.0.0, we start working with a hybrid MVVM/Clean model. This pattern consists of the following components: ### Models A `Model` is a dumb entity. Where possible, models should be immutable, and should not do work. I like to use a `struct` for models as they enforce immutability. Example: `Message.swift` ### Signals A Signal is simply an observable property, usually exposed by a ViewModel to a View. ### View Models A `ViewModel` is the interface between the View (most often the controller in iOS) and the Model. Anything the View needs to convey to the model layer should go through the associated ViewModel. A ViewModel will fire Services and handle their success/failure. ViewModels also use Signals to communicate state changes. Example: `ChatHistoryViewModel.swift` ### Promises A `Promise` is a wrapper around some task that is expected to run asynchronously. Examples include network requests, database queries, decryption, etc. Example: `DecryptionService.swift` ### Services A `Service` is a stateless worker responsible for fetching resources (network, database, etc), usually returning a Promise to complete the task. Because Services are generally stateless, they can be "injected" by simply adopting the related `XXXInjectedProtocol` protocol. Where a service is long-lived or, rarely, stateful, we keep a singleton instance on the `AppInjector` object. Example: `SyncKeysService.swift`, `LocationMonitorService.swift` ### Views A `View` is usually a UIViewController subclass. It is repsonsible for updating the screen based on messages received via either an associated ViewModel, or some other trigger (`NotificationCenter` etc). A `View` can also be a UIView subclass, which may also own ViewModels. ### LiveData This pattern is taken from Android. A `LiveDataViewModel<T>` is a ViewModel that holds some state `T` and propagates updates to that state via an associated Signal property (named `valueSignal`). An example of this might be a data source for a table view. The View would own the table view and a `LiveDataViewModel<T>`, where `T` is the data type of each row in the table. The ViewModel is assigned to the table view's datasource and delegate, and the View subscribes to the ViewModel's value signal. The View can then reload the table view on changes to the LiveData state. ### Networking The protocol `ApiService` describes methods for performing GET, POST & DELETE requests, and multi-part form uploads. YEO 2.0.0 provides a default implementation, `AppApiService`, that calls out to Alamofire to perform the actual networking task. The response of which is assumed to be a JSON object and is deserialized into a given response type (using Unbox, and ObjectMapper in the old Cleveroad code), typically a Model or collection of models. ### Coordinators A `Coordinator` is an abstraction around a transition between two Views (i.e. controllers). They are similar to Segues but do away with the coupling between the UISegue & UIViewController, making for a much cleaner and more testable way of injecting dependencies into the controller (which is otherwise tricky/hacky due to the way initialisers work for controllers). ### Modules A `Module` is an interface between the View and the Coordinator. A Module is responsible for constructing and "starting" a Coordinator on-demand from the View. A Module provides a logical grouping of related transitions, which might include, for example, presenting a modal child View or an Alert. ### Persistence The app stores local data associated with messages sent by the active user (i.e. doesn't store anything about received messages as this is a breach of privacy). **NOTE**: This will change when we implement "Local Storage". v1.x used Realm, but this is clunky and heavy so, starting in v2.0.0, we move over to a simple SQLite db on the device. **NOTE**: This is not set in stone, CoreData may be a better option going forward. ### Message Parsing Because of our server-side limitations at the time of the chat rewrite, we had to come up with a novel way of encoding meta-data inside a message body. What we came up with was the `MessageHeader` model. #### MessageOption `MessageOption` is an enum that represents some data part of a message. That could be some information that the user sees (e.g. geofence, giphy link, etc), or a directive for the message bubble rendering layer that controls how some part of a message is displayed to the user (e.g. secure/open channel, etc). #### MessageHeader A `MessageHeader` contains the raw text of a message, as a parsed, user-facing string, and a collection of `MessageOption` enums. The raw text is passed through a `MessageDecoder` instance, which produces the other parts of a the MessageHeader. #### MessageDecoder The `MessageDecoder` class is the interface between the UI layer and the message parsing layer. A MessageDecoder takes a string as its input and returns a fully parsed MessageHeader. This input string will contain at least a user-defined message, as well as an optional, custom formatted header. The header looks like: ```xml <yeo>@mesageOption1=value1;@messageOption2=value2;</yeo> ``` The MessageEncoder currently supports the following types of values: - Bool (expressed as a 1 or 0 literal) `<yeo>@boolProperty=1</yeo>` - Number (expressed as a either an Int or Double) `<yeo>@numericProperty=2.1</yeo>` - Giphy (an alphanumeric giphy identifier) `<yeo>@giphy=abcde1234ZXY123</yeo>` - Geofence (a triple of Numbers (lat, lon, rad) `<yeo>@geofence=(1.2,2.9,-57.4))</yeo>` - Location (a tuple of Numbers (lat, lon) `<yeo>@location=(1.2,2.9)</yeo>` Here is an example of a complete message: ``` <yeo>@channel=1;@giphy=abc123def456;@location=(1.1,2.2)</yeo>Hello, this is some text ``` But can be extended to supported arbitrary message options by adding new `MessageDataValueRule` parse rules. #### DynamicMessageComponent & MessageBubbleView The view that displays an individual message in a chat is called a `MessageBubbleView`. This view is composed of an arbitrary number of subviews called `DynamicMessageComponent`. These components are responsible for rendering themselves, which might be as simple a task as drawing a text label, or as complex as fetching a video attachment and displaying a thumbnail. A component is also responsible for calculating its size. Once a message is parsed, it knows which components it needs to draw inside its bubble. Once all components have finished rendering, the `MessageBubbleView` combines them and calculates a total size and finally assembles itself. Separating the components in this way allows us to mix and match any subset of component types in a single bubble, without the bubble knowing anything special about specific components. (See: `MessageBubbleView.swift`, and `DynamicMessageComponent.swift` and its various subclasses).