Skip to content

Repository files navigation

OneWay

OneWayKit ♻️

releaselicenselicenselicense

OneWayKit is a reactive unidirectional architecture library built with Combine. It allows you to update the state based on user actions and reflect those changes in the view seamlessly. The library is designed to be simple and easy to integrate into any feature of your application.

Table of Contents

Basic Usage

1. State, Action, Updater, and Middleware are defined through ViewFeature.

ViewFeature

ViewFeature defines four key components:

  • State: Represents the state of the view.
  • Action: Captures user interactions.
  • Updater: Updates the state based on actions.
structTimerFeature:ViewFeature{structState:ViewState{varcurrentTime:Float=0varisStarted:Bool=falsevarinterval:TimeInterval=0.1}enumAction:ViewAction{case start
case add
case tapRightButton
case toggleStart
}staticvarupdater:Updater={ state, action invarnewState= state
switch action {case.add:
newState.currentTime +=Float(state.interval)case.toggleStart:
newState.isStarted.toggle()default:break}return newState
}}

Middleware

Middleware plays a key role in handling asynchronous tasks by processing actions from a specific feature and emitting desired actions. Using Combine, it can also emit periodic actions, and subscriptions can be canceled using the cancel(for:) method.

finalclassTimerMiddleware:Middleware{func send(_ action:ViewAction, currentState:anyViewState)->AnyPublisher<ViewAction,Never>{guardlet currentState = currentState as?TimerFeature.Stateelse{returnEmpty().eraseToAnyPublisher()}switch action as?TimerFeature.Action {case.start:returnTimer.publish(every: currentState.interval, on:.main, in:RunLoop.Mode.common).autoconnect().map{ _ inTimerFeature.Action.add
}.eraseToAnyPublisher()case.tapRightButton:if currentState.isStarted {returnPublishers.Merge(Just(TimerFeature.Action.cancel(for:TimerFeature.Action.start)),Just(TimerFeature.Action.toggleStart)).eraseToAnyPublisher()}else{returnPublishers.Merge(Just(TimerFeature.Action.start),Just(TimerFeature.Action.toggleStart)).eraseToAnyPublisher()}default:returnEmpty().eraseToAnyPublisher()}}}

2. Now, we use the previously defined ViewFeature to create a OneWay that represents the unidirectional flow of the view, set the initial state, and inject middlewares for side effects.

finalclassTimerViewController:UIViewController{privateletoneway=OneWay<TimerFeature>(initialState:.init(), middlewares:[TimerMiddleWare()])...

3. Subscribe to the state of OneWay to bind and update the UI accordingly when changes occur.

privatefunc setupOneWay(){
oneway.statePublisher
.map{String(format:"%.1f", $0.currentTime)}.assign(to: \.text, on: timeLabel).store(in:&cancellables)
oneway.statePublisher
.map{ $0.isStarted }.removeDuplicates().sink{[weak self] isStarted inself?.navigationItem.rightBarButtonItem?.title = isStarted ?"Stop":"Start"}.store(in:&cancellables)}

4. Send actions corresponding to user interactions.

@objcprivatefunc tapLeftButton(){
oneway.send(.left)}@objcprivatefunc tapRightButton(){
oneway.send(.right)}@objcprivatefunc tapUpButton(){
oneway.send(.up)}@objcprivatefunc tapDownButton(){
oneway.send(.down)}

Key Features

  1. Tracer

When creating onewaykit, set the context first.

private lazy varoneway=OneWay<TracerFeature>(
initialState:.init(
position:.init(x: view.center.x, y: view.center.y),
size:.init(width:100, height:100)),
context:TracerViewController.self
)

If you want to track actions and state changes, you can use shouldTrace to trace them when sending actions, as shown below:

@objcprivatefunc tapLeftButton(){
oneway.send(.left, shouldTrace:true)}

By setting shouldTrace to true, you can observe events that capture the context, action, and how the state changes as a result, as shown below:

스크린샷 2024-12-24 오후 11 31 19
  1. Global

First, register the ViewFeature to be used globally and set the initial state.

GlobalOneWay.registerState(feature:GlobalFeature.self, initialState:.init())

Then, you can subscribe to and use the state of the desired global feature.

GlobalOneWay.state(feature:GlobalFeature.self)?.map{ $0.backgroundColor }.sink{[weak self]inself?.titleLabel.backgroundColor = $0
}.store(in:&cancellables)

You can also send actions to the global feature, of course.

GlobalOneWay.send(feature:GlobalFeature.self,.setBackgroundColor(.white))

Examples

This project allows you to add a To-Do List and learn how to detect child actions and update the parent view accordingly.

This project allows you to learn how to implement a timer asynchronously using Middleware and handle cancellation of subscribed events.

This project allows you to learn how to detect triggered actions and track state changes accordingly.

This project allows you to create a global feature, subscribe to it from multiple views, and dispatch actions accordingly.

Installation

You can install OneWayKit via Swift Package Manager by adding the following line to your Package.swift:

import PackageDescription
letpackage=Package([...]dependencies:[.package(url:"https: github.com/fomagran/OneWayKit", from:"1.2.0"),])

References

The following projects have greatly inspired the creation of OneWayKit.

Author

Fomagran, fomagran@icloud.com

License

This library is released under the MIT license. See LICENSE for details.

About

OneWayKit is a reactive unidirectional architecture library using Combine ♻️

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages