Skip to content

Commit

Permalink
98
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis committed Apr 13, 2020
1 parent 0506da9 commit 347e85e
Show file tree
Hide file tree
Showing 74 changed files with 5,223 additions and 0 deletions.
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>


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
}
}
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 {}
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>
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>


Loading

0 comments on commit 347e85e

Please sign in to comment.