UDF (Unidirectional Data Flow) is a library based on Unidirectional Data Flow pattern.

Overview

Swift Package Manager compatible

UDF

UDF

UDF (Unidirectional Data Flow) is a library based on Unidirectional Data Flow pattern. It lets you build maintainable, testable, and scalable apps.

Unidirectional Data Flow Design Pattern

A unidirectional data flow is a design pattern where a state (data) flows down and events (actions) flow up. It's important that UI never edits or sends back data. That's why UI is usually provided with immutable data. It allows having a single source of truth for a whole app and effectively separates domain logic from UI.

UDF

Unidirectional Data Flow design pattern has been popular for a long time in the web development. Now it's time for the mobile development. Having started from multi-platform solutions like React Native and Flutter, Unidirectional Data Flow now becomes a part of native. SwiftUI for Swift and Jetpack Compose for Kotlin are implemented based on ideas of UDF. That's why we in inDriver decided to develop our own UDF library for our purposes.

Advantages

Here are the main advantages of this UDF implementation:

  • Testable. All domain logic implements in pure functions, so it's easy to unit test it. All UI depends only on provided data, so it's easy to configure and cover by snapshot tests.
  • Scalable and Reusable. Low Coupling and High Cohesion are examples of the basic principles of good software design. UDF implements such principles in practice. It allows to decouple UI and Domain, create reusable features, and scale business logic in a convenient way.
  • Easy working with concurrency. The UDF obviously doesn't solve all potential concurrent problems. But it alleviates working with concurrency in regular cases. State updating always runs on separate serial queue. It guarantees the consistency of a state after any changes. For UI or an async task you can use ViewComponent or ServiceComponent protocols respectively. They will subscribe your components on main or background thread so you can concentrate on business tasks rather than concurrency.
  • Free of FRP frameworks. We decided not to use functional reactive frameworks in our library. Instead, we provided it with a convenient way for subscribing to state updates, so in most cases, you don't even need to know how it works. The absence of FRP frameworks also means that you can't use the UDF with SwiftUI right now. But We plan to add Combine version of the UDF in near future. It will only affect the subscription process, so you will not have to rewrite your domain logic.

Differences from other popular UDF implementations:

RxFeedback - requires RxSwift

The Composable Architecture - iOS13+ only because of Combine

ReSwift - no instruments for modularization

Basic Usage

Let's imagine a simple counter app. It shows a counter label and two buttons "+" and "-" to increment and decrement the counter. Let's consider the stages of its creation.

Building Domain

Firstly we need to declare the state of the app:

struct AppState: Equatable {
   var counter = 0
}

State is all the data of an app. In our case, it's just an int counter. Next, we need to know when buttons are tapped:

enum CounterAction: Action {
 case decrementButtonTapped
 case incrementButtonTapped
}

We use an enum and an Action protocol for that. Action describes all of the actions that can occur in your app. Next, we need to update our state according to an action:

func counterReducer(state: inout AppState, action: Action) {
   guard let action = action as? CounterAction else { return }

   switch action {
   case .decrementButtonTapped:
       state.counter -= 1
   case .incrementButtonTapped:
       state.counter += 1
   }
}

Reducer is a pure function that updates a state. Now we need to glue it together:

let store = Store<AppState>(state: .init(), reducer: counterReducer)

Store combines all the above things together.

View Component

Let's take a look at UI now:

class CounterViewController: UIViewController, ViewComponent {

  typealias Props = Int

   @IBOutlet var counterLabel: UILabel!
   var disposer = Disposer()

   var props: Int = 0 {
       didSet {
           guard isViewLoaded else { return }
           counterLabel.text = "\(props)"
       }
   }
   
   @IBAction func decreaseButtonTapped(_ sender: UIButton) {
       store.dispatch(CounterAction.decrementButtonTapped)
   }

   @IBAction func increaseButtonTapped(_ sender: UIButton) {
       store.dispatch(CounterAction.incrementButtonTapped)
   }
}

CounterViewController implements ViewComponent protocol. It guarantees that a component receives a new state only if the state was changed and this process always occurs in the main thread. In CounterViewController we declare props property and update UI in its didSet. Now we have to connect our ViewController to the store:

let counterViewController = CounterViewController()
counterViewController.connect(to: store, state: \.counter)

Notice that we can choose witch part of the state we want to observe.

Modularization

Imagine that you would like to reuse your CounterViewController in another app. Or you have a much bigger reusable feature with many View Controllers. In this case, your AppState will look like this:

struct AppState: Equatable {
  var counter = 0
  var bigFeature = BigFeature()
}

Obviously you don't want your features to know about AppState. You can easily decouple them by scope:

let store = Store<AppState>(state: .init(), reducer: counterReducer)

connectCounter(to: store.scope(\.counter))
connectBigFeature(to: store.scope(\.bigFeature))

