All Projects → marty-suzuki → Prex

marty-suzuki / Prex

Licence: mit
🔁Unidirectional data flow architecture with MVP and Flux combination for Swift

Programming Languages

swift
15916 projects

Projects that are alternatives of or similar to Prex

Mixpanel
Unofficial Swift Mixpanel client
Stars: ✭ 93 (-8.82%)
Mutual labels:  tvos, watchos
Mothership
iTunes Connect Library inspired by FastLane
Stars: ✭ 72 (-29.41%)
Mutual labels:  tvos, watchos
R.swift
Strong typed, autocompleted resources like images, fonts and segues in Swift projects
Stars: ✭ 8,419 (+8153.92%)
Mutual labels:  tvos, watchos
Predicateflow
Write amazing, strong-typed and easy-to-read NSPredicate.
Stars: ✭ 98 (-3.92%)
Mutual labels:  tvos, watchos
Commoncrypto
CommonCrypto Swift module
Stars: ✭ 87 (-14.71%)
Mutual labels:  tvos, watchos
Sqlitelib
Easily build a custom SQLite static library for use in macOS and iOS frameworks and apps.
Stars: ✭ 38 (-62.75%)
Mutual labels:  tvos, watchos
Articles
Articles for NSHipster.com
Stars: ✭ 1,166 (+1043.14%)
Mutual labels:  tvos, watchos
Life
Conway's Game of Life written in Swift 👾
Stars: ✭ 21 (-79.41%)
Mutual labels:  tvos, watchos
Egocache
Fast Caching for Objective-C (iPhone & Mac Compatible)
Stars: ✭ 1,339 (+1212.75%)
Mutual labels:  tvos, watchos
Host.swift
*Host.swift is no longer maintained*, please use Hostess.swift: https://github.com/rjstelling/Hostess.swift. A Swift implementation of NSHost that works on iOS, OS X and tvOS. Host.swift is safe to use in a framework because it does not require a bridging header.
Stars: ✭ 83 (-18.63%)
Mutual labels:  tvos, watchos
Lift
Lift is a Swift library for generating and extracting values into and out of JSON-like data structures.
Stars: ✭ 33 (-67.65%)
Mutual labels:  tvos, watchos
Extendable
Blocks Based Bluetooth LE Connectivity framework for iOS/watchOS/tvOS/OSX. Quickly configure centrals & peripherals, perform read/write operations, and respond characteristic updates.
Stars: ✭ 88 (-13.73%)
Mutual labels:  tvos, watchos
Queuer
Queuer is a queue manager, built on top of OperationQueue and Dispatch (aka GCD).
Stars: ✭ 964 (+845.1%)
Mutual labels:  tvos, watchos
Fugen
Command line tool for exporting resources and generating code from your Figma files
Stars: ✭ 41 (-59.8%)
Mutual labels:  tvos, watchos
Xcconfigs
Collection of common Xcode configuration files. 🛠
Stars: ✭ 28 (-72.55%)
Mutual labels:  tvos, watchos
Waterfallgrid
A waterfall grid layout view for SwiftUI.
Stars: ✭ 1,086 (+964.71%)
Mutual labels:  tvos, watchos
Procedurekit
Advanced Operations in Swift
Stars: ✭ 863 (+746.08%)
Mutual labels:  tvos, watchos
Swiftui Grid
🚀 SwiftUI Grid layout with custom styles
Stars: ✭ 872 (+754.9%)
Mutual labels:  tvos, watchos
Swiftlinkpreview
It makes a preview from an URL, grabbing all the information such as title, relevant texts and images.
Stars: ✭ 1,216 (+1092.16%)
Mutual labels:  tvos, watchos
Activityrings
An attempt to recreate the ring controls in Apple’s Activity app
Stars: ✭ 88 (-13.73%)
Mutual labels:  tvos, watchos

Platform Language Carthage Version License CI Status

Prex is a framework which makes an unidirectional data flow application possible with MVP architecture.

Concept

Prex represents Presenter + Flux, therefore it is a combination of Flux and MVP architecture. In addition, Reactive frameworks are not used in Prex. To reflect a state to a view, using Passive View Pattern. Flux are used behind of the Presenter. Data flow is unidirectional that like a below figure.

If you use Prex, you have to implement those components.

State

The State has properties to use in the View and the Presenter.

struct CounterState: State {
    var count: Int = 0
}

Action

The Action represents internal API of your application. For example, if you want to increment the count of CounterState, dispatch Action.increment to Dispatcher.

enum CounterAction: Action {
    case increment
    case decrement
}

Mutation

The Mutation is allowed to mutate the State with the Action.

struct CounterMutation: Mutation {
    func mutate(action: CounterAction, state: inout CounterState) {
        switch action {
        case .increment:
            state.count += 1

        case .decrement:
            state.count -= 1
        }
    }
}

Presenter

The Presenter has a role to connect between View and Flux components. If you want to access side effect (API access and so on), you must access them in the Presenter. Finally, you dispatch those results with Presenter.dispatch(_:).

extension Presenter where Action == CounterAction, State == CounterState {
    func increment() {
        dispatch(.increment)
    }

    func decrement() {
        if state.count > 0 {
            dispatch(.decrement)
        }
    }
}

View

