-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: custom store implementation #810
Merged
mrehan27
merged 3 commits into
feature/inapp-state-refactor
from
rehan/mbl-480-custom-store
Sep 12, 2024
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// swiftlint:disable:next file_header | ||
// | ||
// 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. | ||
enum Assertions { | ||
static var fatalErrorClosure = swiftFatalErrorClosure | ||
static let swiftFatalErrorClosure: (String, StaticString, UInt) -> Void | ||
= { Swift.fatalError($0, file: $1, line: $2) } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// swiftlint:disable:next file_header | ||
// | ||
// Middleware.swift | ||
// ReSwift | ||
// | ||
// Created by Benji Encz on 12/24/15. | ||
// Copyright © 2015 ReSwift Community. All rights reserved. | ||
// | ||
// Modifications made: | ||
// - Replaced Action with InAppMessageAction from Customer.io. | ||
// - Updated visibility to internal to prevent exposing non-public types. | ||
// | ||
|
||
typealias DispatchFunction = (InAppMessageAction) -> Void | ||
typealias Middleware<State> = (@escaping DispatchFunction, @escaping () -> State?) | ||
-> (@escaping DispatchFunction) -> DispatchFunction |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// swiftlint:disable:next file_header | ||
// | ||
// Reducer.swift | ||
// ReSwift | ||
// | ||
// Created by Benjamin Encz on 12/14/15. | ||
// Copyright © 2015 ReSwift Community. All rights reserved. | ||
// | ||
// Modifications made: | ||
// - Replaced Action with InAppMessageAction from Customer.io. | ||
// - Updated visibility to internal to prevent exposing non-public types. | ||
// | ||
|
||
typealias Reducer<ReducerStateType> = | ||
(_ action: InAppMessageAction, _ state: ReducerStateType?) -> ReducerStateType |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// swiftlint:disable:next file_header | ||
// | ||
// Store.swift | ||
// ReSwift | ||
// | ||
// Created by Benjamin Encz on 11/11/15. | ||
// Copyright © 2015 ReSwift Community. All rights reserved. | ||
// | ||
// Modifications made: | ||
// - Replaced Action with InAppMessageAction from Customer.io. | ||
// - Constrained State to Equatable to simplify state comparison as InAppMessageState is Equatable. | ||
// - Updated visibility to internal to prevent exposing non-public types. | ||
// - Updated initializer to require non-optional initial State to avoid dispatching dummy init action. | ||
// - Removed subscriptionsAutomaticallySkipRepeats as it will always be true. | ||
// - Simplified subscription handling by removing unused subscription options. | ||
// - Removed unused functions. | ||
// | ||
|
||
/** | ||
This class is the default implementation of the `StoreType` 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 initializing a `MainReducer` with all of your reducers as an | ||
argument. | ||
*/ | ||
class Store<State: Equatable> { | ||
typealias SubscriptionType = SubscriptionBox<State> | ||
|
||
public private(set) var state: State! { | ||
didSet { | ||
subscriptions.forEach { | ||
if $0.subscriber == nil { | ||
subscriptions.remove($0) | ||
} else { | ||
$0.newValues(oldState: oldValue, newState: state) | ||
} | ||
} | ||
} | ||
} | ||
|
||
public lazy var dispatchFunction: DispatchFunction! = createDispatchFunction() | ||
|
||
private var reducer: Reducer<State> | ||
|
||
var subscriptions: Set<SubscriptionType> = [] | ||
|
||
private var isDispatching = Synchronized<Bool>(false) | ||
|
||
public var middleware: [Middleware<State>] { | ||
didSet { | ||
dispatchFunction = createDispatchFunction() | ||
} | ||
} | ||
|
||
/// 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>] = [] | ||
) { | ||
self.reducer = reducer | ||
self.middleware = middleware | ||
self.state = state | ||
} | ||
|
||
private func createDispatchFunction() -> DispatchFunction! { | ||
// Wrap the dispatch function with all middlewares | ||
middleware | ||
.reversed() | ||
.reduce({ [unowned self] action in | ||
_defaultDispatch(action: action) }, { dispatchFunction, middleware in | ||
// If the store get's deinitialized before the middleware is complete; drop | ||
// the action without dispatching. | ||
let dispatch: (InAppMessageAction) -> Void = { [weak self] in self?.dispatch($0) } | ||
let getState: () -> State? = { [weak self] in self?.state } | ||
return middleware(dispatch, getState)(dispatchFunction) | ||
} | ||
) | ||
} | ||
|
||
private func _subscribe<S: StoreSubscriber>( | ||
_ subscriber: S, | ||
originalSubscription: Subscription<State>, | ||
transformedSubscription: Subscription<State>? | ||
) where S.StoreSubscriberStateType == State { | ||
let subscriptionBox = self.subscriptionBox( | ||
originalSubscription: originalSubscription, | ||
transformedSubscription: transformedSubscription, | ||
subscriber: subscriber | ||
) | ||
|
||
subscriptions.update(with: subscriptionBox) | ||
|
||
if let state = state { | ||
originalSubscription.newValues(oldState: nil, newState: state) | ||
} | ||
} | ||
|
||
public func subscribe<S: StoreSubscriber>( | ||
_ subscriber: S, | ||
transform: ((Subscription<State>) -> Subscription<State>) = { $0.skipRepeats() } | ||
) where S.StoreSubscriberStateType == State { | ||
let originalSubscription = Subscription<State>() | ||
|
||
_subscribe( | ||
subscriber, | ||
originalSubscription: originalSubscription, | ||
transformedSubscription: transform(originalSubscription) | ||
) | ||
} | ||
|
||
func subscriptionBox<T>( | ||
originalSubscription: Subscription<State>, | ||
transformedSubscription: Subscription<T>?, | ||
subscriber: AnyStoreSubscriber | ||
) -> SubscriptionBox<State> { | ||
SubscriptionBox( | ||
originalSubscription: originalSubscription, | ||
transformedSubscription: transformedSubscription, | ||
subscriber: subscriber | ||
) | ||
} | ||
|
||
func unsubscribe(_ subscriber: AnyStoreSubscriber) { | ||
#if swift(>=5.0) | ||
if let index = subscriptions.firstIndex(where: { $0.subscriber === subscriber }) { | ||
subscriptions.remove(at: index) | ||
} | ||
#else | ||
if let index = subscriptions.index(where: { $0.subscriber === subscriber }) { | ||
subscriptions.remove(at: index) | ||
} | ||
#endif | ||
} | ||
|
||
// swiftlint:disable:next identifier_name | ||
func _defaultDispatch(action: InAppMessageAction) { | ||
guard !isDispatching.value else { | ||
raiseFatalError( | ||
"ReSwift:ConcurrentMutationError- Action has been dispatched while" + | ||
" a previous action is being processed. A reducer" + | ||
" is dispatching an action, or ReSwift is used in a concurrent context" + | ||
" (e.g. from multiple threads). Action: \(action)" | ||
) | ||
} | ||
|
||
isDispatching.value { $0 = true } | ||
let newState = reducer(action, state) | ||
isDispatching.value { $0 = false } | ||
|
||
state = newState | ||
} | ||
|
||
open func dispatch(_ action: InAppMessageAction) { | ||
dispatchFunction(action) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// swiftlint:disable:next file_header | ||
// | ||
// StoreSubscriber.swift | ||
// ReSwift | ||
// | ||
// Created by Benjamin Encz on 12/14/15. | ||
// Copyright © 2015 ReSwift Community. All rights reserved. | ||
// | ||
|
||
public protocol AnyStoreSubscriber: AnyObject { | ||
// swiftlint:disable:next identifier_name | ||
func _newState(state: Any) | ||
} | ||
|
||
public protocol StoreSubscriber: AnyStoreSubscriber { | ||
associatedtype StoreSubscriberStateType | ||
|
||
func newState(state: StoreSubscriberStateType) | ||
} | ||
|
||
public extension StoreSubscriber { | ||
// swiftlint:disable:next identifier_name | ||
func _newState(state: Any) { | ||
if let typedState = state as? StoreSubscriberStateType { | ||
newState(state: typedState) | ||
} | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this might not be needed as we creating the same thing in alias.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To keep store generic and copy tests, it will be needed