...

//Somewhere in Counter.framework
func connectCounter(to store: Store<Int>) {
  ...
  counterViewController.connect(to: store)
}

//Somewhere in BigFeature.framework
func connectCounter(to store: Store
   ) {
 
   ...
 firstViewController.
   connect(
   to: store, 
   state: \.
   first)
}

  

Now you can move your features to separate frameworks and use them wherever you want.

Installation

You can add the UDF to an Xcode project by adding it as a package dependency.

  1. Open File › Swift Packages › Add Package Dependency…
  2. Enter "https://github.com/inDriver/UDF"
  3. Choose the last version

Inspiration & Acknowledgments

The UDF idea is based on Alexey Demedetskiy ideas and MaximBazarov implementation of Unidirection Data Flow Pattern. Originally the UDF was a fork of Unicore.

The Composable Architecture inspired our implementation of a scope function and modularisation.

Also, we would like to thank all people that took part in development, testing, and using the UDF: Artem Lytkin, Ivan Dyagilev, Andrey Zamorshchikov, Dmitry Filippov, Eldar Adelshin, Anton Nochnoy, Denis Sidorenko, Ilya Kuznetsov and Viktor Gordienko.

If you have any questions or suggestions, please do not hesitate to contact Anton Goncharov or Yuri Trykov.

License

UDF is released under the Apache 2.0 license. See LICENSE for details.

Copyright 2021  Suol Innovations Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • promote ServiceComponent default queue quality-of-service class

    promote ServiceComponent default queue quality-of-service class

    Even though documentation states that default quality-of-service class should be .default

    class func global(qos: DispatchQoS.QoSClass= .default) -> DispatchQueue
    

    we get .unspecified with the default constructor.

      1> DispatchQueue.global().qos
    $R0: Dispatch.DispatchQoS = {
      qosClass = unspecified
      relativePriority = 0
    }
    

    Considering it is the lowest available priority, this may lead to performance issues and unwanted behavior.

    This patch promotes qos to .userInitiated, that should be sufficient for tasks that prevent the user from actively using your app.. (as doc states).

    opened by younke 0
  • promote ServiceComponent default queue quality-of-service class

    promote ServiceComponent default queue quality-of-service class

    Even though documentation states that default quality-of-service class should be .default

    class func global(qos: DispatchQoS.QoSClass= .default) -> DispatchQueue
    

    we get .unspecified with the default constructor.

      1> DispatchQueue.global().qos
    $R0: Dispatch.DispatchQoS = {
      qosClass = unspecified
      relativePriority = 0
    }
    

    Considering it is the lowest available priority, this may lead to performance issues and unwanted behavior.

    This patch promotes qos to .userInitiated, that should be sufficient for tasks that prevent the user from actively using your app.. (as doc states).

    opened by younke 0
  • Github Action Cloud CI build

    Github Action Cloud CI build

    Goal

    For automatic CI builds with cloud environment

    Changes

    • Setup latest macos image
    • Updated test simulator iOS version to 15.2 that available at macos image
    • Minor update for checkout version to v3
    HACKTOBERFEST-ACCEPTED 
    opened by trykovyura 0
  • promote ServiceComponent default queue quality-of-service class

    promote ServiceComponent default queue quality-of-service class

    Even though documentation states that default quality-of-service class should be .default

    class func global(qos: DispatchQoS.QoSClass= .default) -> DispatchQueue
    

    we get .unspecified with the default constructor.

      1> DispatchQueue.global().qos
    $R0: Dispatch.DispatchQoS = {
      qosClass = unspecified
      relativePriority = 0
    }
    

    Considering it is the lowest available priority, this may lead to performance issues and unwanted behavior.

    This patch promotes qos to .userInitiated, that should be sufficient for tasks that prevent the user from actively using your app.. (as doc states).

    HACKTOBERFEST-ACCEPTED 
    opened by younke 0
  • UDF + combine in store

    UDF + combine in store

    В данном pr переписала существующую модель для подписки компонентов на combine

    Есть несколько моментов:

    1. Вопрос, что делать с очередью для сервис компонент, оставить ли как есть или сделать main по умолчанию
    2. Оставила пока observe метод в store, он пока используется в ProxyStore, и в inDriver в UDFCommon тоже есть обращения
    opened by OlgaMiriutova 0
Releases(2.1.1)
Owner
inDriver
inDriver
Mvi Architecture for SwiftUI Apps. MVI is a unidirectional data flow architecture.

Mvi-SwiftUI If you like to read this on Medium , you can find it here MVI Architecture for SwiftUI Apps MVI Architecture Model-View-Intent (MVI) is a

null 12 Dec 7, 2022
Mahmoud-Abdelwahab 5 Nov 23, 2022
Giphy is an open source iOS app based on MVVM (Model–view–viewmodel) software architectural pattern.

