Example of Clean Architecture of iOS app using RxSwift

Overview

Clean architecture with RxSwift

Contributions are welcome and highly appreciated!!

You can do this by:

  • opening an issue to discuss the current solution, ask a question, propose your solution etc. (also English is not my native language so if you think that something can be corrected please open a PR 😊 )
  • opening a PR if you want to fix bugs or improve something

Installation

Dependencies in this project are provided via Cocoapods. Please install all dependecies with

pod install

High level overview

Domain

The Domain is basically what is your App about and what it can do (Entities, UseCase etc.) It does not depend on UIKit or any persistence framework, and it doesn't have implementations apart from entities

Platform

The Platform is a concrete implementation of the Domain in a specific platform like iOS. It does hide all implementation details. For example Database implementation whether it is CoreData, Realm, SQLite etc.

Application

Application is responsible for delivering information to the user and handling user input. It can be implemented with any delivery pattern e.g (MVVM, MVC, MVP). This is the place for your UIViews and UIViewControllers. As you will see from the example app, ViewControllers are completely independent of the Platform. The only responsibility of a view controller is to "bind" the UI to the Domain to make things happen. In fact, in the current example we are using the same view controller for Realm and CoreData.

Detail overview

To enforce modularity, Domain, Platform and Application are separate targets in the App, which allows us to take advantage of the internal access layer in Swift to prevent exposing of types that we don't want to expose.

Domain

Entities are implemented as Swift value types

public struct Post {
    public let uid: String
    public let createDate: Date
    public let updateDate: Date
    public let title: String
    public let content: String
}

UseCases are protocols which do one specific thing:

public protocol PostsUseCase {
    func posts() -> Observable<[Post]>
    func save(post: Post) -> Observable<Void>
}

UseCaseProvider is a service locator. In the current example, it helps to hide the concrete implementation of use cases.

Platform

In some cases, we can't use Swift structs for our domain objects because of DB framework requirements (e.g. CoreData, Realm).

final class CDPost: NSManagedObject {
    @NSManaged public var uid: String?
    @NSManaged public var title: String?
    @NSManaged public var content: String?
    @NSManaged public var createDate: NSDate?
    @NSManaged public var updateDate: NSDate?
}

final class RMPost: Object {
    dynamic var uid: String = ""
    dynamic var createDate: NSDate = NSDate()
    dynamic var updateDate: NSDate = NSDate()
    dynamic var title: String = ""
    dynamic var content: String = ""
}

The Platform also contains concrete implementations of your use cases, repositories or any services that are defined in the Domain.

final class PostsUseCase: Domain.PostsUseCase {
    
    private let repository: AbstractRepository<Post>

    init(repository: AbstractRepository<Post>) {
        self.repository = repository
    }

    func posts() -> Observable<[Post]> {
        return repository.query(sortDescriptors: [Post.CoreDataType.uid.descending()])
    }
    
    func save(post: Post) -> Observable<Void> {
        return repository.save(entity: post)
    }
}

final class Repository<T: CoreDataRepresentable>: AbstractRepository<T> where T == T.CoreDataType.DomainType {
    private let context: NSManagedObjectContext
    private let scheduler: ContextScheduler

    init(context: NSManagedObjectContext) {
        self.context = context
        self.scheduler = ContextScheduler(context: context)
    }

    override func query(with predicate: NSPredicate? = nil,
                        sortDescriptors: [NSSortDescriptor]? = nil) -> Observable<[T]> {
        let request = T.CoreDataType.fetchRequest()
        request.predicate = predicate
        request.sortDescriptors = sortDescriptors
        return context.rx.entities(fetchRequest: request)
            .mapToDomain()
            .subscribeOn(scheduler)
    }

    override func save(entity: T) -> Observable<Void> {
        return entity.sync(in: context)
            .mapToVoid()
            .flatMapLatest(context.rx.save)
            .subscribeOn(scheduler)
    }
}

