-
Notifications
You must be signed in to change notification settings - Fork 299
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0506da9
commit 347e85e
Showing
74 changed files
with
5,223 additions
and
0 deletions.
There are no files selected for viewing
19 changes: 19 additions & 0 deletions
19
...-ergonomic-state-management-pt1/PrimeTime/ComposableArchitecture/ComposableArchitecture.h
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,19 @@ | ||
// | ||
// ComposableArchitecture.h | ||
// ComposableArchitecture | ||
// | ||
// Created by Stephen Celis on 9/8/19. | ||
// Copyright © 2019 Point-Free. All rights reserved. | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
//! Project version number for ComposableArchitecture. | ||
FOUNDATION_EXPORT double ComposableArchitectureVersionNumber; | ||
|
||
//! Project version string for ComposableArchitecture. | ||
FOUNDATION_EXPORT const unsigned char ComposableArchitectureVersionString[]; | ||
|
||
// In this header, you should import all the public headers of your framework using statements like #import <ComposableArchitecture/PublicHeader.h> | ||
|
||
|
220 changes: 220 additions & 0 deletions
220
...onomic-state-management-pt1/PrimeTime/ComposableArchitecture/ComposableArchitecture.swift
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,220 @@ | ||
import CasePaths | ||
import Combine | ||
import SwiftUI | ||
|
||
//(inout RandomNumberGenerator) -> A | ||
struct Gen<A> { | ||
let run: (inout RandomNumberGenerator) -> A | ||
} | ||
|
||
//(inout Substring) -> A? | ||
struct Parser<A> { | ||
let run: (inout Substring) -> A? | ||
} | ||
|
||
//(@escaping (A) -> Void) -> Void | ||
//struct Effect<A> { | ||
// let run: (@escaping (A) -> Void) -> Void | ||
//} | ||
|
||
//public typealias Reducer<Value, Action, Environment> = (inout Value, Action, Environment) -> [Effect<Action>] | ||
public struct Reducer<Value, Action, Environment> { | ||
let reducer: (inout Value, Action, Environment) -> [Effect<Action>] | ||
|
||
public init(_ reducer: @escaping (inout Value, Action, Environment) -> [Effect<Action>]) { | ||
self.reducer = reducer | ||
} | ||
} | ||
|
||
extension Reducer { | ||
public func callAsFunction(_ value: inout Value, _ action: Action, _ environment: Environment) -> [Effect<Action>] { | ||
self.reducer(&value, action, environment) | ||
} | ||
} | ||
|
||
extension Reducer { | ||
public static func combine(_ reducers: Reducer...) -> Reducer { | ||
.init { value, action, environment in | ||
let effects = reducers.flatMap { $0(&value, action, environment) } | ||
return effects | ||
} | ||
} | ||
} | ||
|
||
//public func combine<Value, Action, Environment>( | ||
// _ reducers: Reducer<Value, Action, Environment>... | ||
//) -> Reducer<Value, Action, Environment> { | ||
// .init { value, action, environment in | ||
// let effects = reducers.flatMap { $0(&value, action, environment) } | ||
// return effects | ||
// } | ||
//} | ||
|
||
extension Reducer { | ||
public func pullback<GlobalValue, GlobalAction, GlobalEnvironment>( | ||
value: WritableKeyPath<GlobalValue, Value>, | ||
action: CasePath<GlobalAction, Action>, | ||
environment: @escaping (GlobalEnvironment) -> Environment | ||
) -> Reducer<GlobalValue, GlobalAction, GlobalEnvironment> { | ||
.init { globalValue, globalAction, globalEnvironment in | ||
guard let localAction = action.extract(from: globalAction) else { return [] } | ||
let localEffects = self(&globalValue[keyPath: value], localAction, environment(globalEnvironment)) | ||
|
||
return localEffects.map { localEffect in | ||
localEffect.map(action.embed) | ||
.eraseToEffect() | ||
} | ||
} | ||
} | ||
} | ||
|
||
//public func pullback<LocalValue, GlobalValue, LocalAction, GlobalAction, LocalEnvironment, GlobalEnvironment>( | ||
// _ reducer: Reducer<LocalValue, LocalAction, LocalEnvironment>, | ||
// value: WritableKeyPath<GlobalValue, LocalValue>, | ||
// action: CasePath<GlobalAction, LocalAction>, | ||
// environment: @escaping (GlobalEnvironment) -> LocalEnvironment | ||
//) -> Reducer<GlobalValue, GlobalAction, GlobalEnvironment> { | ||
// return .init { globalValue, globalAction, globalEnvironment in | ||
// guard let localAction = action.extract(from: globalAction) else { return [] } | ||
// let localEffects = reducer(&globalValue[keyPath: value], localAction, environment(globalEnvironment)) | ||
// | ||
// return localEffects.map { localEffect in | ||
// localEffect.map(action.embed) | ||
// .eraseToEffect() | ||
// } | ||
// } | ||
//} | ||
|
||
extension Reducer { | ||
public func logging( | ||
printer: @escaping (Environment) -> (String) -> Void = { _ in { print($0) } } | ||
) -> Reducer { | ||
.init { value, action, environment in | ||
let effects = self(&value, action, environment) | ||
let newValue = value | ||
let print = printer(environment) | ||
return [.fireAndForget { | ||
print("Action: \(action)") | ||
print("Value:") | ||
var dumpedNewValue = "" | ||
dump(newValue, to: &dumpedNewValue) | ||
print(dumpedNewValue) | ||
print("---") | ||
}] + effects | ||
} | ||
} | ||
} | ||
|
||
//public func logging<Value, Action, Environment>( | ||
// _ reducer: Reducer<Value, Action, Environment> | ||
//) -> Reducer<Value, Action, Environment> { | ||
// return .init { value, action, environment in | ||
// let effects = reducer(&value, action, environment) | ||
// let newValue = value | ||
// return [.fireAndForget { | ||
// print("Action: \(action)") | ||
// print("Value:") | ||
// dump(newValue) | ||
// print("---") | ||
// }] + effects | ||
// } | ||
//} | ||
|
||
public final class Store<Value, Action> { | ||
private let reducer: Reducer<Value, Action, Any> | ||
private let environment: Any | ||
@Published private var value: Value | ||
private var viewCancellable: Cancellable? | ||
private var effectCancellables: Set<AnyCancellable> = [] | ||
|
||
public init<Environment>( | ||
initialValue: Value, | ||
reducer: Reducer<Value, Action, Environment>, | ||
environment: Environment | ||
) { | ||
self.reducer = .init { value, action, environment in | ||
reducer(&value, action, environment as! Environment) | ||
} | ||
self.value = initialValue | ||
self.environment = environment | ||
} | ||
|
||
private func send(_ action: Action) { | ||
let effects = self.reducer(&self.value, action, self.environment) | ||
effects.forEach { effect in | ||
var effectCancellable: AnyCancellable? | ||
var didComplete = false | ||
effectCancellable = effect.sink( | ||
receiveCompletion: { [weak self, weak effectCancellable] _ in | ||
didComplete = true | ||
guard let effectCancellable = effectCancellable else { return } | ||
self?.effectCancellables.remove(effectCancellable) | ||
}, | ||
receiveValue: { [weak self] in self?.send($0) } | ||
) | ||
if !didComplete, let effectCancellable = effectCancellable { | ||
self.effectCancellables.insert(effectCancellable) | ||
} | ||
} | ||
} | ||
|
||
public func scope<LocalValue, LocalAction>( | ||
value toLocalValue: @escaping (Value) -> LocalValue, | ||
action toGlobalAction: @escaping (LocalAction) -> Action | ||
) -> Store<LocalValue, LocalAction> { | ||
let localStore = Store<LocalValue, LocalAction>( | ||
initialValue: toLocalValue(self.value), | ||
reducer: .init { localValue, localAction, _ in | ||
self.send(toGlobalAction(localAction)) | ||
localValue = toLocalValue(self.value) | ||
return [] | ||
}, | ||
environment: self.environment | ||
) | ||
localStore.viewCancellable = self.$value | ||
.map(toLocalValue) | ||
.sink { [weak localStore] newValue in | ||
localStore?.value = newValue | ||
} | ||
return localStore | ||
} | ||
} | ||
|
||
public final class ViewStore<Value, Action>: ObservableObject { | ||
@Published public fileprivate(set) var value: Value | ||
fileprivate var cancellable: Cancellable? | ||
public let send: (Action) -> Void | ||
|
||
public init( | ||
initialValue value: Value, | ||
send: @escaping (Action) -> Void | ||
) { | ||
self.value = value | ||
self.send = send | ||
} | ||
} | ||
|
||
extension Store where Value: Equatable { | ||
public var view: ViewStore<Value, Action> { | ||
self.view(removeDuplicates: ==) | ||
} | ||
} | ||
|
||
extension Store { | ||
public func view( | ||
removeDuplicates predicate: @escaping (Value, Value) -> Bool | ||
) -> ViewStore<Value, Action> { | ||
let viewStore = ViewStore( | ||
initialValue: self.value, | ||
send: self.send | ||
) | ||
|
||
viewStore.cancellable = self.$value | ||
.removeDuplicates(by: predicate) | ||
.sink(receiveValue: { [weak viewStore] value in | ||
viewStore?.value = value | ||
}) | ||
|
||
return viewStore | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
0098-ergonomic-state-management-pt1/PrimeTime/ComposableArchitecture/Effect.swift
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,42 @@ | ||
import Combine | ||
|
||
public struct Effect<Output>: Publisher { | ||
public typealias Failure = Never | ||
|
||
let publisher: AnyPublisher<Output, Failure> | ||
|
||
public func receive<S>( | ||
subscriber: S | ||
) where S: Subscriber, Failure == S.Failure, Output == S.Input { | ||
self.publisher.receive(subscriber: subscriber) | ||
} | ||
} | ||
|
||
extension Effect { | ||
public static func fireAndForget(work: @escaping () -> Void) -> Effect { | ||
return Deferred { () -> Empty<Output, Never> in | ||
work() | ||
return Empty(completeImmediately: true) | ||
}.eraseToEffect() | ||
} | ||
|
||
public static func sync(work: @escaping () -> Output) -> Effect { | ||
return Deferred { | ||
Just(work()) | ||
}.eraseToEffect() | ||
} | ||
} | ||
|
||
extension Publisher where Failure == Never { | ||
public func eraseToEffect() -> Effect<Output> { | ||
return Effect(publisher: self.eraseToAnyPublisher()) | ||
} | ||
} | ||
|
||
extension Publisher where Output == Never, Failure == Never { | ||
public func fireAndForget<A>() -> Effect<A> { | ||
return self.map(absurd).eraseToEffect() | ||
} | ||
} | ||
|
||
private func absurd<A>(_ never: Never) -> A {} |
22 changes: 22 additions & 0 deletions
22
0098-ergonomic-state-management-pt1/PrimeTime/ComposableArchitecture/Info.plist
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,22 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>$(DEVELOPMENT_LANGUAGE)</string> | ||
<key>CFBundleExecutable</key> | ||
<string>$(EXECUTABLE_NAME)</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>$(PRODUCT_NAME)</string> | ||
<key>CFBundlePackageType</key> | ||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleVersion</key> | ||
<string>$(CURRENT_PROJECT_VERSION)</string> | ||
</dict> | ||
</plist> |
19 changes: 19 additions & 0 deletions
19
...ement-pt1/PrimeTime/ComposableArchitectureTestSupport/ComposableArchitectureTestSupport.h
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,19 @@ | ||
// | ||
// ComposableArchitectureTestSupport.h | ||
// ComposableArchitectureTestSupport | ||
// | ||
// Created by Stephen Celis on 2/6/20. | ||
// Copyright © 2020 Point-Free. All rights reserved. | ||
// | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
//! Project version number for ComposableArchitectureTestSupport. | ||
FOUNDATION_EXPORT double ComposableArchitectureTestSupportVersionNumber; | ||
|
||
//! Project version string for ComposableArchitectureTestSupport. | ||
FOUNDATION_EXPORT const unsigned char ComposableArchitectureTestSupportVersionString[]; | ||
|
||
// In this header, you should import all the public headers of your framework using statements like #import <ComposableArchitectureTestSupport/PublicHeader.h> | ||
|
||
|
Oops, something went wrong.