Giphy(Search and display gifs) - MVVM Description Giphy is an open source iOS app based on MVVM (Model–view–viewmodel) software architectural pattern.

Tirupati Balan 8 Jun 14, 2022
Unidirectional reactive architecture using new Apple Combine framework

Unidirectional Reactive Architecture. This is a Combine implemetation of ReactiveFeedback and RxFeedback

null 687 Nov 25, 2022
A document-based SwiftUI application for viewing and editing EEG data, aimed at making software for viewing brain imaging data more accessible.

Trace A document-based SwiftUI application for viewing and editing EEG data, aimed at making software for viewing brain imaging data more accessible.

Tahmid Azam 3 Dec 15, 2022
Implemented MVVM-C (Coordinator) architecture pattern for the project. Which is satisfying SOLID principles altogether. Protocol oriented development has been followed.

BreakingBad BreakingBad API doc Implemented MVVM-C (Coordinator) architecture pattern for the project. Which is satisfying SOLID principples altogethe

Dhruvik Rao 2 Mar 10, 2022
Lentit iOS app developed in Swift with SwiftUI using MVVM design pattern

Lentit Track things you lend with Lentit Features 100% Swift 100% SwiftUI MVVM d

W1W1-M 2 Jun 26, 2022
News App 📱 built to demonstrate the use of SwiftUI 3 features, Async/Await, CoreData and MVVM architecture pattern.

Box Feed News App ?? built to demonstrate the use of following features, SwiftUI 3 Async/Await AsyncImage List Refreshable Swipe Actions Separator Cor

Sameer Nawaz 113 Dec 20, 2022
Aplikasi iOS Advanced Level To Do List dengan Firebase Auth, SwiftUI, MVVM Design Pattern, dan Firebase Firestore

Aplikasi Tasker adalah aplikasi iOS To Do List yang dibuat menggunakan Autentikasi Firebase / Firestore dan MVVM Design Pattern.

DK 10 Oct 17, 2022
Aplikasi iOS Statistik Internasional Penyebaran Covid-19 dengan SwiftUI, MVVM Design Pattern, dan REST APIs dari rapidapi.com

CovStats CovStats adalah aplikasi iOS Data Statistik Internasional Covid-19 yang datanya didapatkan dari rapidapi.com dengan struktur REST API. Dibuat

DK 7 Aug 1, 2022
NewsApp - MVVM pattern have been used

NewsApp MVVM pattern have been used. All features are working properly as suppose to. Alamofire, Kingfisher, lottie-ios and IQKeyboardManagerSwift pod

Uğur Can GEDİK 0 Jun 6, 2022
Food Order App for iOS. VIPER pattern, Alamofire and Firebase used.

Nibble Yemek Sipariş Uygulaması Techcareer.net Techmasters IOS Bootcamp bitirme ödevi için hazırladığım bir yemek sipariş uygulaması. API KEY SON GEÇE

null 2 Sep 20, 2022
Weather forecast app that allows the user to either look for weather at their current location based on the GPS data or search for another city manually.

⛅️ Cloudy Weather forecast app that allows the user to either look for weather at their current location based on the GPS data or search for another c

Burhan 0 Nov 7, 2021
Notes App using Core Data for persisting the data ✨

Notes Notes app where we can save and manage our daily notes. App usage The app allow us to add new notes We can delete our existing notes We can edit

Chris 0 Nov 13, 2021
A library of data structures for working with collections of identifiable elements in an ergonomic, performant way.

Swift Identified Collections A library of data structures for working with collections of identifiable elements in an ergonomic, performant way. Motiv

Point-Free 263 Jan 1, 2023
CZWeatherKit is a simple, extensible weather library for iOS, tvOS, and OS X that allows for easy fetching of weather data from various weather services.

CZWeatherKit is a simple, extensible weather library for iOS, tvOS, and OS X that allows for easy fetching of weather data from various weather services.

Comyar 455 Nov 20, 2022
SwiftUI and Combine based GitHubSearch example.

GitHubSearchWithSwiftUI GitHubSearchWithSwiftUI is an example that using Combine and SwiftUI Receive Results Receive Error SafariViewController ricemi

Taiki Suzuki 200 Dec 18, 2022
Jetpack Compose and SwiftUI based Kotlin Multiplatform project

BikeShare Jetpack Compose and SwiftUI based Kotlin Multiplatform sample project (based on CityBikes API). Running on iOS (SwiftUI) macOS (SwiftUI) And

John O'Reilly 464 Jan 1, 2023
A small SwiftUI based chat client for IRC, using swift-nio-irc

NeoIRC A simple Internet Relay Chat client implemented using SwiftNIO and SwiftUI. Inspired by: For maximum NIO someone (I’m tempted) should adopt NIO

The Noze Consortium 18 Jun 22, 2022