As you can see, concrete implementations are internal, because we don't want to expose our dependecies. The only thing that is exposed in the current example from the Platform is a concrete implementation of the UseCaseProvider.

public final class UseCaseProvider: Domain.UseCaseProvider {
    private let coreDataStack = CoreDataStack()
    private let postRepository: Repository<Post>

    public init() {
        postRepository = Repository<Post>(context: coreDataStack.context)
    }

    public func makePostsUseCase() -> Domain.PostsUseCase {
        return PostsUseCase(repository: postRepository)
    }
}

Application

In the current example, Application is implemented with the MVVM pattern and heavy use of RxSwift, which makes binding very easy.

Where the ViewModel performs pure transformation of a user Input to the Output

protocol ViewModelType {
    associatedtype Input
    associatedtype Output
    
    func transform(input: Input) -> Output
}
final class PostsViewModel: ViewModelType {
    struct Input {
        let trigger: Driver<Void>
        let createPostTrigger: Driver<Void>
        let selection: Driver<IndexPath>
    }
    struct Output {
        let fetching: Driver<Bool>
        let posts: Driver<[Post]>
        let createPost: Driver<Void>
        let selectedPost: Driver<Post>
        let error: Driver<Error>
    }
    
    private let useCase: AllPostsUseCase
    private let navigator: PostsNavigator
    
    init(useCase: AllPostsUseCase, navigator: PostsNavigator) {
        self.useCase = useCase
        self.navigator = navigator
    }
    
    func transform(input: Input) -> Output {
       ......
    }

A ViewModel can be injected into a ViewController via property injection or initializer. In the current example, this is done by Navigator.

protocol PostsNavigator {
    func toCreatePost()
    func toPost(_ post: Post)
    func toPosts()
}

class DefaultPostsNavigator: PostsNavigator {
    private let storyBoard: UIStoryboard
    private let navigationController: UINavigationController
    private let services: ServiceLocator
    
    init(services: ServiceLocator,
         navigationController: UINavigationController,
         storyBoard: UIStoryboard) {
        self.services = services
        self.navigationController = navigationController
        self.storyBoard = storyBoard
    }
    
    func toPosts() {
        let vc = storyBoard.instantiateViewController(ofType: PostsViewController.self)
        vc.viewModel = PostsViewModel(useCase: services.getAllPostsUseCase(),
                                      navigator: self)
        navigationController.pushViewController(vc, animated: true)
    }
    ....
}

class PostsViewController: UIViewController {
    private let disposeBag = DisposeBag()
    
    var viewModel: PostsViewModel!
    
    ...
}

Example

The example app is Post/TODOs app which uses Realm, CoreData and Network at the same time as a proof of concept that the Application level is not dependant on the Platform level implementation details.

CoreData Realm Network

Modularization

The corner stone of Clean Architecture is modularization, as you can hide implementation detail under internal access layer. Further read of this topic here

TODO:

  • add tests
  • add MVP example
  • Redux example??

Links

Any questions?

Comments
  • ViewModel and UIKit

    ViewModel and UIKit

    Should viewModel import framework that exposes UIKit like RxCocoa? Maybe it's better to expose input/output as Observable's and convert them to Driver in viewController implementation where it's legit to import RxCocoa?

    opened by IvanKalaica 7
  • Store network requests in Realm

    Store network requests in Realm

    Hi, I have a question related to architecture of view model.

    Let's assume that I have a requirement to store network requests payload in Realm in order to be able to access data even if the user is offline. Currently in the example you show how to use Network or Realm or CoreData but not all together.

    If i want to fulfill the requirement I need to do the following:

    1. Pass 2 use cases into my view model . One is the network use case and the other one is the realm use case
    2. Execute a call to the server via the Network use case and then use flat map to get the results and store them in Realm.
    3. If the user is offline i can simply fetch the data from realm use case instead of the network use case.

    I think it can work but maybe there is a better way to achieve it. Maybe by creating an abstraction layer on top of it that will do the call (either to the server or to the offline database in case the user is online/offline)

    What do you think ?

    Thanks!

    opened by ranhsd 7
  • Crash on asDriverOnErrorJustComplete function

    Crash on asDriverOnErrorJustComplete function

    Hi, I started to work on some side project which is based on this great project.

    I facing some strange issue that when i execute a request from my view model to the server if the request failed the app crash. After debug it i found the crash is because the asserationFailure command check the error and because the error object is not nil the app crash.

    This is the whole function:

    func asDriverOnErrorJustComplete() -> Driver<E> { return asDriver { error in assertionFailure("Error \(error)") return Driver.empty() } }

    My view model code is:

    ` let login = input.loginTrigger.withLatestFrom(emailAndPassword) .flatMapLatest({ [unowned self] (email, password) in

            return self.loginUseCase.login(username: email, password: password)
                .trackActivity(activityIndicator)
                .trackError(errorTracker)
                .asDriverOnErrorJustComplete()
        })
    

    `

    As you can see i use the same best practices like you use in your project. I also tried to change your code so network requests will fail and the asDriverOnErrorJustComplete but from some reason on your project it ignore the asserationFailure line. Can someone advice what should i do or what i am doing wrong?

    thanks!

    opened by ranhsd 5
  • How to decode json other then using codable?

    How to decode json other then using codable?

    Hello,

    in Postentry the part where Encodable do the task to decode json from internet. Is there any efficient and fast way to do this? like say you have nested object in one you gotta have several lines of code just to decode it. Thank you.

    opened by ninjandroid 4
  • Protocol conformance

    Protocol conformance

    final class Repository<T: CoreDataRepresentable>: AbstractRepository where T == T.CoreDataType.DomainType

    could not understand how T.CoreDataType.DomainType is returning back the Domain Type .

    In case of Domain.Post, Post is conforming to CoreDataRepresentable `extension Post: CoreDataRepresentable { typealias CoreDataType = CDPost ....

    }`

    and CDPost conforms to DomainConvertibleType and Persistable But nowhere associatedtype DomainType is defined.

    Can you please clarify?

    opened by fodderkonoko 4
  • Whats the difference between this & Clean-Swift

    Whats the difference between this & Clean-Swift

    Hello All I'm new to Clean Architecture, for the last couple of days, I've been struggling with learning the basic concepts of Clean Architecture, For iOS, I have 2 main sources for learning, This repo & Raymond's implementation here. These 2 implementations differ in the concepts of clean architecture. 1- one example is that this implementation relies on a Domain target to store all the models for the app, All the targets that want to use one of these models has to import Domain to see them. 2- Another example is that Raymond's implementation relies on workers to do the actual work of the interactor, why doesn't this repo make use of these workers to push the Single Responsibility Principle further?

    Any help is highly appreciated!

    opened by wassimseif 4
  • UITableViewCell/UICollectionViewCell input example

    UITableViewCell/UICollectionViewCell input example

    Hi Guys, I'm very exited with this architecture and want to thank you. While working with it I've got some questions. Could you please provide an example for the following case: I have a cell with a textfield placed on it. How should I create an Input for controller's ViewModel in this case to make it work properly with cells reusability?

    Thanks.

    opened by ipaboy 4
  • Possible wrong use of MVVM pattern

    Possible wrong use of MVVM pattern

    Hi guys,

    first of all, let me thank you for your work, which is really great and inspiring! I've been using MVVM in iOS since 4 years now and I've learned that everyone has its personal way to implement things with this pattern and, most of all, everyone intend a "View Model" in different way. However, there seems to be 2 things that almost everyone agrees upon:

    1. Views should NEVER know the existence of the "model layer"
    2. In terms of separations between layers, UIViews and UIViewControllers falls inside the "View" layer

    Taking a look at your example, in the PostsViewController, you're fetching the UITableView with the .rx extension: inside the closure, you're directly referencing the UITableViewCells IBOutlets and directly populating with model properties, which (in my opinion) totally breaks the MVVM pattern for different reasons :

    • The UITableViewCell (a view) is fully aware (and directly accessing) something from the Model layer.
    • The cell is configured by a single view controller, meaning that the same cell must be configured in every scenario it's going to be used (suppose you have another screen where you would like to present
    • There's no "cell view Model" concept, which could be useful to reuse the same cell (with same outlets) to display data from different models.

    Does it make sense to you? Am I being too "strict to the pattern" for your scenario? Thanks in advance :)

    opened by stefanomondino 4
  • [Question] What is the intent between using either subscribeOn or observeOn?

    [Question] What is the intent between using either subscribeOn or observeOn?

    Hi,

    As I mentioned in the title, what is the difference between two use cases? observeOn: https://github.com/sergdort/CleanArchitectureRxSwift/blob/master/Network/Network/Network.swift subscribeOn: https://github.com/sergdort/CleanArchitectureRxSwift/blob/master/RealmPlatform/Repository/Repository.swift

    Thank you. Your example is very great and it helps me a lot :)

    opened by lizhiquan 4
  • Multiple models for Repository in single UseCase.

    Multiple models for Repository in single UseCase.

    Let's say we want to save PostDetail model also in this PostsUseCase, in that case, how do we satisfy the generic constraint for it.

    final class PostsUseCase<Repository>: Domain.PostsUseCase where Repository: AbstractRepository, Repository.T == Post {
    
        private let repository: Repository
    
        init(repository: Repository) {
            self.repository = repository
        }
    
         func save(postDetail: PostDetail) -> Observable<Void> {
            return repository.save(entity: postDetail)
        }
    
         func save(post: Post) -> Observable<Void> {
            return repository.save(entity: post)
        }
    
    }
    
    opened by kaungsoe 3
  • Question around `do` and `Driver` for Input

    Question around `do` and `Driver` for Input

    As the title says, I have got two questions trying to figure out why it's required or what's the underlying thinking around it.

    1, Why do you use do in ViewModel instead of using subscribe in ViewModel?

            let dismiss = Driver.of(save, input.cancelTrigger)
            .merge()
            .do(onNext: navigator.toPosts)
    

    Is there a specific reason to use do here? I remember that I read somewhere about do is a bad practice since it's a side effect, why not just use subscribe to achieve the pretty much same thing?

    2, I know Driver has bunch of benefit, a) main thread b) no error c) shared

        struct Input {
            let cancelTrigger: Driver<Void>
            let saveTrigger: Driver<Void>
            let title: Driver<String>
            let details: Driver<String>
        }
        struct Output {
            let dismiss: Driver<Void>
            let saveEnabled: Driver<Bool>
        }
    

    But over here it makes sense to use it for Output, but why we are using it for input?

    opened by tonylin75 3
  • Why use case containing rxSwift?

    Why use case containing rxSwift?

    As far as I know, rxSwift is 3rd party library. If the use case which is main logic of the app containing 3rd party library.. at the base of the app it's depending on rxSwift.

    Meaning that this one is violated clean architecture at the very base. You can't copy paste the use case and domain without rxSwift library.

    And this project lack of unit test as well, unit test is the interface of the domain.

    opened by tirtavium 1
  • Maybe memory leaks

    Maybe memory leaks

    Hi. I found this good repo today, and learn so much from it. But I maybe just found a memory leak. Select the "Network" tab, and select one post what ever you like, then the edit post vc come in, then click the back button, edit post vc out. Now, check the memory graph in Xcode, the edit post vc instance can't be released. I check the code and don't know why.

    I think memory leak is a difficult thing in software develop work, especially when using RxSwift. Hope you can correct this.

    opened by mengxiangjian13 0
  • Combine Version of ActivityIndicator and ErrorTacker

    Combine Version of ActivityIndicator and ErrorTacker

    HI, First to say, this an awesome project which focus on Clean Architecture and my main reference. Now i am on a Combine project and requires ActivityIndicator and ErrorTracker on Combine Version.

    opened by mahi8514 1
  • Making use cases generic doesn't work with associatedtypes

    Making use cases generic doesn't work with associatedtypes

    Hi! Thanks for providing a really nice starting point to think about how to build up an app and separate concerns. Of course, my first improvement was to have a UsecaseProviding and UsecaseDefinable protocol.

    public protocol UsecaseProviding {
        associatedtype Usecase: UsecaseDefinable
    
        func makeUsecase() -> Usecase
    }
    
    public protocol UsecaseDefinable {
        associatedtype DomainEntity
    
        func entities() -> Observable<[DomainEntity]>
        func save(entity: DomainEntity) -> Observable<Void>
        func delete(entity: DomainEntity) -> Observable<Void>
    }
    

    However, as soon as I begin the implementation in the Database target, for example:

    public final class PostsUsecaseProvider: UsecaseProviding {
        public typealias Usecase = PostsUsecase<Repository<Post>>
    }
    

    I'll get an error like: type alias cannot be declared public, because its underlying type uses an internal type because the Repository types are all internal. As they should be. That's specific logic for the database. However, I do want to have a concrete implementation of the Usecase and UsecaseProvider in the Database target.

    The easiest fix would be to just publicize the Repository parts, but that feels kind of dirty. How would you fix this?

    opened by jbehrens94 0
  • MVVM Isuue

    MVVM Isuue

    PostsViewModel is exposing to the view the Post mode as output: let selectedPost: Driver<Post>

    My question is: PostsViewController knows Post data model which means that the view knows the model. As far as I understand MVVM architecture the UI (view) doesn't need to know the model (Post).

    What do you think? is there a place to change the selectedPost parameter to be: let selectedPost: Driver<PostViewModel> ?

    opened by evyasafhouzz 1
  • crash on Xcode 11 when click Realm Tab

    crash on Xcode 11 when click Realm Tab

    I tried Xcode 10, it works fine, but crashes on Xcode 11 whenever you click Realm Tab.

    RunLoopThreadScheduler:

    private final class ThreadTarget: NSObject { @objc fileprivate func threadEntryPoint() { let runLoop = RunLoop.current ***CRASH: com.CleanArchitectureRxSwift.RealmPlatform.Repository (8): signal SIGABRT runLoop.add(NSMachPort(), forMode: RunLoop.Mode.default) runLoop.run() } }

    2019-09-24 22:40:34.205345+1200 CleanArchitectureRxSwift[95204:325374] *** Terminating app due to uncaught exception 'RLMException', reason: 'Primary key property 'uid' does not exist on object 'RMPhoto''

    opened by g-enius 2
Owner
iOS developer
null
SwiftUI sample app using Clean Architecture. Examples of working with CoreData persistence, networking, dependency injection, unit testing, and more.

Articles related to this project Clean Architecture for SwiftUI Programmatic navigation in SwiftUI project Separation of Concerns in Software Design C

Alexey Naumov 4k Dec 31, 2022
Clean Actor Model Architecture

CAMA Eonil, 2021. CAMA is short for "Clean Actor Model Architecture". As like it says, its CA(Clean Architecture) + AM(Actor model). AM here means AM

Eonil 1 Oct 10, 2021
A collection of iOS architectures - MVC, MVVM, MVVM+RxSwift, VIPER, RIBs and many others

ios-architecture WIP ?? ?? ?? ??️ Demystifying MVC, MVVM, VIPER, RIBs and many others A collection of simple one screen apps to showcase and discuss d

Pawel Krawiec 1.3k Jan 3, 2023
This repository contains a detailed sample app that implements VIPER architecture in iOS using libraries and frameworks like Alamofire, AlamofireImage, PKHUD, CoreData etc.

iOS Viper Architecture: Sample App This repository contains a detailed sample app that implements VIPER architecture using libraries and frameworks li

MindOrks 653 Jan 2, 2023
Spin aims to provide a versatile Feedback Loop implementation working with the three main reactive frameworks available in the Swift community (RxSwift, ReactiveSwift and Combine)