The View displays the State with View.reflect(change:). It is called by the Presenter when the State has changed. In addition, it calls the Presenter methods by User interactions.

final class CounterViewController: UIViewController {
    private let counterLabel: UILabel
    private lazy var presenter = Presenter(view: self,
                                           state: CounterState(),
                                           mutation: CounterMutation())

    @objc private func incrementButtonTap(_ button: UIButton) {
        presenter.increment()
    }

    @objc private func decrementButtonTap(_ button: UIButton) {
        presenter.decrement()
    }
}

extension CounterViewController: View {
    func reflect(change: StateChange<CounterState>) {
        if let count = change.count?.value {
            counterLabel.text = "\(count)"
        }
    }
}

You can get only specified value that has changed in the State from StateChange.changedProperty(for:).

Advanced Usage

Shared Store

Initializers of the Store and the Dispatcher are not public access level. But you can initialize them with Flux and inject them with Presenter.init(view:flux:).

This is shared Flux components example.

extension Flux where Action == CounterAction, State == CounterState {
    static let shared = Flux(state: CounterState(), mutation: CounterMutation())
}

or

enum SharedFlux {
    static let counter = Flux(state: CounterState(), mutation: CounterMutation())
}

Inject Flux like this.

final class CounterViewController: UIViewController {
    private lazy var presenter = {
        let flux =  Flux<CounterAction, CounterState>.shared
        return Presenter(view: self, flux: flux)
    }()
}

Presenter Subclass

The Presenter is class that has generic parameters. You can create the Presenter subclass like this.

final class CounterPresenter: Presenter<CounterAction, CounterState> {
    init<T: View>(view: T) where T.State == CounterState {
        let flux = Flux(state: CounterState(), mutation: CounterMutation())
        super.init(view: view, flux: flux)
    }

    func increment() {
        dispatch(.increment)
    }

    func decrement() {
        if state.count > 0 {
            dispatch(.decrement)
        }
    }
}

Testing

I'll explain how to test with Prex. Focus on two test cases in this document.

1. Reflection state testing 2. Create actions testing

Both tests need the View to initialize a Presenter. You can create MockView like this.

final class MockView: View {
    var refrectParameters: ((StateChange<CounterState>) -> ())?

    func reflect(change: StateChange<CounterState>) {
        refrectParameters?(change)
    }
}

1. Reflection state testing

This test starts with dispatching an Action. An action is passed to Mutation, and Mutation mutates state with a received action. The Store notifies changes of state, and the Presenter calls reflect method of the View to reflects state. Finally, receives state via reflect method parameters of the View.

This is a sample test code.

func test_presenter_calls_reflect_of_view_when_state_changed() {
    let view = MockView()
    let flux = Flux(state: CounterState(), mutation: CounterMutation())
    let presenter = Presenter(view: view, flux: flux)

    let expect = expectation(description: "wait receiving ValueChange")
    view.refrectParameters = { change in
        let count = change.changedProperty(for: \.count)?.value
        XCTAssertEqual(count, 1)
        expect.fulfill()
    }

    flux.dispatcher.dispatch(.increment)
    wait(for: [expect], timeout: 0.1)
}

2. Create actions testing

This test starts with calling the Presenter method as dummy user interaction. The Presenter accesses side-effect and finally creates an action from that result. That action is dispatched to the Dispatcher. Finally, receives action via register callback of the Dispatcher.

This is a sample test code.

func test_increment_method_of_presenter() {
    let view = MockView()
    let flux = Flux(state: CounterState(), mutation: CounterMutation())
    let presenter = Presenter(view: view, flux: flux)

    let expect = expectation(description: "wait receiving ValueChange")
    let subscription = flux.dispatcher.register { action in
        XCTAssertEqual(action, .increment)
        expect.fulfill()
    }

    presenter.increment()
    wait(for: [expect], timeout: 0.1)
    flux.dispatcher.unregister(subscription)
}

An addition

You can test mutating state like this.

func test_mutation() {
    var state = CounterState()
    let mutation = CounterMutation()

    mutation.mutate(action: .increment, state: &state)
    XCTAssertEqual(state.count, 1)

    mutation.mutate(action: .decrement, state: &state)
    XCTAssertEqual(state.count, 0)
}

Example

Project

You can try Prex with GitHub Repository Search Application Example. Open PrexSample.xcworkspace and run it!

Playground

You can try Prex counter sample with Playground! Open Prex.xcworkspace and build Prex-iOS. Finally, you can run manually in Playground.

Requirements

  • Xcode 9.4.1 or greater
  • iOS 10.0 or greater
  • tvOS 10.0 or greater
  • macOS 10.10 or greater
  • watchOS 3.0 or greater
  • Swift 4.1 or greater

Installation

Carthage

If you’re using Carthage, simply add Prex to your Cartfile:

github "marty-suzuki/Prex"

CocoaPods

Prex is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Prex'

Swift Package Manager

Prex is available through Swift Package Manager. Just add the url of this repository to your Package.swift.

dependencies: [
    .package(url: "https://github.com/marty-suzuki/Prex.git", from: "0.2.0")
]

Inspired by these unidirectional data flow frameworks

Author

marty-suzuki, [email protected]

License

Prex is available under the MIT license. See the LICENSE file for more info.

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].