All Projects → Mioke → SwiftArchitectureWithPOP

Mioke / SwiftArchitectureWithPOP

Licence: MIT license
A base architecture written in swift and protocol oriented, for building new apps easily and quickly

Programming Languages

swift
15916 projects
ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to SwiftArchitectureWithPOP

TVToday
iOS TV Shows app with TMDb Api. RxSwift, MVVM, Clean Architecture. Tuist + Swift Package Manager
Stars: ✭ 27 (-3.57%)
Mutual labels:  rxswift, ios-app
Rxxmly
RxSwift 实现MVVM高仿喜马拉雅的函数响应式编程
Stars: ✭ 313 (+1017.86%)
Mutual labels:  rxswift, ios-app
Bookstore Ios
 Sample iOS App - A collection of examples and patterns for Unit Testing, UI Testing, handling Result/Optionals, writing documentation, and more. Details in README.
Stars: ✭ 147 (+425%)
Mutual labels:  ios-app, swift-framework
Leon
Leon is swift library to show and slid images with more gesture
Stars: ✭ 16 (-42.86%)
Mutual labels:  ios-app, swift-framework
Reduxmoviedb
🎥 See the upcoming movies! ReSwift + RxSwift 💖 Hacktoberfest 🎃
Stars: ✭ 137 (+389.29%)
Mutual labels:  rxswift, ios-app
Httper Ios
App for developers to test REST API.
Stars: ✭ 376 (+1242.86%)
Mutual labels:  rxswift, ios-app
DailyNews
Daily News is a news app with good looking user interface ! Apps architecture is MVVM and used RxSwift for binding.
Stars: ✭ 31 (+10.71%)
Mutual labels:  rxswift, ios-app
LXFProtocolTool
由Swift中协议方式实现功能的实用工具库【Refreshable、EmptyDataSetable 支持 Rx 】
Stars: ✭ 101 (+260.71%)
Mutual labels:  rxswift, protocol
Bark
Bark is an iOS App which allows you to push customed notifications to your iPhone
Stars: ✭ 2,371 (+8367.86%)
Mutual labels:  rxswift, ios-app
Papr
🌁 An Unsplash app for iOS
Stars: ✭ 1,025 (+3560.71%)
Mutual labels:  rxswift, ios-app
Gitiny
An iOS app for GitHub with exploring trending
Stars: ✭ 247 (+782.14%)
Mutual labels:  rxswift, ios-app
RxCocoa-Texture
RxCocoa Extension Library for Texture.
Stars: ✭ 98 (+250%)
Mutual labels:  rxswift, ios-app
RxDisplayLink
RxDisplayLink reactive wrapper for CADisplayLink
Stars: ✭ 25 (-10.71%)
Mutual labels:  rxswift
PSAVanCanBridge
VAN - CAN protocol bridge (V2C) for cars made by PSA Group (Peugeot, Citroen)
Stars: ✭ 67 (+139.29%)
Mutual labels:  protocol
diepssect
A public repo for hacky diep stuff - networking protocol, WebAssembly, memory editing, & physics
Stars: ✭ 26 (-7.14%)
Mutual labels:  protocol
minecraft-protocol
Library for decoding and encoding Minecraft packets
Stars: ✭ 20 (-28.57%)
Mutual labels:  protocol
refugerestrooms-ios
[DECOMMISSIONED] iOS app for Refuge Restrooms, crowdsourcing safe restrooms for the trans community
Stars: ✭ 19 (-32.14%)
Mutual labels:  ios-app
dev-feed
Flutter-based mobile app displaying a list of daily curated content from top engineering blogs and articles. Backed by a GraphQL-based API written in Kotlin..
Stars: ✭ 20 (-28.57%)
Mutual labels:  ios-app
RxMVC-Swift
Unidirectional MVC with ReactiveX
Stars: ✭ 87 (+210.71%)
Mutual labels:  rxswift
rust android ios
Android / iOS app with shared Rust logic
Stars: ✭ 182 (+550%)
Mutual labels:  ios-app

SwiftArchitectureWithPOP

A base architecture written in swift and protocol oriented.

Installation

Using cocoapods:
pod 'SwiftyArchitecture'
# or choose on your need
pod 'SwiftyArchitecture/Persistance'
pod 'SwiftyArchitecture/Networking'
pod 'SwiftyArchitecture/RxExtension'
# Write tests
pod 'SwiftyArchitecture/Testable'
Manually

Download .zip package and copy the SwiftyArchitecture/Base folder into you project.

What to provide

  • Networking.
  • Persistence.
  • Data center, handle all API and storage of response data in realm database.
  • Modulize or componentize your main project, provide data transmit and router between modules.
  • Reactive extension for all functionalities above.

Networking

  • Server

    Provide some basic functionalities of a server like url configuation, environments switch etc. In test Mode, offline the server.

  let server: Server = .init(live: cdn.config.liveURL,
                             customEnvironments: [
                              .custom("Dev"): cdn.config.devURL,
                              .custom("Staging"): cdn.config.stagingURL,
                            ])
  #if DEBUG
      server.switch(to: .custom("Dev"))
  #endif

You can comstomize the operation of dealing response data now, just subclass from Server and conform to protocol ServerDataProcessProtocol like:

func handle(data: Any) throws -> Void {
    
    if let dic = data as? [String: Any],
       let errorCode = dic["error_code"] as? Int,
       errorCode != 0 {
        throw NSError(domain: kYourErrorDomain, 
                      code: errorCode, 
                      userInfo: [NSLocalizedDescriptionKey: message])
    }
}
  • API

    Now you can manager request with API<ApiInfoProtocol>, creating a class conformed to ApiInfoProtocol, only need to provide some infomation about the API and set where the callback is, you are already finished the configuration of an API.

    var apiVersion: String {
        get { return "v2" }
    }
    var apiName: String {
        get { return "user/login" }
    }
    var server: Server {
        get { return mainServer }
    }

    typealias ResultType = _User

    static var responseSerializer: ResponseSerializer<_User> {
        return JSONResponseSerializer<_User>()
    }

The API provide some basic method like:

  public func loadData(with params: [String: Any]?) -> Void

Using chaining syntax to request data:

api.loadData(with: nil).response({ (api, user, error) in
  if let error = error {
    // deal error
  }
  if let user = user {
    // do response if have data
  }
})
  • Rx supported

ApiManager provides an Observable for you, you can transfrom it or directly bind it to something:

api.rx.loadData(with: params)
    .flatMap {
        ...
        return yourResultObservable
    }
    .bind(to: label.rx.text)
    .dispose(by: bag)

Data Center

SwiftyArchitecture provides a data center, which can automatically manage your models, data and requests. This function is based on Reactive Functional Programing, and using RxSwift. The networking kit is using API and database is Realm.

It provides an accessor called DataAccessObject<Object>, and all data or models can be read throught this DAO.

Example:

Firstly, define your data model in model layer and your database model in Realm.

final class User: Codable {
    var userId: String = ""
    var name: String = ""
}

final class _User: Object {
    @objc dynamic var userId: String = ""
    @objc dynamic var name: String = ""
}

To manage this User model, it must be conform to protocol DataCenterManaged, this protocol defines how data center should do with this model.

extension User: DataCenterManaged {
    // defines how transform data from API to data base object.
    static func serialize(data: User) throws -> _User {
        let user = _User()
        user.name = data.name
        user.userId = data.userId
        return user
    }
    // define data base object's type
    typealias DatabaseObject = User
    // define API's type
    typealias APIInfo = UserAPI
}

Then you can read the data in database and use it by using DataAccessObject<User>.

// read all users and display on table view.
DataAccessObject<User>.all
    .map { $0.sorted(by: <) }
    .map { [AnimatableSectionModel(model: "", items: $0)] }
    .bind(to: tableView.rx.items(dataSource: datasource))
    .disposed(by: disposeBag)

And then you can update models throught requests, and when data changed, the Observable<User> will notify observers the new model is coming. And data center will save the newest data to database. For developers of in feature team, they should only need to forcus on models and actions.

print("Show loading")

let request = Request<User>()
DataAccessObject<User>
    .update(with: request)
    .subscribe({ event in
        switch event {
        case .completed:
            print("Hide loading")
        case .error(let error as NSError):
            print("Hide loading with error: \(error.localizedDescription)")
        default:
            break
        }
    })
    .disposed(by: self.disposeBag)

Persistance (refactoring)

  • Database

    Like ApiManager, only need to subclass from KMPersistanceDatabase and conform to DatabaseManagerProtocol, provide path,databaseName,database, you are already create a new database in your project. e.g.

  class DefaultDatabase: KMPersistanceDatabase, DatabaseManagerProtocol {
    
    override init() {

        self.databaseName = "default.db"
        self.path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first! + "/" + self.databaseName
        self.database = FMDatabaseQueue(path: self.path)
        
        super.init()
    }
  }
  • Table and Record

    Subclass from KMPersistanceTable and conform to TableProtocol let you create a new table in a database. Any objcect conform RecordProtocol can be record in the table you want. See more details in demo.

    Using this just like:

    let table = UserTable()
    let newUser = UserModel(name: "Klein", uid: 310)
    table.replaceRecord(newUser)
  • Fetch

    Fetch data with conditions using DatabaseCommandCondition:

    let table = UserTable()
    let condition = DatabaseCommandCondition()
            
    condition.whereConditions = "user_id >= 0"
    condition.orderBy = "user_name"
            
    let result = table.queryRecordWithSelect("user_name", condition: condition)
  • Advanced

    Always, database provide method of doing query or execute with sql directly, for complex database operation:

  let db = DefaultDatabase()
  db.query("select * from tableDoesntExtist", withArgumentsInArray: nil)

Modulize or Componentize

Support using cocoapods for modulize your project. You can separate some part of code which is indepence enough, and put the code into a pod repo. And finally the main project maybe have no code any more, when building your app, the cocoapods will install all the dependencies together and generate your app.

The good things of modulization or componentization are

  • modules are closed between each other, and there's no way to visit the detail from another module;
  • modules can be used in other project, if the module is well designed;
  • instead of using git branch, now we can use version of the pod for development and generate app;
  • using cocoapods package to generate binary library, fasten the app package process.

The bad things of it are (for now)

  • low running performance for developing, because the more frameworks or libraries, the worse lldb performance get;
  • need a lot of utils to maintain pods and their git repos, like auto generate version, auto update Podfile etc.

So, this function should depend on the situation of your team. ;)

  • Module

    Modules should register into the ModuleManager when app started.

    if let url = Bundle.main.url(forResource: "ModulesRegistery", withExtension: ".plist") {
        try! ModuleManager.default.registerModules(withConfigFilePath: url)
    }

    The registery .plist file contains an array of class names, which implement module's protocol, like:

    // in public protocol pod:
    public extension ModuleIdentifier {
        static let auth: ModuleIdentifier = "com.klein.module.auth"
    }
    
    public protocol AuthServiceProtocol {
        func authenticate(completion: @escaping (User) -> ()) throws
    }
    
    // in hidden pod:
    class AuthModule: ModuleProtocol, AuthServiceProtocol {
        static var moduleIdentifier: ModuleIdentifier {
            return .auth
        }
        required init() { }
        
        func moduleDidLoad(with manager: ModuleManager) {}
        
        func authenticate(completion: @escaping (User) -> ()) throws {
            // do your work
        }
    }
    
    // in .plist
    array:
      Auth.AuthModule,
      ...

    Call other module's method like

    guard let authModule = try? self.moduleManager?.bridge.resolve(.auth) as? AuthServiceProtocol 
    else {
        fatalError()
    }
    authModule.authenticate(completion: { user in
        print(user)
    })
  • Router

    TBA

  • Initiator

    When some modules should do some work at the start process, you will need Initiator and regitster you opartion in it.

    // defined in framework
    public protocol ModuleInitiatorProtocol {
        static var identifier: String { get }
        static var operation: Initiator.Operation { get }
        static var priority: Initiator.Priority { get }
        static var dependencies: [String] { get }
    }
    
    // implement in pod repo
    extension AuthModule: ModuleInitiatorProtocol {
        static var identifier: String { moduleIdentifier }
        static var priority: Initiator.Priority { .high }
        static var dependencies: [String] { [ModuleIdentifier.application] }
        static var operation: Initiator.Operation {
            return {
                // do something
            }
        }
    }  

Tools and Kits

  • Custom extensions and categories.
  • UI relevant class for easy accessing global UI settings.
  • SystemLog can write log to files, and stored in sandbox. (Refactoring)

Almost done >w<!

TODO

  • Networking: cache, origin data transform to Model or View's data, priority of request.
  • Mock of API's response.
  • Download and upload functions in API manager.
  • Persistance: transform data to model or View's data after query.(don't need it now, using Realm)
  • Animations, Tools and Kits: TextKit like YYText, etc. (SwiftyArchitecture won't provide those utilities, because base shouldn't have to.)
  • Refactoring, more functional and reative. Considering to use Rx or ReactiveSwift. Fully use genericity.
  • Modulize of componentlization usage. Router.

License

All the source code is published under the MIT license. See LICENSE file for details.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].