With the introduction of Combine and SwiftUI, we will face some transition periods in our code base. Our applications will use both Combine and a thir

Spinners 119 Dec 29, 2022
Sample project using VIPER architecture

VIPER-Persons Small project using a master and detail view Demonstrates the use of the following features: VIPER architecture (https://mutualmobile.co

Sebastian Wramba 43 Feb 11, 2022
Sample Code of the App Architecture Book

App Architecture iOS Application Design Patterns in Swift Welcome to the repository of the App Architecture book! This repository hosts all sample cod

objc.io 2k Dec 29, 2022
Reactant is a reactive architecture for iOS

Reactant Reactant is a foundation for rapid and safe iOS development. It allows you to cut down your development costs by improving reusability, testa

Brightify 374 Nov 22, 2022
YARCH iOS Architecture

YARCH is an architecture pattern developed primarly for iOS applications. You can ask any questions in our telegram channel. Russian version of the re

Alfa Digital 196 Jan 3, 2023
Swift Interaction with VIPER Architecture

SwiftyVIPER SwiftyVIPER allows easy use of VIPER architecture throughout your iOS application. VIPER Architecture What is VIPER? Great question! VIPER

Cody Winton 121 Jan 2, 2023
MoneySafe - Application for tracking income and expenses and analyzing expenses. VIPER architecture, CoreData, Charts

?? MoneySafe ?? Application for tracking income and expenses and analyzing expen

Vladislav 5 Dec 27, 2022
Challenge-viper-finance - Project for VIPER Architecture Dev Sprints on Devpass

VIPER Challenge - Finance App ?? Neste desafio, aplicaremos conceitos da arquite

Devpass 15 Oct 11, 2022
Viper Framework for iOS using Swift

Write an iOS app following VIPER architecture. But in an easy way. Viper the easy way We all know Viper is cool. But we also know that it's hard to se

Ferran Abelló 504 Dec 31, 2022
Sample applications of iOS Design patterns written using swift.

ios-design-patterns This repo contains all my Sample applications of iOS Design patterns written using swift. Link for my Design patterns Blog : https

ShreeThaanu Raveendran 3 Nov 2, 2022
Stateful view controller containment for iOS and tvOS

StateViewController When creating rich view controllers, a single view controller class is often tasked with managing the appearance of many other vie

David Ask 309 Nov 29, 2022
A holistic approach to iOS development, inspired by Redux and MVVM

Tempura is a holistic approach to iOS development, it borrows concepts from Redux (through Katana) and MVVM. ?? Installation Requirements CocoaPods Sw

Bending Spoons 693 Jan 4, 2023
Mahmoud-Abdelwahab 5 Nov 23, 2022
SpaceX rocket listing app using RxSwift and CLEAN Architecture with MVVM

Jibble SpaceX rocket listing app using RxSwift and CLEAN Architecture with MVVM Demo Features Reactive Bindings URL / JSON Parameter Encoding Filter Y

Ammad Akhtar 0 Dec 5, 2021
iOS TV Shows app with TMDb Api. RxSwift, MVVM, Clean Architecture. Tuist + Swift Package Manager

TVShows iOS app built with RxSwift, using the TMDb API. Built with Swift 5 RxSwift, RxDataSources Clean + Modular Architecture Cordinator Pattern. MVV

Jeans Ruiz 86 Dec 30, 2022
Realm RxSwift - This application was written in order to use Realm, RxSwift frameworks in real example

Realm_RxSwift This simple app was written to introduce basic operations of some

Elbek Khasanov 3 Apr 7, 2022