Skip to content

Instantly share code, notes, and snippets.

@pyrou
Last active July 3, 2019 22:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pyrou/4f2a17f481eb82d339510c31c74d9baf to your computer and use it in GitHub Desktop.
Save pyrou/4f2a17f481eb82d339510c31c74d9baf to your computer and use it in GitHub Desktop.
SwiftUI Todo list prototype using ReSwift
//
// BindableStore.swift
// SwiftUIExperiment
//
// Created by Michael Hurni on 06/06/2019.
// Copyright © 2019 SwitchKit. All rights reserved.
//
import Combine
import SwiftUI
final class BindableStore<S>: BindableObject, StoreSubscriber where S: StateType {
public typealias StoreSubscriberStateType = S
var didChange = PassthroughSubject<BindableStore, Never>()
private(set) var state: S {
didSet {
didChange.send(self)
}
}
private let store: Store<S>
func dispatch(_ action: Action) -> Void {
store.dispatch(action)
}
/// Initializes the store with a reducer, an initial state and a list of middleware.
///
/// Middleware is applied in the order in which it is passed into this constructor.
///
/// - parameter reducer: Main reducer that processes incoming actions.
/// - parameter state: Initial state, if any. Can be `nil` and will be
/// provided by the reducer in that case.
/// - parameter middleware: Ordered list of action pre-processors, acting
/// before the root reducer.
/// - parameter automaticallySkipsRepeats: If `true`, the store will attempt
/// to skip idempotent state updates when a subscriber's state type
/// implements `Equatable`. Defaults to `true`.
init(
reducer: @escaping Reducer<S>,
state: S?,
middleware: [Middleware<S>] = [],
automaticallySkipsRepeats: Bool = true
) {
store = Store(reducer: reducer, state: state, middleware: middleware, automaticallySkipsRepeats: automaticallySkipsRepeats)
self.state = store.state
defer { store.subscribe(self) }
}
public func newState(state: S) {
self.state = state
}
}
//
// ContentView.swift
// SwiftUIExperiment
//
// Created by Michael Hurni on 05/06/2019.
// Copyright © 2019 SwitchKit. All rights reserved.
//
import Combine
import SwiftUI
struct ContentView : View {
@EnvironmentObject var store: BindableStore<AppState>
@State var newTodoTitle: String = ""
var body: some View {
NavigationView {
List {
TextField($newTodoTitle, placeholder: Text("Add"), onCommit: self.save)
ForEach(self.store.state.todos) { todo in
Button(action: {
if todo.done {
self.store.dispatch(Undone(id: todo.id))
} else {
self.store.dispatch(Done(id: todo.id))
}
}) { Text(todo.title).strikethrough(todo.done).color(.black) }
}
}
.listStyle(.grouped)
.navigationBarTitle(Text("Todo"))
.navigationBarItems(trailing: Button(action: { self.store.dispatch(Clear()) }) {
Text("Clear done").color(.red)
})
}
}
func save() {
self.store.dispatch(AddTodo(title: self.newTodoTitle))
self.newTodoTitle = ""
}
}
// MARK: - Reducer
func appReducer(action: Action, state: AppState?) -> AppState {
var state = state ?? AppState(todos: [])
switch action {
case let action as AddTodo:
let id = (state.todos.map { $0.id }.max() ?? 0) + 1
state.todos.append(Todo(id: id, title: action.title ?? "new item " + String(id), done: false))
case _ as Clear:
state.todos = state.todos.filter { $0.done == false }
case let done as Done:
state.todos = state.todos.map { todo in
var todo = todo
if todo.id == done.id { todo.done = true }
return todo
}
case let done as Undone:
state.todos = state.todos.map { todo in
var todo = todo
if todo.id == done.id { todo.done = false }
return todo
}
default: break
}
state.todos = state.todos.sorted { a, b in
a.done != b.done ? b.done : a.title < b.title
}
return state
}
// MARK: - Action
struct AddTodo: Action { let title: String? }
struct Clear: Action {}
struct Sort: Action {}
struct Undone: Action { let id: Int}
struct Done: Action { let id: Int}
// MARK: - State
struct AppState: StateType, Equatable {
var todos: [Todo]
}
struct Todo: Identifiable, Equatable {
var id: Int
var title: String
var done: Bool
}
//
// Action.swift
// ReSwift
//
// Created by Benjamin Encz on 12/14/15.
// Copyright © 2015 Benjamin Encz. All rights reserved.
//
/// All actions that want to be able to be dispatched to a store need to conform to this protocol
/// Currently it is just a marker protocol with no requirements.
public protocol Action { }
/// Initial Action that is dispatched as soon as the store is created.
/// Reducers respond to this action by configuring their initial state.
public struct ReSwiftInit: Action {}
import Foundation
/**
Defines the interface of a dispatching, stateless Store in ReSwift. `StoreType` is
the default usage of this interface. Can be used for store variables where you don't
care about the state, but want to be able to dispatch actions.
*/
public protocol DispatchingStoreType {
/**
Dispatches an action. This is the simplest way to modify the stores state.
Example of dispatching an action:
```
store.dispatch( CounterAction.IncreaseCounter )
```
- parameter action: The action that is being dispatched to the store
*/
func dispatch(_ action: Action)
}
//
// Middleware.swift
// ReSwift
//
// Created by Benji Encz on 12/24/15.
// Copyright © 2015 Benjamin Encz. All rights reserved.
//
public typealias DispatchFunction = (Action) -> Void
public typealias Middleware<State> = (@escaping DispatchFunction, @escaping () -> State?)
-> (@escaping DispatchFunction) -> DispatchFunction
//
// Reducer.swift
// ReSwift
//
// Created by Benjamin Encz on 12/14/15.
// Copyright © 2015 Benjamin Encz. All rights reserved.
//
public typealias Reducer<ReducerStateType> =
(_ action: Action, _ state: ReducerStateType?) -> ReducerStateType
//
// State.swift
// ReSwift
//
// Created by Benjamin Encz on 12/14/15.
// Copyright © 2015 Benjamin Encz. All rights reserved.
//
public protocol StateType { }
//
// Store.swift
// ReSwift
//
// Created by Benjamin Encz on 11/11/15.
// Copyright © 2015 DigiTales. All rights reserved.
//
/**
This class is the default implementation of the `Store` protocol. You will use this store in most
of your applications. You shouldn't need to implement your own store.
You initialize the store with a reducer and an initial application state. If your app has multiple
reducers you can combine them by initializng a `MainReducer` with all of your reducers as an
argument.
*/
open class Store<State: StateType>: StoreType {
typealias SubscriptionType = SubscriptionBox<State>
private(set) public var state: State! {
didSet {
subscriptions.forEach {
if $0.subscriber == nil {
subscriptions.remove($0)
} else {
$0.newValues(oldState: oldValue, newState: state)
}
}
}
}
public var dispatchFunction: DispatchFunction!
private var reducer: Reducer<State>
var subscriptions: Set<SubscriptionType> = []
private var isDispatching = false
/// Indicates if new subscriptions attempt to apply `skipRepeats`
/// by default.
fileprivate let subscriptionsAutomaticallySkipRepeats: Bool
/// Initializes the store with a reducer, an initial state and a list of middleware.
///
/// Middleware is applied in the order in which it is passed into this constructor.
///
/// - parameter reducer: Main reducer that processes incoming actions.
/// - parameter state: Initial state, if any. Can be `nil` and will be
/// provided by the reducer in that case.
/// - parameter middleware: Ordered list of action pre-processors, acting
/// before the root reducer.
/// - parameter automaticallySkipsRepeats: If `true`, the store will attempt
/// to skip idempotent state updates when a subscriber's state type
/// implements `Equatable`. Defaults to `true`.
public required init(
reducer: @escaping Reducer<State>,
state: State?,
middleware: [Middleware<State>] = [],
automaticallySkipsRepeats: Bool = true
) {
self.subscriptionsAutomaticallySkipRepeats = automaticallySkipsRepeats
self.reducer = reducer
// Wrap the dispatch function with all middlewares
self.dispatchFunction = middleware
.reversed()
.reduce(
{ [unowned self] action in
self._defaultDispatch(action: action) },
{ dispatchFunction, middleware in
// If the store get's deinitialized before the middleware is complete; drop
// the action without dispatching.
let dispatch: (Action) -> Void = { [weak self] in self?.dispatch($0) }
let getState = { [weak self] in self?.state }
return middleware(dispatch, getState)(dispatchFunction)
})
if let state = state {
self.state = state
} else {
dispatch(ReSwiftInit())
}
}
fileprivate func _subscribe<SelectedState, S: StoreSubscriber>(
_ subscriber: S, originalSubscription: Subscription<State>,
transformedSubscription: Subscription<SelectedState>?)
where S.StoreSubscriberStateType == SelectedState
{
let subscriptionBox = self.subscriptionBox(
originalSubscription: originalSubscription,
transformedSubscription: transformedSubscription,
subscriber: subscriber
)
subscriptions.update(with: subscriptionBox)
if let state = self.state {
originalSubscription.newValues(oldState: nil, newState: state)
}
}
open func subscribe<S: StoreSubscriber>(_ subscriber: S)
where S.StoreSubscriberStateType == State {
_ = subscribe(subscriber, transform: nil)
}
open func subscribe<SelectedState, S: StoreSubscriber>(
_ subscriber: S, transform: ((Subscription<State>) -> Subscription<SelectedState>)?
) where S.StoreSubscriberStateType == SelectedState
{
// Create a subscription for the new subscriber.
let originalSubscription = Subscription<State>()
// Call the optional transformation closure. This allows callers to modify
// the subscription, e.g. in order to subselect parts of the store's state.
let transformedSubscription = transform?(originalSubscription)
_subscribe(subscriber, originalSubscription: originalSubscription,
transformedSubscription: transformedSubscription)
}
func subscriptionBox<T>(
originalSubscription: Subscription<State>,
transformedSubscription: Subscription<T>?,
subscriber: AnyStoreSubscriber
) -> SubscriptionBox<State> {
return SubscriptionBox(
originalSubscription: originalSubscription,
transformedSubscription: transformedSubscription,
subscriber: subscriber
)
}
open func unsubscribe(_ subscriber: AnyStoreSubscriber) {
#if swift(>=5.0)
if let index = subscriptions.firstIndex(where: { return $0.subscriber === subscriber }) {
subscriptions.remove(at: index)
}
#else
if let index = subscriptions.index(where: { return $0.subscriber === subscriber }) {
subscriptions.remove(at: index)
}
#endif
}
// swiftlint:disable:next identifier_name
open func _defaultDispatch(action: Action) {
guard !isDispatching else {
raiseFatalError(
"ReSwift:ConcurrentMutationError- Action has been dispatched while" +
" a previous action is action is being processed. A reducer" +
" is dispatching an action, or ReSwift is used in a concurrent context" +
" (e.g. from multiple threads)."
)
}
isDispatching = true
let newState = reducer(action, state)
isDispatching = false
state = newState
}
open func dispatch(_ action: Action) {
dispatchFunction(action)
}
@available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
open func dispatch(_ actionCreatorProvider: @escaping ActionCreator) {
if let action = actionCreatorProvider(state, self) {
dispatch(action)
}
}
@available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
open func dispatch(_ asyncActionCreatorProvider: @escaping AsyncActionCreator) {
dispatch(asyncActionCreatorProvider, callback: nil)
}
@available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
open func dispatch(_ actionCreatorProvider: @escaping AsyncActionCreator,
callback: DispatchCallback?) {
actionCreatorProvider(state, self) { actionProvider in
let action = actionProvider(self.state, self)
if let action = action {
self.dispatch(action)
callback?(self.state)
}
}
}
public typealias DispatchCallback = (State) -> Void
@available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
public typealias ActionCreator = (_ state: State, _ store: Store) -> Action?
@available(*, deprecated, message: "Deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk")
public typealias AsyncActionCreator = (
_ state: State,
_ store: Store,
_ actionCreatorCallback: @escaping ((ActionCreator) -> Void)
) -> Void
}
// MARK: Skip Repeats for Equatable States
extension Store {
open func subscribe<SelectedState: Equatable, S: StoreSubscriber>(
_ subscriber: S, transform: ((Subscription<State>) -> Subscription<SelectedState>)?
) where S.StoreSubscriberStateType == SelectedState
{
let originalSubscription = Subscription<State>()
var transformedSubscription = transform?(originalSubscription)
if subscriptionsAutomaticallySkipRepeats {
transformedSubscription = transformedSubscription?.skipRepeats()
}
_subscribe(subscriber, originalSubscription: originalSubscription,
transformedSubscription: transformedSubscription)
}
}
extension Store where State: Equatable {
open func subscribe<S: StoreSubscriber>(_ subscriber: S)
where S.StoreSubscriberStateType == State {
guard subscriptionsAutomaticallySkipRepeats else {
_ = subscribe(subscriber, transform: nil)
return
}
_ = subscribe(subscriber, transform: { $0.skipRepeats() })
}
}
//
// StoreSubscriber.swift
// ReSwift
//
// Created by Benjamin Encz on 12/14/15.
// Copyright © 2015 Benjamin Encz. All rights reserved.
//
public protocol AnyStoreSubscriber: class {
// swiftlint:disable:next identifier_name
func _newState(state: Any)
}
public protocol StoreSubscriber: AnyStoreSubscriber {
associatedtype StoreSubscriberStateType
func newState(state: StoreSubscriberStateType)
}
extension StoreSubscriber {
// swiftlint:disable:next identifier_name
public func _newState(state: Any) {
if let typedState = state as? StoreSubscriberStateType {
newState(state: typedState)
}
}
}
//
// StoreType.swift
// ReSwift
//
// Created by Benjamin Encz on 11/28/15.
// Copyright © 2015 DigiTales. All rights reserved.
//
/**
Defines the interface of Stores in ReSwift. `Store` is the default implementation of this
interface. Applications have a single store that stores the entire application state.
Stores receive actions and use reducers combined with these actions, to calculate state changes.
Upon every state update a store informs all of its subscribers.
*/
public protocol StoreType: DispatchingStoreType {
associatedtype State: StateType
/// The current state stored in the store.
var state: State! { get }
/**
The main dispatch function that is used by all convenience `dispatch` methods.
This dispatch function can be extended by providing middlewares.
*/
var dispatchFunction: DispatchFunction! { get }
/**
Subscribes the provided subscriber to this store.
Subscribers will receive a call to `newState` whenever the
state in this store changes.
- parameter subscriber: Subscriber that will receive store updates
- note: Subscriptions are not ordered, so an order of state updates cannot be guaranteed.
*/
func subscribe<S: StoreSubscriber>(_ subscriber: S) where S.StoreSubscriberStateType == State
/**
Subscribes the provided subscriber to this store.
Subscribers will receive a call to `newState` whenever the
state in this store changes and the subscription decides to forward
state update.
- parameter subscriber: Subscriber that will receive store updates
- parameter transform: A closure that receives a simple subscription and can return a
transformed subscription. Subscriptions can be transformed to only select a subset of the
state, or to skip certain state updates.
- note: Subscriptions are not ordered, so an order of state updates cannot be guaranteed.
*/
func subscribe<SelectedState, S: StoreSubscriber>(
_ subscriber: S, transform: ((Subscription<State>) -> Subscription<SelectedState>)?
) where S.StoreSubscriberStateType == SelectedState
/**
Unsubscribes the provided subscriber. The subscriber will no longer
receive state updates from this store.
- parameter subscriber: Subscriber that will be unsubscribed
*/
func unsubscribe(_ subscriber: AnyStoreSubscriber)
/**
Dispatches an action creator to the store. Action creators are functions that generate
actions. They are called by the store and receive the current state of the application
and a reference to the store as their input.
Based on that input the action creator can either return an action or not. Alternatively
the action creator can also perform an asynchronous operation and dispatch a new action
at the end of it.
Example of an action creator:
```
func deleteNote(noteID: Int) -> ActionCreator {
return { state, store in
// only delete note if editing is enabled
if (state.editingEnabled == true) {
return NoteDataAction.DeleteNote(noteID)
} else {
return nil
}
}
}
```
This action creator can then be dispatched as following:
```
store.dispatch( noteActionCreatore.deleteNote(3) )
```
*/
func dispatch(_ actionCreator: ActionCreator)
/**
Dispatches an async action creator to the store. An async action creator generates an
action creator asynchronously.
*/
func dispatch(_ asyncActionCreator: AsyncActionCreator)
/**
Dispatches an async action creator to the store. An async action creator generates an
action creator asynchronously. Use this method if you want to wait for the state change
triggered by the asynchronously generated action creator.
This overloaded version of `dispatch` calls the provided `callback` as soon as the
asynchronoously dispatched action has caused a new state calculation.
- Note: If the ActionCreator does not dispatch an action, the callback block will never
be called
*/
func dispatch(_ asyncActionCreator: AsyncActionCreator, callback: DispatchCallback?)
/**
An optional callback that can be passed to the `dispatch` method.
This callback will be called when the dispatched action triggers a new state calculation.
This is useful when you need to wait on a state change, triggered by an action (e.g. wait on
a successful login). However, you should try to use this callback very seldom as it
deviates slighlty from the unidirectional data flow principal.
*/
associatedtype DispatchCallback = (State) -> Void
/**
An ActionCreator is a function that, based on the received state argument, might or might not
create an action.
Example:
```
func deleteNote(noteID: Int) -> ActionCreator {
return { state, store in
// only delete note if editing is enabled
if (state.editingEnabled == true) {
return NoteDataAction.DeleteNote(noteID)
} else {
return nil
}
}
}
```
*/
associatedtype ActionCreator = (_ state: State, _ store: StoreType) -> Action?
/// AsyncActionCreators allow the developer to wait for the completion of an async action.
associatedtype AsyncActionCreator =
(_ state: State, _ store: StoreType,
_ actionCreatorCallback: (ActionCreator) -> Void) -> Void
}
//
// SubscriberWrapper.swift
// ReSwift
//
// Created by Virgilio Favero Neto on 4/02/2016.
// Copyright © 2016 Benjamin Encz. All rights reserved.
//
/// A box around subscriptions and subscribers.
///
/// Acts as a type-erasing wrapper around a subscription and its transformed subscription.
/// The transformed subscription has a type argument that matches the selected substate of the
/// subscriber; however that type cannot be exposed to the store.
///
/// The box subscribes either to the original subscription, or if available to the transformed
/// subscription and passes any values that come through this subscriptions to the subscriber.
class SubscriptionBox<State>: Hashable {
private let originalSubscription: Subscription<State>
weak var subscriber: AnyStoreSubscriber?
private let objectIdentifier: ObjectIdentifier
#if swift(>=5.0)
func hash(into hasher: inout Hasher) {
hasher.combine(self.objectIdentifier)
}
#elseif swift(>=4.2)
#if compiler(>=5.0)
func hash(into hasher: inout Hasher) {
hasher.combine(self.objectIdentifier)
}
#else
var hashValue: Int {
return self.objectIdentifier.hashValue
}
#endif
#else
var hashValue: Int {
return self.objectIdentifier.hashValue
}
#endif
init<T>(
originalSubscription: Subscription<State>,
transformedSubscription: Subscription<T>?,
subscriber: AnyStoreSubscriber
) {
self.originalSubscription = originalSubscription
self.subscriber = subscriber
self.objectIdentifier = ObjectIdentifier(subscriber)
// If we received a transformed subscription, we subscribe to that subscription
// and forward all new values to the subscriber.
if let transformedSubscription = transformedSubscription {
transformedSubscription.observer = { [unowned self] _, newState in
self.subscriber?._newState(state: newState as Any)
}
// If we haven't received a transformed subscription, we forward all values
// from the original subscription.
} else {
originalSubscription.observer = { [unowned self] _, newState in
self.subscriber?._newState(state: newState as Any)
}
}
}
func newValues(oldState: State, newState: State) {
// We pass all new values through the original subscription, which accepts
// values of type `<State>`. If present, transformed subscriptions will
// receive this update and transform it before passing it on to the subscriber.
self.originalSubscription.newValues(oldState: oldState, newState: newState)
}
static func == (left: SubscriptionBox<State>, right: SubscriptionBox<State>) -> Bool {
return left.objectIdentifier == right.objectIdentifier
}
}
/// Represents a subscription of a subscriber to the store. The subscription determines which new
/// values from the store are forwarded to the subscriber, and how they are transformed.
/// The subscription acts as a very-light weight signal/observable that you might know from
/// reactive programming libraries.
public class Subscription<State> {
private func _select<Substate>(
_ selector: @escaping (State) -> Substate
) -> Subscription<Substate>
{
return Subscription<Substate> { sink in
self.observer = { oldState, newState in
sink(oldState.map(selector) ?? nil, selector(newState))
}
}
}
// MARK: Public Interface
/// Initializes a subscription with a sink closure. The closure provides a way to send
/// new values over this subscription.
public init(sink: @escaping (@escaping (State?, State) -> Void) -> Void) {
// Provide the caller with a closure that will forward all values
// to observers of this subscription.
sink { old, new in
self.newValues(oldState: old, newState: new)
}
}
/// Provides a subscription that selects a substate of the state of the original subscription.
/// - parameter selector: A closure that maps a state to a selected substate
public func select<Substate>(
_ selector: @escaping (State) -> Substate
) -> Subscription<Substate>
{
return self._select(selector)
}
/// Provides a subscription that skips certain state updates of the original subscription.
/// - parameter isRepeat: A closure that determines whether a given state update is a repeat and
/// thus should be skipped and not forwarded to subscribers.
/// - parameter oldState: The store's old state, before the action is reduced.
/// - parameter newState: The store's new state, after the action has been reduced.
public func skipRepeats(_ isRepeat: @escaping (_ oldState: State, _ newState: State) -> Bool)
-> Subscription<State> {
return Subscription<State> { sink in
self.observer = { oldState, newState in
switch (oldState, newState) {
case let (old?, new):
if !isRepeat(old, new) {
sink(oldState, newState)
} else {
return
}
default:
sink(oldState, newState)
}
}
}
}
/// The closure called with changes from the store.
/// This closure can be written to for use in extensions to Subscription similar to `skipRepeats`
public var observer: ((State?, State) -> Void)?
// MARK: Internals
init() {}
/// Sends new values over this subscription. Observers will be notified of these new values.
func newValues(oldState: State?, newState: State) {
self.observer?(oldState, newState)
}
}
extension Subscription where State: Equatable {
public func skipRepeats() -> Subscription<State>{
return self.skipRepeats(==)
}
}
/// Subscription skipping convenience methods
extension Subscription {
/// Provides a subscription that skips certain state updates of the original subscription.
///
/// This is identical to `skipRepeats` and is provided simply for convenience.
/// - parameter when: A closure that determines whether a given state update is a repeat and
/// thus should be skipped and not forwarded to subscribers.
/// - parameter oldState: The store's old state, before the action is reduced.
/// - parameter newState: The store's new state, after the action has been reduced.
public func skip(when: @escaping (_ oldState: State, _ newState: State) -> Bool) -> Subscription<State> {
return self.skipRepeats(when)
}
/// Provides a subscription that only updates for certain state changes.
///
/// This is effectively the inverse of `skip(when:)` / `skipRepeats(:)`
/// - parameter when: A closure that determines whether a given state update should notify
/// - parameter oldState: The store's old state, before the action is reduced.
/// - parameter newState: The store's new state, after the action has been reduced.
/// the subscriber.
public func only(when: @escaping (_ oldState: State, _ newState: State) -> Bool) -> Subscription<State> {
return self.skipRepeats { oldState, newState in
return !when(oldState, newState)
}
}
}
//
// Assertions
// Copyright © 2015 mohamede1945. All rights reserved.
// https://github.com/mohamede1945/AssertionsTestingExample
//
import Foundation
/// drop-in fatalError replacement for testing
/**
Swift.fatalError wrapper for catching in tests
- parameter message: Message to be wrapped
- parameter file: Calling file
- parameter line: Calling line
*/
func raiseFatalError(_ message: @autoclosure () -> String = "",
file: StaticString = #file, line: UInt = #line) -> Never {
Assertions.fatalErrorClosure(message(), file, line)
repeat {
RunLoop.current.run()
} while (true)
}
/// Stores custom assertions closures, by default it points to Swift functions. But test target can
/// override them.
class Assertions {
static var fatalErrorClosure = swiftFatalErrorClosure
static let swiftFatalErrorClosure: (String, StaticString, UInt) -> Void
= { Swift.fatalError($0, file: $1, line: $2) }
}
//
// Types.swift
// ReSwift
//
// Created by Benjamin Encz on 11/27/15.
// Copyright © 2015 DigiTales. All rights reserved.
//
public protocol Coding {
init?(dictionary: [String: AnyObject])
var dictionaryRepresentation: [String: AnyObject] { get }
}
//
// TypeHelper.swift
// ReSwift
//
// Created by Benjamin Encz on 11/27/15.
// Copyright © 2015 DigiTales. All rights reserved.
//
/**
Method is only used internally in ReSwift to cast the generic `StateType` to a specific
type expected by reducers / store subscribers.
- parameter action: An action that will be passed to `handleAction`.
- parameter state: A generic state type that will be casted to `SpecificStateType`.
- parameter function: The `handleAction` method.
- returns: A `StateType` from `handleAction` or the original `StateType` if it cannot be
casted to `SpecificStateType`.
*/
@discardableResult
func withSpecificTypes<SpecificStateType, Action>(
_ action: Action,
state genericStateType: StateType?,
function: (_ action: Action, _ state: SpecificStateType?) -> SpecificStateType
) -> StateType {
guard let genericStateType = genericStateType else {
return function(action, nil) as! StateType
}
guard let specificStateType = genericStateType as? SpecificStateType else {
return genericStateType
}
return function(action, specificStateType) as! StateType
}
@pyrou
Copy link
Author

pyrou commented Jun 5, 2019

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment