Skip to content

Commit

Permalink
93
Browse files Browse the repository at this point in the history
  • Loading branch information
stephencelis committed Mar 2, 2020
1 parent 134dfad commit fc02e29
Show file tree
Hide file tree
Showing 67 changed files with 4,882 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,105 @@
import CasePaths
import Combine
import SwiftUI

public typealias Reducer<Value, Action, Environment> = (inout Value, Action, Environment) -> [Effect<Action>]
//public typealias Reducer<Value, Action, Environment> = (inout Value, Action) -> (Environment) -> [Effect<Action>]

public func combine<Value, Action, Environment>(
_ reducers: Reducer<Value, Action, Environment>...
) -> Reducer<Value, Action, Environment> {
return { value, action, environment in
let effects = reducers.flatMap { $0(&value, action, environment) }
return effects
}
}

public func pullback<LocalValue, GlobalValue, LocalAction, GlobalAction, LocalEnvironment, GlobalEnvironment>(
_ reducer: @escaping Reducer<LocalValue, LocalAction, LocalEnvironment>,
value: WritableKeyPath<GlobalValue, LocalValue>,
action: CasePath<GlobalAction, LocalAction>,
environment: @escaping (GlobalEnvironment) -> LocalEnvironment
) -> Reducer<GlobalValue, GlobalAction, GlobalEnvironment> {
return { 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()
}
}
}

public func logging<Value, Action, Environment>(
_ reducer: @escaping Reducer<Value, Action, Environment>
) -> Reducer<Value, Action, Environment> {
return { 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>: ObservableObject {
private let reducer: Reducer<Value, Action, Any>
private let environment: Any
@Published public private(set) var value: Value
private var viewCancellable: Cancellable?
private var effectCancellables: Set<AnyCancellable> = []

public init<Environment>(
initialValue: Value,
reducer: @escaping Reducer<Value, Action, Environment>,
environment: Environment
) {
self.reducer = { value, action, environment in
reducer(&value, action, environment as! Environment)
}
self.value = initialValue
self.environment = environment
}

public 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] _ in
didComplete = true
guard let effectCancellable = effectCancellable else { return }
self?.effectCancellables.remove(effectCancellable)
},
receiveValue: self.send
)
if !didComplete, let effectCancellable = effectCancellable {
self.effectCancellables.insert(effectCancellable)
}
}
}

public func view<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: { localValue, localAction, _ in
self.send(toGlobalAction(localAction))
localValue = toLocalValue(self.value)
return []
},
environment: self.environment
)
localStore.viewCancellable = self.$value.sink { [weak localStore] newValue in
localStore?.value = toLocalValue(newValue)
}
return localStore
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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())
}
}
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>


Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import ComposableArchitecture
import XCTest

public enum StepType {
case send
case receive
}

public struct Step<Value, Action> {
let type: StepType
let action: Action
let update: (inout Value) -> Void
let file: StaticString
let line: UInt

public init(
_ type: StepType,
_ action: Action,
file: StaticString = #file,
line: UInt = #line,
_ update: @escaping (inout Value) -> Void = { _ in }
) {
self.type = type
self.action = action
self.update = update
self.file = file
self.line = line
}
}

public func assert<Value: Equatable, Action: Equatable, Environment>(
initialValue: Value,
reducer: Reducer<Value, Action, Environment>,
environment: Environment,
steps: Step<Value, Action>...,
file: StaticString = #file,
line: UInt = #line
) {
var state = initialValue
var effects: [Effect<Action>] = []

steps.forEach { step in
var expected = state

switch step.type {
case .send:
if !effects.isEmpty {
XCTFail("Action sent before handling \(effects.count) pending effect(s)", file: step.file, line: step.line)
}
effects.append(contentsOf: reducer(&state, step.action, environment))

case .receive:
guard !effects.isEmpty else {
XCTFail("No pending effects to receive from", file: step.file, line: step.line)
break
}
let effect = effects.removeFirst()
var action: Action!
let receivedCompletion = XCTestExpectation(description: "receivedCompletion")
let e = effect.sink(
receiveCompletion: { _ in
receivedCompletion.fulfill()
},
receiveValue: { action = $0 }
)
if XCTWaiter.wait(for: [receivedCompletion], timeout: 0.01) != .completed {
XCTFail("Timed out waiting for the effect to complete", file: step.file, line: step.line)
}
XCTAssertEqual(action, step.action, file: step.file, line: step.line)
effects.append(contentsOf: reducer(&state, action, environment))
}

step.update(&expected)
XCTAssertEqual(state, expected, file: step.file, line: step.line)
}
if !effects.isEmpty {
XCTFail("Assertion failed to handle \(effects.count) pending effect(s)", file: file, line: line)
}
}
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,5 @@
import XCTest
@testable import ComposableArchitecture

class ComposableArchitectureTests: XCTestCase {
}
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>1</string>
</dict>
</plist>
19 changes: 19 additions & 0 deletions 0093-modular-dependency-injection-pt3/PrimeTime/Counter/Counter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Counter.h
// Counter
//
// Created by Stephen Celis on 9/8/19.
// Copyright © 2019 Point-Free. All rights reserved.
//

#import <Foundation/Foundation.h>

//! Project version number for Counter.
FOUNDATION_EXPORT double CounterVersionNumber;

//! Project version string for Counter.
FOUNDATION_EXPORT const unsigned char CounterVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <Counter/PublicHeader.h>


Loading

0 comments on commit fc02e29

Please sign in to comment.