From f110bd20357cd5916c79e5d4796b5b23ac1fa3d4 Mon Sep 17 00:00:00 2001 From: Andrey Yoshua Date: Thu, 24 Feb 2022 14:34:47 +0700 Subject: [PATCH 1/3] delete all first --- .DS_Store | Bin 0 -> 8196 bytes DiffingInterface/AnyHashDiffable.swift | 96 - .../DiffingInterface+Primitives.swift | 96 - .../DiffingInterface.docc/DiffingInterface.md | 13 - DiffingInterface/DiffingInterface.h | 18 - DiffingInterface/HashDiffable.swift | 25 - DiffingInterface/HashDiffing.swift | 260 --- .../DiffingTestSupport.md | 13 - DiffingTestSupport/DiffingTestSupport.h | 18 - DiffingTestSupport/XCTDecode.swift | 57 - DiffingTestSupport/XCTPrettyEqual.swift | 15 - DiffingUtility/Debug.swift | 253 --- DiffingUtility/Diff.swift | 101 - .../DiffingUtility.docc/DiffingUtility.md | 13 - DiffingUtility/DiffingUtility.h | 18 - Podfile | 39 - Podfile.lock | 33 - RxComposableArchitecture/Binding.swift | 216 -- .../Debugging/Bootstrapping.swift | 134 -- .../Debugging/MockPageTemplate.swift | 240 --- .../Debugging/ReducerDebugging.swift | 256 --- .../Debugging/ReducerInstrumentation.swift | 124 -- RxComposableArchitecture/Effect.swift | 311 --- .../Effects/Cancellation.swift | 91 - .../Effects/Debouncing.swift | 37 - .../Effects/Deferring.swift | 36 - .../Effects/FireAndForget.swift | 18 - RxComposableArchitecture/Effects/Timer.swift | 110 - RxComposableArchitecture/Export.swift | 2 - .../IdentifiedArray.swift | 348 ---- RxComposableArchitecture/IfLet.swift | 78 - .../Internal/AnyDisposable.swift | 29 - .../Internal/Deprecated.swift | 13 - .../Internal/Locking.swift | 26 - RxComposableArchitecture/OptionalPaths.swift | 275 --- .../PropertyWrapper/SingleSelection.swift | 255 --- .../PropertyWrapper/UniqueElements.swift | 60 - RxComposableArchitecture/Reducer.swift | 625 ------ .../RxComposableArchitecture.md | 13 - .../RxComposableArchitecture.h | 18 - RxComposableArchitecture/Stateless.swift | 12 - RxComposableArchitecture/Store.swift | 284 --- .../CasePaths.podspec.json | 20 - .../project.pbxproj | 1836 ----------------- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcschemes/DiffingUtility.xcscheme | 67 - .../xcschemes/xcschememanagement.plist | 52 - .../contents.xcworkspacedata | 10 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../AppDelegate.swift | 36 - .../AccentColor.colorset/Contents.json | 11 - .../AppIcon.appiconset/Contents.json | 98 - .../Assets.xcassets/Contents.json | 6 - .../Base.lproj/LaunchScreen.storyboard | 25 - .../Base.lproj/Main.storyboard | 24 - RxComposableArchitectureExample/Info.plist | 25 - .../SceneDelegate.swift | 52 - .../ViewController.swift | 19 - .../DebugTests.swift | 748 ------- .../EffectCancellationTests.swift | 257 --- .../EffectDebounceTests.swift | 88 - .../EffectDeferredTests.swift | 91 - .../EffectTests.swift | 165 -- .../IdentifiedArrayTests.swift | 191 -- RxComposableArchitectureTests/LCRNG.swift | 15 - .../MemoryManagementTests.swift | 26 - .../ReducerTests.swift | 125 -- .../RxComposableArchitectureTests.md | 13 - .../RxComposableArchitectureTests.h | 18 - .../RxComposableArchitectureTests.swift | 168 -- .../SingleSelectionSelectableTypeTests.swift | 104 - .../SingleSelectionTests.swift | 104 - .../StoreTests.swift | 395 ---- .../TimerTests.swift | 140 -- TestSupport/Annotating.swift | 71 - TestSupport/Export.swift | 2 - TestSupport/FailingEffect.swift | 89 - TestSupport/Internal/PriorityQueue.swift | 107 - .../Internal/VirtualTimeScheduler.swift | 276 --- TestSupport/TestScheduler.swift | 64 - TestSupport/TestStore.swift | 690 ------- TestSupport/TestSupport.docc/TestSupport.md | 13 - TestSupport/TestSupport.h | 18 - TestSupport/Unwrap.swift | 25 - 85 files changed, 10986 deletions(-) create mode 100644 .DS_Store delete mode 100644 DiffingInterface/AnyHashDiffable.swift delete mode 100644 DiffingInterface/DiffingInterface+Primitives.swift delete mode 100755 DiffingInterface/DiffingInterface.docc/DiffingInterface.md delete mode 100644 DiffingInterface/DiffingInterface.h delete mode 100644 DiffingInterface/HashDiffable.swift delete mode 100644 DiffingInterface/HashDiffing.swift delete mode 100755 DiffingTestSupport/DiffingTestSupport.docc/DiffingTestSupport.md delete mode 100644 DiffingTestSupport/DiffingTestSupport.h delete mode 100644 DiffingTestSupport/XCTDecode.swift delete mode 100644 DiffingTestSupport/XCTPrettyEqual.swift delete mode 100644 DiffingUtility/Debug.swift delete mode 100644 DiffingUtility/Diff.swift delete mode 100755 DiffingUtility/DiffingUtility.docc/DiffingUtility.md delete mode 100644 DiffingUtility/DiffingUtility.h delete mode 100644 Podfile delete mode 100644 Podfile.lock delete mode 100644 RxComposableArchitecture/Binding.swift delete mode 100644 RxComposableArchitecture/Debugging/Bootstrapping.swift delete mode 100644 RxComposableArchitecture/Debugging/MockPageTemplate.swift delete mode 100644 RxComposableArchitecture/Debugging/ReducerDebugging.swift delete mode 100644 RxComposableArchitecture/Debugging/ReducerInstrumentation.swift delete mode 100644 RxComposableArchitecture/Effect.swift delete mode 100644 RxComposableArchitecture/Effects/Cancellation.swift delete mode 100644 RxComposableArchitecture/Effects/Debouncing.swift delete mode 100644 RxComposableArchitecture/Effects/Deferring.swift delete mode 100644 RxComposableArchitecture/Effects/FireAndForget.swift delete mode 100644 RxComposableArchitecture/Effects/Timer.swift delete mode 100644 RxComposableArchitecture/Export.swift delete mode 100644 RxComposableArchitecture/IdentifiedArray.swift delete mode 100644 RxComposableArchitecture/IfLet.swift delete mode 100644 RxComposableArchitecture/Internal/AnyDisposable.swift delete mode 100644 RxComposableArchitecture/Internal/Deprecated.swift delete mode 100644 RxComposableArchitecture/Internal/Locking.swift delete mode 100644 RxComposableArchitecture/OptionalPaths.swift delete mode 100644 RxComposableArchitecture/PropertyWrapper/SingleSelection.swift delete mode 100644 RxComposableArchitecture/PropertyWrapper/UniqueElements.swift delete mode 100644 RxComposableArchitecture/Reducer.swift delete mode 100755 RxComposableArchitecture/RxComposableArchitecture.docc/RxComposableArchitecture.md delete mode 100644 RxComposableArchitecture/RxComposableArchitecture.h delete mode 100644 RxComposableArchitecture/Stateless.swift delete mode 100644 RxComposableArchitecture/Store.swift delete mode 100644 RxComposableArchitecture/development-podspecs/CasePaths.podspec.json delete mode 100644 RxComposableArchitectureExample.xcodeproj/project.pbxproj delete mode 100644 RxComposableArchitectureExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 RxComposableArchitectureExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 RxComposableArchitectureExample.xcodeproj/xcshareddata/xcschemes/DiffingUtility.xcscheme delete mode 100644 RxComposableArchitectureExample.xcodeproj/xcuserdata/andreyyoshuamanik.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 RxComposableArchitectureExample.xcworkspace/contents.xcworkspacedata delete mode 100644 RxComposableArchitectureExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 RxComposableArchitectureExample/AppDelegate.swift delete mode 100644 RxComposableArchitectureExample/Assets.xcassets/AccentColor.colorset/Contents.json delete mode 100644 RxComposableArchitectureExample/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 RxComposableArchitectureExample/Assets.xcassets/Contents.json delete mode 100644 RxComposableArchitectureExample/Base.lproj/LaunchScreen.storyboard delete mode 100644 RxComposableArchitectureExample/Base.lproj/Main.storyboard delete mode 100644 RxComposableArchitectureExample/Info.plist delete mode 100644 RxComposableArchitectureExample/SceneDelegate.swift delete mode 100644 RxComposableArchitectureExample/ViewController.swift delete mode 100644 RxComposableArchitectureTests/DebugTests.swift delete mode 100644 RxComposableArchitectureTests/EffectCancellationTests.swift delete mode 100644 RxComposableArchitectureTests/EffectDebounceTests.swift delete mode 100644 RxComposableArchitectureTests/EffectDeferredTests.swift delete mode 100644 RxComposableArchitectureTests/EffectTests.swift delete mode 100644 RxComposableArchitectureTests/IdentifiedArrayTests.swift delete mode 100644 RxComposableArchitectureTests/LCRNG.swift delete mode 100644 RxComposableArchitectureTests/MemoryManagementTests.swift delete mode 100644 RxComposableArchitectureTests/ReducerTests.swift delete mode 100755 RxComposableArchitectureTests/RxComposableArchitectureTests.docc/RxComposableArchitectureTests.md delete mode 100644 RxComposableArchitectureTests/RxComposableArchitectureTests.h delete mode 100644 RxComposableArchitectureTests/RxComposableArchitectureTests.swift delete mode 100644 RxComposableArchitectureTests/SingleSelectionSelectableTypeTests.swift delete mode 100644 RxComposableArchitectureTests/SingleSelectionTests.swift delete mode 100644 RxComposableArchitectureTests/StoreTests.swift delete mode 100644 RxComposableArchitectureTests/TimerTests.swift delete mode 100644 TestSupport/Annotating.swift delete mode 100644 TestSupport/Export.swift delete mode 100644 TestSupport/FailingEffect.swift delete mode 100644 TestSupport/Internal/PriorityQueue.swift delete mode 100644 TestSupport/Internal/VirtualTimeScheduler.swift delete mode 100644 TestSupport/TestScheduler.swift delete mode 100644 TestSupport/TestStore.swift delete mode 100755 TestSupport/TestSupport.docc/TestSupport.md delete mode 100644 TestSupport/TestSupport.h delete mode 100644 TestSupport/Unwrap.swift diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..57040a963cc65fe0e5b51745da97c829fbb85f7d GIT binary patch literal 8196 zcmeHML2DC16n@hd3>2k!F$m%wEMCo7kZnwnUXAsfCXH<&aYK_ry>0c66hTl#@Z`;t zUPVFiF1`9A{NBv$Iz+z}IHmF@OrpX?ZbyYUSP{tki12zYS7Gs0P zos@AWWoK44Ls5Em_yyXWRA^9IQ9u;PD-fV-g<8nmpWx@_?|s@w&ldHlKiFxsd(m)Y z+?&6O_za^~t2>Cgu&o`vJG%4X`uaRW=6NN-OSDdhSaCxX4Z{fzexf&;HEHjQ9-E=E2p4ImpPn{kjp8HrQ z98rX|vqfEwjMlJndbFz*?&E5L6}6v(%eu-qo-T_WP@G7@VBf6Ts{h4Z3F-}*(^BdLElfJQ@H%jZ7>||r_uUp+7R~yaM@zwPg$=a(B zpR3{djOgj7j#DGR{A;&u}Wzhsu=lp*iuL1HM zU|yb##32e?oC0d8)owL0pUtgvGjpyTpmtEXuw86WyP(nOI8dkK!1N!6xDH^-KB2|f kAbL(_ base: D) { - /// Condition to handle if accidentaly `AnyHashDiffable` being wrapped in another `AnyHashDiffable` again. - if let anyDifferentiable = base as? AnyHashDiffable { - self = anyDifferentiable - } else { - box = HashDiffableBox(base) - } - } - - /// Indicate whether the content of `base` is equals to the content of the given source value. - /// - /// - Parameters: - /// - source: A source value to be compared. - /// - /// - Returns: A Boolean value indicating whether the content of `base` is equals - /// to the content of `base` of the given source value. - public func isEqual(to source: AnyHashDiffable) -> Bool { - return box.isEqual(to: source.box) - } -} - -extension AnyHashDiffable: CustomDebugStringConvertible { - public var debugDescription: String { - return "AnyHashDiffable(\(String(reflecting: base)))" - } -} - -internal protocol AnyHashDiffableBox { - var base: Any { get } - var id: AnyHashable { get } - - func isEqual(to source: AnyHashDiffableBox) -> Bool -} - -internal struct HashDiffableBox: AnyHashDiffableBox { - internal let baseComponent: Base - - internal var base: Any { - return baseComponent - } - - internal var id: AnyHashable { - return baseComponent.id - } - - internal init(_ base: Base) { - baseComponent = base - } - - internal func isEqual(to source: AnyHashDiffableBox) -> Bool { - guard let sourceBase = source.base as? Base else { - return false - } - return baseComponent.isEqual(to: sourceBase) - } -} diff --git a/DiffingInterface/DiffingInterface+Primitives.swift b/DiffingInterface/DiffingInterface+Primitives.swift deleted file mode 100644 index d56de70..0000000 --- a/DiffingInterface/DiffingInterface+Primitives.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// DiffingInterface+Primitives.swift -// DiffingInterface -// -// Created by Edho Prasetyo on 25/09/20. -// - -extension Int: HashDiffable { - public var id: Int { - return self - } -} - -extension String: HashDiffable { - public var id: String { - return self - } -} - -extension Bool: HashDiffable { - public var id: Bool { - return self - } -} - -extension Double: HashDiffable { - public var id: Double { - return self - } -} - -extension Float: HashDiffable { - public var id: Float { - return self - } -} - -extension Array where Element == AnyHashDiffable { - public func removeDuplicates() -> [AnyHashDiffable] { - /// This table will contain `diffIdentifier` as the `key` and object `type` as the value - var tableOfIdentifiersType = [AnyHashable: Any.Type]() - var uniqueObjects = [AnyHashDiffable]() - - let tempSelf = self - tempSelf.forEach { object in - /// Get current object identifier - let currentId = object.id - /// Get current object type from Type Erasure base object - let currentObjectType = type(of: object.base) - /// Check if `currentId` is already registered on `Table Bank of Identifiers Type` - /// If `yes` > Get object type with current identifier from `Table Bank of Identifiers Type` - /// If `no` > Then return `nil` - let previousesObjectType = tableOfIdentifiersType[currentId] - - /// Check wether current object type is the same with previous object type(if exist) fetched from `Table Bank of Identifiers Type` - /// If `currentId` already exist on `Table Bank of Identifiers Type` but the type is different it's not counted as _**duplicates**_ - if currentObjectType != previousesObjectType { - tableOfIdentifiersType[currentId] = currentObjectType - uniqueObjects.append(object) - } - } - - return uniqueObjects - } -} - -extension Array where Element: HashDiffable { - public func removeDuplicates() -> Self { - /// This table will contain `diffIdentifier` as the `key` and object `type` as the value - var tableOfObjectType = [AnyHashable: Any.Type]() - - var uniqueObjects = [Element]() - - forEach { currentObject in - /// Get current object identifier - let currentId = currentObject.id - - /// Get current object type from Type Erasure base object - let currentObjectType = type(of: currentObject) - - /// Check if `currentId` is already registered on `Table Bank of Identifiers Type` - /// If `yes` > Get object type with current identifier from `Table Bank of Identifiers Type` - /// If `no` > Then return `nil` - let previousObjectType = tableOfObjectType[currentId] - - /// Check whether current object type is the same with previous object type(if exist) fetched from `Table Bank of Identifiers Type` - /// If `currentId` already exist on `Table Bank of Identifiers Type` but the type is different it's not counted as _**duplicates**_ - if currentObjectType != previousObjectType { - tableOfObjectType[currentId] = currentObjectType - uniqueObjects.append(currentObject) - } - } - - return uniqueObjects - } -} diff --git a/DiffingInterface/DiffingInterface.docc/DiffingInterface.md b/DiffingInterface/DiffingInterface.docc/DiffingInterface.md deleted file mode 100755 index cd4d191..0000000 --- a/DiffingInterface/DiffingInterface.docc/DiffingInterface.md +++ /dev/null @@ -1,13 +0,0 @@ -# ``DiffingInterface`` - -Summary - -## Overview - -Text - -## Topics - -### Group - -- ``Symbol`` \ No newline at end of file diff --git a/DiffingInterface/DiffingInterface.h b/DiffingInterface/DiffingInterface.h deleted file mode 100644 index 8d05e36..0000000 --- a/DiffingInterface/DiffingInterface.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// DiffingInterface.h -// DiffingInterface -// -// Created by Andrey Yoshua Manik on 21/02/22. -// - -#import - -//! Project version number for DiffingInterface. -FOUNDATION_EXPORT double DiffingInterfaceVersionNumber; - -//! Project version string for DiffingInterface. -FOUNDATION_EXPORT const unsigned char DiffingInterfaceVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/DiffingInterface/HashDiffable.swift b/DiffingInterface/HashDiffable.swift deleted file mode 100644 index d2fea48..0000000 --- a/DiffingInterface/HashDiffable.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// HashDiffableProtocol.swift -// DiffingInterface -// -// Created by Edho Prasetyo on 24/09/20. -// - -public protocol HashDiffable { - associatedtype IdentifierType: Hashable - var id: Self.IdentifierType { get } - func isEqual(to source: Self) -> Bool -} - -public extension HashDiffable where Self: Hashable { - /// The `self` value as an identifier for difference calculation. - var id: Self { - return self - } -} - -extension HashDiffable where Self: Equatable { - public func isEqual(to source: Self) -> Bool { - return self == source - } -} diff --git a/DiffingInterface/HashDiffing.swift b/DiffingInterface/HashDiffing.swift deleted file mode 100644 index eae6d3c..0000000 --- a/DiffingInterface/HashDiffing.swift +++ /dev/null @@ -1,260 +0,0 @@ -// -// HashDiffing.swift -// DiffingInterface -// -// Created by Edho Prasetyo on 24/09/20. -// - -import Foundation - -internal struct Stack { - private var items = [Element]() - internal var isEmpty: Bool { - return items.isEmpty - } - - internal mutating func push(_ item: Element) { - items.append(item) - } - - internal mutating func pop() -> Element { - return items.removeLast() - } -} - -/** - `References`: - **About the Code Reference** - - https://github.com/Instagram/IGListKit/blob/master/Source/IGListDiff.mm - - **If You want to learn the Algorithm** - - https://github.com/AmbarSeptian/DiffingSharingSession - - https://docs.google.com/spreadsheets/d/10CV51it4Oq_Uv1sTS8wBLdGyiE6t5OGtFBT-gCn7w1k/edit?usp=sharing - */ -public enum DiffingInterfaceList { - /// Used to track data stats while diffing. - /// We expect to keep a reference of entry, thus its declaration as (final) class. - internal final class Entry { - /// The number of times the data occurs in the old array - internal var oldCounter: Int = 0 - /// The number of times the data occurs in the new array - internal var newCounter: Int = 0 - /// The indexes of the data in the old array - internal var oldIndexes: Stack = Stack() - /// Flag marking if the data has been updated between arrays by equality check - internal var updated: Bool = false - /// Returns `true` if the data occur on both sides, `false` otherwise - internal var occurOnBothSides: Bool { - return newCounter > 0 && oldCounter > 0 - } - - internal func push(new index: Int?) { - newCounter += 1 - oldIndexes.push(index) - } - - internal func push(old index: Int?) { - oldCounter += 1 - oldIndexes.push(index) - } - } - - /// Track both the entry and the algorithm index. Default the index to `nil` - internal struct Record { - internal let entry: Entry - internal var index: Int? - internal init(_ entry: Entry) { - self.entry = entry - index = nil - } - } - - public struct MoveIndex: Equatable { - public let from: Int - public let to: Int - - public init(from: Int, to: Int) { - self.from = from - self.to = to - } - - public static func == (lhs: MoveIndex, rhs: MoveIndex) -> Bool { - return lhs.from == rhs.from && lhs.to == rhs.to - } - } - - public struct Result { - public var inserts = IndexSet() - public var updates = IndexSet() - public var deletes = IndexSet() - public var moves = [MoveIndex]() - public var oldMap = [AnyHashable: Int]() - public var newMap = [AnyHashable: Int]() - public var hasChanges: Bool { - return (inserts.count > 0) || (deletes.count > 0) || (updates.count > 0) || (moves.count > 0) - } - - public var changeCount: Int { - return inserts.count + deletes.count + updates.count + moves.count - } - - public func validate(_ oldArray: [T], _ newArray: [T]) -> Bool { - return (oldArray.count + inserts.count - deletes.count) == newArray.count - } - - public func oldIndexFor(identifier: AnyHashable) -> Int? { - return oldMap[identifier] - } - - public func newIndexFor(identifier: AnyHashable) -> Int? { - return newMap[identifier] - } - } - - /// Diffing a pair of collection to get which `index/item` is `deleted, inserted, updated or moved` - /// - /// - Parameters: - /// - oldArray: An `Old/Source Array` of generic `T` HashDiffable value. - /// - newArray: An `New/Target Array` of generic `T` HashDiffable value. - public static func diffing(oldArray: [T], newArray: [T]) -> Result { - // symbol table uses the old/new array `diffIdentifier` as the key and `Entry` as the value - var table = [AnyHashable: Entry]() - - // pass 1 - // create an entry for every item in the new array - // increment its new count for each occurence - // record `nil` for each occurence of the item in the new array - var newRecords = newArray.map { (newRecord) -> Record in - let key = newRecord.id - if let entry = table[key] { - // add `nil` for each occurence of the item in the new array - entry.push(new: nil) - return Record(entry) - } else { - let entry = Entry() - // add `nil` for each occurence of the item in the new array - entry.push(new: nil) - table[key] = entry - return Record(entry) - } - } - - // pass 2 - // update or create an entry for every item in the old array - // increment its old count for each occurence - // record the old index for each occurence of the item in the old array - // MUST be done in descending order to respect the oldIndexes stack construction - var oldRecords = oldArray.enumerated().reversed().map { (i, oldRecord) -> Record in - let key = oldRecord.id - if let entry = table[key] { - // push the old indices where the item occured onto the index stack - entry.push(old: i) - return Record(entry) - } else { - let entry = Entry() - // push the old indices where the item occured onto the index stack - entry.push(old: i) - table[key] = entry - return Record(entry) - } - } - - // pass 3 - // handle data that occurs in both arrays - newRecords.enumerated().filter { $1.entry.occurOnBothSides }.forEach { i, newRecord in - let entry = newRecord.entry - // grab and pop the top old index. if the item was inserted this will be nil - assert(!entry.oldIndexes.isEmpty, "Old indexes is empty while iterating new item \(i). Should have nil") - guard let oldIndex = entry.oldIndexes.pop() else { - return - } - if oldIndex < oldArray.count { - let n = newArray[i] - let o = oldArray[oldIndex] - if !n.isEqual(to: o) { - entry.updated = true - } - } - - // if an item occurs in the new and old array, it is unique - // assign the index of new and old records to the opposite index (reverse lookup) - newRecords[i].index = oldIndex - oldRecords[oldIndex].index = i - } - - // storage for final indexes - var result = Result() - - // track offsets from deleted items to calculate where items have moved - // iterate old array records checking for deletes - // increment offset for each delete - var runningOffset = 0 - let deleteOffsets = oldRecords.enumerated().map { (i, oldRecord) -> Int in - let deleteOffset = runningOffset - // if the record index in the new array doesn't exist, its a delete - if oldRecord.index == nil { - result.deletes.insert(i) - runningOffset += 1 - } - result.oldMap[oldArray[i].id] = i - return deleteOffset - } - - // reset and track offsets from inserted items to calculate where items have moved - runningOffset = 0 - /* let insertOffsets */ _ = newRecords.enumerated().map { (i, newRecord) -> Int in - let insertOffset = runningOffset - if let oldIndex = newRecord.index { - // note that an entry can be updated /and/ moved - if newRecord.entry.updated { - result.updates.insert(oldIndex) - } - - // calculate the offset and determine if there was a move - // if the indexes match, ignore the index - let deleteOffset = deleteOffsets[oldIndex] - if (oldIndex - deleteOffset + insertOffset) != i { - result.moves.append(MoveIndex(from: oldIndex, to: i)) - } - } else { // add to inserts if the opposing index is nil - result.inserts.insert(i) - runningOffset += 1 - } - result.newMap[newArray[i].id] = i - return insertOffset - } - - assert(result.validate(oldArray, newArray), "Sanity check failed applying \(result.inserts.count) inserts and \(result.deletes.count) deletes to old count \(oldArray.count) equaling new count \(newArray.count)") - - return result - } -} - -extension DiffingInterfaceList.Result { - public func forBatchUpdates() -> DiffingInterfaceList.Result { - var result = self - result.mutatingForBatchUpdates() - return result - } - - private mutating func mutatingForBatchUpdates() { - // convert move+update to delete+insert, respecting the from/to of the move - for (index, move) in moves.enumerated().reversed() { - if let _ = updates.remove(move.from) { - moves.remove(at: index) - deletes.insert(move.from) - inserts.insert(move.to) - } - } - - // iterate all new identifiers. if its index is updated, delete from the old index and insert the new index - for (key, oldIndex) in oldMap { - if updates.contains(oldIndex), let newIndex = newMap[key] { - deletes.insert(oldIndex) - inserts.insert(newIndex) - } - } - - updates.removeAll() - } -} diff --git a/DiffingTestSupport/DiffingTestSupport.docc/DiffingTestSupport.md b/DiffingTestSupport/DiffingTestSupport.docc/DiffingTestSupport.md deleted file mode 100755 index 0502619..0000000 --- a/DiffingTestSupport/DiffingTestSupport.docc/DiffingTestSupport.md +++ /dev/null @@ -1,13 +0,0 @@ -# ``DiffingTestSupport`` - -Summary - -## Overview - -Text - -## Topics - -### Group - -- ``Symbol`` \ No newline at end of file diff --git a/DiffingTestSupport/DiffingTestSupport.h b/DiffingTestSupport/DiffingTestSupport.h deleted file mode 100644 index f2c9f7d..0000000 --- a/DiffingTestSupport/DiffingTestSupport.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// DiffingTestSupport.h -// DiffingTestSupport -// -// Created by Andrey Yoshua Manik on 21/02/22. -// - -#import - -//! Project version number for DiffingTestSupport. -FOUNDATION_EXPORT double DiffingTestSupportVersionNumber; - -//! Project version string for DiffingTestSupport. -FOUNDATION_EXPORT const unsigned char DiffingTestSupportVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/DiffingTestSupport/XCTDecode.swift b/DiffingTestSupport/XCTDecode.swift deleted file mode 100644 index 9862cff..0000000 --- a/DiffingTestSupport/XCTDecode.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// XCTDecode.swift -// Tests -// -// Created by Kensen on 12/07/21. -// - -import XCTest - -public func XCTDecode(_ type: T.Type, from data: Data, atKeyPath keyPath: String? = nil, file: StaticString = #file, line: UInt = #line) throws -> T where T: Decodable { - do { - if let keyPath = keyPath { - let topLevel = try JSONSerialization.jsonObject(with: data) - - if let nestedJson = (topLevel as AnyObject).value(forKeyPath: keyPath) { - let nestedJsonData = try JSONSerialization.data(withJSONObject: nestedJson) - return try JSONDecoder().decode(type, from: nestedJsonData) - } else { - let debugDescription = "Nested JSON not found for key path \"\(keyPath)\"" - let decodingError = DecodingError.dataCorrupted( - .init( - codingPath: [], - debugDescription: debugDescription - ) - ) - - XCTFail(debugDescription, file: file, line: line) - throw decodingError - } - } else { - return try JSONDecoder().decode(type, from: data) - } - } catch { - guard let decodingError = error as? DecodingError else { - XCTFail(error.localizedDescription, file: file, line: line) - throw error - } - - switch decodingError { - case let .dataCorrupted(context), let .keyNotFound(_, context), let .typeMismatch(_, context), let .valueNotFound(_, context): - let codingPath: String = context.codingPath.map(\.stringValue).joined(separator: ".") - - let failMessage: String - if codingPath.isEmpty { - failMessage = context.debugDescription - } else { - failMessage = "\(context.debugDescription) (codingPath: \"\(codingPath)\")" - } - - XCTFail(failMessage, file: file, line: line) - @unknown default: - XCTFail(decodingError.localizedDescription, file: file, line: line) - } - - throw decodingError - } -} diff --git a/DiffingTestSupport/XCTPrettyEqual.swift b/DiffingTestSupport/XCTPrettyEqual.swift deleted file mode 100644 index 5befbbb..0000000 --- a/DiffingTestSupport/XCTPrettyEqual.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// XCTPrettyAssertEqual.swift -// RxComposableArchitecture_TestSupport -// -// Created by Jefferson Setiawan on 20/01/21. -// - -import DiffingUtility -import XCTest - -public func XCTPrettyAssertEqual(_ before: T, _ after: T, _ mode: DiffMode = .full, file: StaticString = #file, line: UInt = #line) { - guard let diff = debugDiff(before, after, mode) - .map({ "\($0.indent(by: 4))\n\n(Expected: −, Received: +)" }) else { return } - XCTFail(diff, file: file, line: line) -} diff --git a/DiffingUtility/Debug.swift b/DiffingUtility/Debug.swift deleted file mode 100644 index 9fc70a1..0000000 --- a/DiffingUtility/Debug.swift +++ /dev/null @@ -1,253 +0,0 @@ -import Foundation - -public func debugOutput(_ value: Any, indent: Int = 0) -> String { - var visitedItems: Set = [] - - func debugOutputHelp(_ value: Any, indent: Int = 0) -> String { - let mirror = Mirror(reflecting: value) - switch (value, mirror.displayStyle) { - case let (value as CustomDebugOutputConvertible, _): - return value.debugOutput.indent(by: indent) - case (_, .collection?): - return """ - [ - \(mirror.children.map { "\(debugOutput($0.value, indent: 2)),\n" }.joined())] - """ - .indent(by: indent) - - case (_, .dictionary?): - let pairs = mirror.children.map { _, value -> String in - let pair = value as! (key: AnyHashable, value: Any) - return - "\("\(debugOutputHelp(pair.key.base)): \(debugOutputHelp(pair.value)),".indent(by: 2))\n" - } - return """ - [ - \(pairs.sorted().joined())] - """ - .indent(by: indent) - - case (_, .set?): - return """ - Set([ - \(mirror.children.map { "\(debugOutputHelp($0.value, indent: 2)),\n" }.sorted().joined())]) - """ - .indent(by: indent) - - case (_, .optional?): - return mirror.children.isEmpty - ? "nil".indent(by: indent) - : debugOutputHelp(mirror.children.first!.value, indent: indent) - - case (_, .enum?) where !mirror.children.isEmpty: - let child = mirror.children.first! - let childMirror = Mirror(reflecting: child.value) - let elements = - childMirror.displayStyle != .tuple - ? debugOutputHelp(child.value, indent: 2) - : childMirror.children.map { child -> String in - let label = child.label! - return "\(label.hasPrefix(".") ? "" : "\(label): ")\(debugOutputHelp(child.value))" - } - .joined(separator: ",\n") - .indent(by: 2) - return """ - \(mirror.subjectType).\(child.label!)( - \(elements) - ) - """ - .indent(by: indent) - - case (_, .enum?): - return """ - \(mirror.subjectType).\(value) - """ - .indent(by: indent) - - case (_, .struct?) where !mirror.children.isEmpty: - let elements = mirror.children - .map { "\($0.label.map { "\($0): " } ?? "")\(debugOutputHelp($0.value))".indent(by: 2) } - .joined(separator: ",\n") - return """ - \(mirror.subjectType)( - \(elements) - ) - """ - .indent(by: indent) - - case let (value as AnyObject, .class?) - where !mirror.children.isEmpty && !visitedItems.contains(ObjectIdentifier(value)): - visitedItems.insert(ObjectIdentifier(value)) - let elements = mirror.children - .map { "\($0.label.map { "\($0): " } ?? "")\(debugOutputHelp($0.value))".indent(by: 2) } - .joined(separator: ",\n") - return """ - \(mirror.subjectType)( - \(elements) - ) - """ - .indent(by: indent) - - case let (value as AnyObject, .class?) - where !mirror.children.isEmpty && visitedItems.contains(ObjectIdentifier(value)): - return "\(mirror.subjectType)(↩ī¸Ž)" - - case let (value as CustomStringConvertible, .class?): - return value.description - .replacingOccurrences( - of: #"^<([^:]+): 0x[^>]+>$"#, with: "$1()", options: .regularExpression - ) - .indent(by: indent) - - case let (value as CustomDebugStringConvertible, _): - return value.debugDescription - .replacingOccurrences( - of: #"^<([^:]+): 0x[^>]+>$"#, with: "$1()", options: .regularExpression - ) - .indent(by: indent) - - case let (value as CustomStringConvertible, _): - return value.description - .indent(by: indent) - - case (_, .struct?), (_, .class?): - return "\(mirror.subjectType)()" - .indent(by: indent) - - case (_, .tuple?) where mirror.children.isEmpty: - return "()" - .indent(by: indent) - - case (_, .tuple?): - let elements = mirror.children.map { child -> String in - let label = child.label! - return "\(label.hasPrefix(".") ? "" : "\(label): ")\(debugOutputHelp(child.value))" - .indent(by: 2) - } - return """ - ( - \(elements.joined(separator: ",\n")) - ) - """ - .indent(by: indent) - - case (_, nil): - return "\(value)" - .indent(by: indent) - - @unknown default: - return "\(value)" - .indent(by: indent) - } - } - - return debugOutputHelp(value, indent: indent) -} - -public func debugDiff(_ before: T, _ after: T, _ mode: DiffMode = .full, printer: (T) -> String = { debugOutput($0) }) -> String? { - diff(printer(before), printer(after), mode) -} - -extension String { - public func indent(by indent: Int) -> String { - let indentation = String(repeating: " ", count: indent) - return indentation + replacingOccurrences(of: "\n", with: "\n\(indentation)") - } -} - -public protocol CustomDebugOutputConvertible { - var debugOutput: String { get } -} - -extension Date: CustomDebugOutputConvertible { - public var debugOutput: String { - dateFormatter.string(from: self) - } -} - -private let dateFormatter: ISO8601DateFormatter = { - let formatter = ISO8601DateFormatter() - formatter.timeZone = TimeZone(identifier: "UTC")! - return formatter -}() - -extension DispatchQueue: CustomDebugOutputConvertible { - public var debugOutput: String { - switch (self, label) { - case (.main, _): return "DispatchQueue.main" - case (_, "com.apple.root.default-qos"): return "DispatchQueue.global()" - case (_, _) where label == "com.apple.root.\(qos.qosClass)-qos": - return "DispatchQueue.global(qos: .\(qos.qosClass))" - default: - return "DispatchQueue(label: \(label.debugDescription), qos: .\(qos.qosClass))" - } - } -} - -extension OperationQueue: CustomDebugOutputConvertible { - public var debugOutput: String { - switch (self, name) { - case (.main, _): return "OperationQueue.main" - default: return "OperationQueue()" - } - } -} - -extension RunLoop: CustomDebugOutputConvertible { - public var debugOutput: String { - switch self { - case .main: return "RunLoop.main" - default: return "RunLoop()" - } - } -} - -extension URL: CustomDebugOutputConvertible { - public var debugOutput: String { - absoluteString - } -} - -#if DEBUG - #if canImport(CoreLocation) - import CoreLocation - extension CLAuthorizationStatus: CustomDebugOutputConvertible { - public var debugOutput: String { - switch self { - case .notDetermined: - return "notDetermined" - case .restricted: - return "restricted" - case .denied: - return "denied" - case .authorizedAlways: - return "authorizedAlways" - case .authorizedWhenInUse: - return "authorizedWhenInUse" - @unknown default: - return "unknown" - } - } - } - #endif - - #if canImport(Speech) - import Speech - extension SFSpeechRecognizerAuthorizationStatus: CustomDebugOutputConvertible { - public var debugOutput: String { - switch self { - case .notDetermined: - return "notDetermined" - case .denied: - return "denied" - case .restricted: - return "restricted" - case .authorized: - return "authorized" - @unknown default: - return "unknown" - } - } - } - #endif -#endif diff --git a/DiffingUtility/Diff.swift b/DiffingUtility/Diff.swift deleted file mode 100644 index 9d29c6e..0000000 --- a/DiffingUtility/Diff.swift +++ /dev/null @@ -1,101 +0,0 @@ -import Foundation - -public enum DiffMode { - case full - case distinct -} - -public func diff(_ first: String, _ second: String, _ mode: DiffMode = .full) -> String? { - struct Difference { - enum Which { - case both - case first - case second - - var prefix: StaticString { - switch self { - case .both: return "\u{2007}" - case .first: return "−" - case .second: return "+" - } - } - } - - let elements: ArraySlice - let which: Which - } - - func diffHelp(_ first: ArraySlice, _ second: ArraySlice) -> [Difference] { - var indicesForLine: [Substring: [Int]] = [:] - for (firstIndex, firstLine) in zip(first.indices, first) { - indicesForLine[firstLine, default: []].append(firstIndex) - } - - var overlap: [Int: Int] = [:] - var firstIndex = first.startIndex - var secondIndex = second.startIndex - var count = 0 - - for (index, secondLine) in zip(second.indices, second) { - var innerOverlap: [Int: Int] = [:] - var innerFirstIndex = firstIndex - var innerSecondIndex = secondIndex - var innerCount = count - - indicesForLine[secondLine]?.forEach { firstIndex in - let newCount = (overlap[firstIndex - 1] ?? 0) + 1 - innerOverlap[firstIndex] = newCount - if newCount > count { - innerFirstIndex = firstIndex - newCount + 1 - innerSecondIndex = index - newCount + 1 - innerCount = newCount - } - } - - overlap = innerOverlap - firstIndex = innerFirstIndex - secondIndex = innerSecondIndex - count = innerCount - } - - if count == 0 { - var differences: [Difference] = [] - if !first.isEmpty { differences.append(Difference(elements: first, which: .first)) } - if !second.isEmpty { differences.append(Difference(elements: second, which: .second)) } - return differences - } else { - var differences = diffHelp(first.prefix(upTo: firstIndex), second.prefix(upTo: secondIndex)) - differences.append( - Difference(elements: first.suffix(from: firstIndex).prefix(count), which: .both)) - differences.append( - contentsOf: diffHelp( - first.suffix(from: firstIndex + count), second.suffix(from: secondIndex + count) - ) - ) - return differences - } - } - - let unfilteredDifferences = diffHelp( - first.split(separator: "\n", omittingEmptySubsequences: false)[...], - second.split(separator: "\n", omittingEmptySubsequences: false)[...] - ) - - let differences: [Difference] = { - switch mode { - case .full: - return unfilteredDifferences - case .distinct: - return unfilteredDifferences.filter { $0.which != .both } - } - }() - - if differences.count == 1, case .both = differences[0].which { return nil } - var string = differences.reduce(into: "") { string, diff in - diff.elements.forEach { line in - string += "\(diff.which.prefix) \(line)\n" - } - } - string.removeLast() - return string -} diff --git a/DiffingUtility/DiffingUtility.docc/DiffingUtility.md b/DiffingUtility/DiffingUtility.docc/DiffingUtility.md deleted file mode 100755 index d20c6a7..0000000 --- a/DiffingUtility/DiffingUtility.docc/DiffingUtility.md +++ /dev/null @@ -1,13 +0,0 @@ -# ``DiffingUtility`` - -Summary - -## Overview - -Text - -## Topics - -### Group - -- ``Symbol`` \ No newline at end of file diff --git a/DiffingUtility/DiffingUtility.h b/DiffingUtility/DiffingUtility.h deleted file mode 100644 index 4e88f45..0000000 --- a/DiffingUtility/DiffingUtility.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// DiffingUtility.h -// DiffingUtility -// -// Created by Andrey Yoshua Manik on 21/02/22. -// - -#import - -//! Project version number for DiffingUtility. -FOUNDATION_EXPORT double DiffingUtilityVersionNumber; - -//! Project version string for DiffingUtility. -FOUNDATION_EXPORT const unsigned char DiffingUtilityVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/Podfile b/Podfile deleted file mode 100644 index 26c757a..0000000 --- a/Podfile +++ /dev/null @@ -1,39 +0,0 @@ -# Uncomment the next line to define a global platform for your project -# platform :ios, '9.0' - -target 'RxComposableArchitecture' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! - - # Pods for RxComposableArchitecture - pod 'RxSwift', '5.1.1' - pod 'RxCocoa', '5.1.1' - pod 'CasePaths', :podspec => './RxComposableArchitecture/development-podspecs/CasePaths.podspec.json' - -end - -target 'RxComposableArchitectureTests' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! - - # Pods for RxComposableArchitecture - pod 'RxSwift', '5.1.1' - -end - -target 'TestSupport' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! - - # Pods for RxComposableArchitecture - pod 'RxSwift', '5.1.1' - -end - -target 'RxComposableArchitectureExample' do - # Comment the next line if you don't want to use dynamic frameworks - use_frameworks! - - # Pods for RxComposableArchitectureExample - -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index 9e07be4..0000000 --- a/Podfile.lock +++ /dev/null @@ -1,33 +0,0 @@ -PODS: - - CasePaths (0.1.0) - - RxCocoa (5.1.1): - - RxRelay (~> 5) - - RxSwift (~> 5) - - RxRelay (5.1.3): - - RxSwift (~> 5) - - RxSwift (5.1.1) - -DEPENDENCIES: - - CasePaths (from `./RxComposableArchitecture/development-podspecs/CasePaths.podspec.json`) - - RxCocoa (= 5.1.1) - - RxSwift (= 5.1.1) - -SPEC REPOS: - trunk: - - RxCocoa - - RxRelay - - RxSwift - -EXTERNAL SOURCES: - CasePaths: - :podspec: "./RxComposableArchitecture/development-podspecs/CasePaths.podspec.json" - -SPEC CHECKSUMS: - CasePaths: 24576a4ae8972199d6195c91d6474896755664ed - RxCocoa: 32065309a38d29b5b0db858819b5bf9ef038b601 - RxRelay: 5a18c2eb2d68326ebaf0112f80d837ae41b92b97 - RxSwift: 81470a2074fa8780320ea5fe4102807cb7118178 - -PODFILE CHECKSUM: b186eb071dc125f12c8b55c07e3715069964f397 - -COCOAPODS: 1.11.2 diff --git a/RxComposableArchitecture/Binding.swift b/RxComposableArchitecture/Binding.swift deleted file mode 100644 index 0b3bbd0..0000000 --- a/RxComposableArchitecture/Binding.swift +++ /dev/null @@ -1,216 +0,0 @@ -// -// Binding.swift -// RxComposableArchitecture_RxComposableArchitecture -// -// Created by Jefferson Setiawan on 23/03/21. -// - -import CasePaths - -/// An action that describes simple mutations to some root state at a writable key path. -/// -/// This type can be used to eliminate the boilerplate that is typically incurred when working with -/// multiple mutable fields on state. -/// -/// For example, a settings screen may model its state with the following struct: -/// -/// struct SettingsState { -/// var digest = Digest.daily -/// var displayName = "" -/// var enableNotifications = false -/// var protectMyPosts = false -/// var sendEmailNotifications = false -/// var sendMobileNotifications = false -/// } -/// -/// Each of these fields should be editable, and in the Composable Architecture this means that each -/// field requires a corresponding action that can be sent to the store. Typically this comes in the -/// form of an enum with a case per field: -/// -/// enum SettingsAction { -/// case digestChanged(Digest) -/// case displayNameChanged(String) -/// case enableNotificationsChanged(Bool) -/// case protectMyPostsChanged(Bool) -/// case sendEmailNotificationsChanged(Bool) -/// case sendMobileNotificationsChanged(Bool) -/// } -/// -/// And we're not even done yet. In the reducer we must now handle each action, which simply -/// replaces the state at each field with a new value: -/// -/// let settingsReducer = Reducer< -/// SettingsState, SettingsAction, SettingsEnvironment -/// > { state, action, environment in -/// switch action { -/// case let digestChanged(digest): -/// state.digest = digest -/// return .none -/// -/// case let displayNameChanged(displayName): -/// state.displayName = displayName -/// return .none -/// -/// case let enableNotificationsChanged(isOn): -/// state.enableNotifications = isOn -/// return .none -/// -/// case let protectMyPostsChanged(isOn): -/// state.protectMyPosts = isOn -/// return .none -/// -/// case let sendEmailNotificationsChanged(isOn): -/// state.sendEmailNotifications = isOn -/// return .none -/// -/// case let sendMobileNotificationsChanged(isOn): -/// state.sendMobileNotifications = isOn -/// return .none -/// } -/// } -/// -/// This is a _lot_ of boilerplate for something that should be simple. Luckily, we can dramatically -/// eliminate this boilerplate using `BindingAction`. First, we can collapse all of these -/// field-mutating actions into a single case that holds a `BindingAction` generic over the -/// reducer's root `SettingsState`: -/// -/// enum SettingsAction { -/// case binding(BindingAction) -/// } -/// -/// And then, we can simplify the settings reducer by allowing the `binding` method to handle these -/// field mutations for us: -/// -/// let settingsReducer = Reducer< -/// SettingsState, SettingsAction, SettingsEnvironment -/// > { -/// switch action { -/// case .binding: -/// return .none -/// } -/// } -/// .binding(action: /SettingsAction.binding) -/// -/// Binding actions are constructed and sent to the store by providing a writable key path from root -/// state to the field being mutated. There is even a view store helper that simplifies this work. -/// You can derive a binding by specifying the key path and binding action case: -/// -/// TextField( -/// "Display name", -/// text: viewStore.binding(keyPath: \.displayName, send: SettingsAction.binding) -/// ) -/// -/// Should you need to layer additional functionality over these bindings, your reducer can pattern -/// match the action for a given key path: -/// -/// case .binding(\.displayName): -/// // Validate display name -/// -/// case .binding(\.enableNotifications): -/// // Return an authorization request effect -/// -/// Binding actions can also be tested in much the same way regular actions are tested. Rather than -/// send a specific action describing how a binding changed, such as `displayNameChanged("Blob")`, -/// you will send a `.binding` action that describes which key path is being set to what value, such -/// as `.binding(.set(\.displayName, "Blob"))`: -/// -/// let store = TestStore( -/// initialState: SettingsState(), -/// reducer: settingsReducer, -/// environment: SettingsEnvironment(...) -/// ) -/// -/// store.send(.binding(.set(\.displayName, "Blob"))) { -/// $0.displayName = "Blob" -/// } -/// store.send(.binding(.set(\.protectMyPosts, true))) { -/// $0.protectMyPosts = true -/// ) -/// -public struct BindingAction: Equatable { - public let keyPath: PartialKeyPath - - fileprivate let set: (inout Root) -> Void - private let value: Any - private let valueIsEqualTo: (Any) -> Bool - - /// Returns an action that describes simple mutations to some root state at a writable key path. - /// - /// - Parameters: - /// - keyPath: A key path to the property that should be mutated. - /// - value: A value to assign at the given key path. - /// - Returns: An action that describes simple mutations to some root state at a writable key - /// path. - public static func set( - _ keyPath: WritableKeyPath, - _ value: Value - ) -> Self - where Value: Equatable { - .init( - keyPath: keyPath, - set: { $0[keyPath: keyPath] = value }, - value: value, - valueIsEqualTo: { $0 as? Value == value } - ) - } - - /// Transforms a binding action over some root state to some other type of root state given a key - /// path. - /// - /// - Parameter keyPath: A key path from a new type of root state to the original root state. - /// - Returns: A binding action over a new type of root state. - public func pullback( - _ keyPath: WritableKeyPath - ) -> BindingAction { - .init( - keyPath: (keyPath as AnyKeyPath).appending(path: self.keyPath) as! PartialKeyPath, - set: { self.set(&$0[keyPath: keyPath]) }, - value: value, - valueIsEqualTo: valueIsEqualTo - ) - } - - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.keyPath == rhs.keyPath && lhs.valueIsEqualTo(rhs.value) - } - - public static func ~= ( - keyPath: WritableKeyPath, - bindingAction: Self - ) -> Bool { - keyPath == bindingAction.keyPath - } -} - -extension Reducer { - /// Returns a reducer that applies `BindingAction` mutations to `State` before running this - /// reducer's logic. - /// - /// For example, a settings screen may gather its binding actions into a single `BindingAction` - /// case: - /// - /// enum SettingsAction { - /// ... - /// case binding(BindingAction) - /// } - /// - /// The reducer can then be enhanced to automatically handle these mutations for you by tacking on - /// the `binding` method: - /// - /// let settingsReducer = Reducer>) -> Self { - Self { state, action, _ in - toBindingAction.extract(from: action)?.set(&state) - return .none - } - .combined(with: self) - } -} diff --git a/RxComposableArchitecture/Debugging/Bootstrapping.swift b/RxComposableArchitecture/Debugging/Bootstrapping.swift deleted file mode 100644 index c4128f4..0000000 --- a/RxComposableArchitecture/Debugging/Bootstrapping.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// Bootstrapping.swift -// RxComposableArchitecture -// -// Created by Wendy Liga on 25/06/21. -// - -import Foundation - -#if DEBUG - /** - A Way to Mock/Inject custom behaviour to your TCA. - with `Bootstrap` you can inject your custom `Environment` to your page. - You can injected it like on your `Example`, or using `MainApp TCA Bootstrapping Tweak` options on - `Tweaks -> Others -> Bootstrapping`. (will explain later) - - let's say, our page have this environment - ```swift - struct Environment { - var request: () -> Effect> - ... - ... - } - ``` - - ## Injecting custom Environment - - we want to inject custom behaviour like if request fail, - we will create the fail `Environment` case, and inject it. - - ```swift - let requestFail = Environment { - request: { - return Effect(value: .failure(.serverError)) - } - } - ``` - - once we have our Environment, we can inject it by - calling `mock(_:)` function on `Bootstrap` - - ```swift - Bootstrap.mock(requestFail) - ``` - - you can expect your feature to have this custom behaviour right away, - you *do not* need to restart the simulator, or reinit your page. - - ## Reset custom Environment - - once you inject something, it will be there for the rest of app session(means untill app is killed). - maybe after testing and playing with custom behaviour, you want to go back to 'live' or production behaviour, - then you need to reset it by - - ```swift - Bootstrap.clear(_YOUR_ENVIRONMENT_TYPE) - ``` - - so on our example - ```swift - Bootstrap.clear(Environment.self) - ``` - it will clear the custom behaviour and you can expect it right away like when you custom it in the first place. - - - Warning: - the way bootstrap works is each `Envrionment` type will become identifier. means you only can inject one at a time for spesific `Environment` type. - */ - public struct Bootstrap { - /// Inject your custom `Environment` - /// - /// ## Example - /// - /// ```swift - /// Bootstrap.mock(HomeEnvironment.self) - /// ``` - /// - Parameter environment: `Environment` to be injected - public static func mock(environment: Environment) { - guard type(of: environment) != Void.self else { - assertionFailure("You made a mistake by passing Void as a param, you never need to mock the Void") - return - } - - _bootstrappedEnvironments[String(reflecting: Environment.self)] = environment - } - - /// Clear Previous custom injected `Environment` - /// - /// ## Example - /// - /// ```swift - /// let requestFail = Environment { - /// request: { - /// return Effect(value: .failure(.serverError)) - /// } - /// } - /// - /// Bootstrap.mock(requestFail) - /// ``` - /// - /// - Parameter environment: `Environment` type - public static func clear(environment _: Environment.Type) { - clear(String(reflecting: Environment.self)) - } - - /// fetch bootstrapped environment from given `Environment` type if exist - /// - Parameter : `Environment` type - /// - Returns: `Environment` from `_bootstrappedEnvironments` by given type - internal static func get(environment _: Environment.Type) -> Environment? { - _bootstrappedEnvironments[String(reflecting: Environment.self)] as? Environment - } - - /// clear from spesific id - /// - Warning: this api only supposed to be used on `BootstrapPicker` - /// - Parameter id: environment type in string - internal static func clear(_ id: String) { - _bootstrappedEnvironments.removeValue(forKey: id) - } - - /// clear all bootstrapped environment - /// - Warning: this api only supposed to be used on `BootstrapPicker` - internal static func clearAll() { - _bootstrappedEnvironments.removeAll() - } - - /// get all Bootstrapped identifier - /// - Warning: this api only supposed to be used on `BootstrapPicker` - /// - Returns: all active indentifier - internal static func getAllBootstrappedIdentifier() -> [String] { - _bootstrappedEnvironments.map(\.key) - } - } - - internal var _bootstrappedEnvironments: [String: Any] = [:] -#endif diff --git a/RxComposableArchitecture/Debugging/MockPageTemplate.swift b/RxComposableArchitecture/Debugging/MockPageTemplate.swift deleted file mode 100644 index 66eb125..0000000 --- a/RxComposableArchitecture/Debugging/MockPageTemplate.swift +++ /dev/null @@ -1,240 +0,0 @@ -// -// MockPageTemplate.swift -// RxComposableArchitecture -// -// Created by Wendy Liga on 07/07/21. -// - -/** - Use this template to help you create a list of behaviour on your `Example`, and also used it on MainApp's `BootstrapPicker`. - Here is an example, i create new VC named `MockViewController` to be used on `Example` and MainApp's `BootstrapPicker`. - - ## Example - ```swift - public final class MockViewController: MockPageTemplate { - public init(isExample: Bool) { - let mockNoInternet = Mock( - title: "No Internet", - apply: { [weak self] in - let noInternetEnvironment = HomeEnvironment(...) - Bootstrap.mock(environment: noInternetEnvironment) - - if isExample { - let homeViewController = HomeViewController() - self?.navigationController.pushViewController(homeViewController, animated: true) - } else { - Toast.shared.display("Injected") - } - } - ) - - var sections = [ - Section( - title: "Mock", - mocks: [mockNoInternet, ...] - ), - ... - ] - super.init(sections: sections) - } - - public required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - ``` - - then on my Example `AppDelegate`. We recommend to place `MockViewController` on separate/new framework, so example and main app can import it. - - the parameter `isExample` here is very critical, because it gives flag to our `Mock` `apply(_:_:)` to push new viewController or not. - on main app, we do not want to push viewController, but just apply `Bootstrap` - ```swift - internal func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - window = UIWindow(frame: UIScreen.main.bounds) - - + let viewController = MockViewController(isExample: true) - let navController = UINavigationController(rootViewController: viewController) - navController.navigationBar.isTranslucent = false - window?.rootViewController = navController - window?.makeKeyAndVisible() - - return true - } - ``` - */ -#if DEBUG - import UIKit - - public struct Section { - public let title: String - public let mocks: [Mock] - - public init(title: String, mocks: [Mock]) { - self.title = title - self.mocks = mocks - } - } - - // MARK: - Mock - - public struct Mock { - public let title: String - - /// closure to apply the `Mock`, - public let apply: () -> Void - - public init(title: String, apply: @escaping () -> Void) { - self.title = title - self.apply = apply - } - } - - open class MockPageTemplate: UITableViewController { - // MARK: - Interface - - /// sections dataSource - public var sections: [Section] { - didSet { - applySearchToSection(data: sections) - } - } - - // MARK: - Values - - private var searchKeyword: String? - - /// section that's filtered by `searchKeyword` - private var filteredSections: [Section] = [] - - // MARK: - Views - - private lazy var searchBar: UISearchBar = { - let view = UISearchBar(frame: CGRect(x: 0, y: 0, width: 200, height: 20)) - view.placeholder = "search with name" - view.delegate = self - view.showsCancelButton = true - - if #available(iOS 13.0, *) { - view.searchTextField.clearButtonMode = .never - } - - return view - }() - - private lazy var searchBarButtonItem = UIBarButtonItem( - barButtonSystemItem: .search, - target: self, - action: #selector(showSearchBar) - ) - - // MARK: - Life Cycle - - /// Init - /// - Parameters: - /// - sections: sections dataSource - /// - isExample: is used on `Example` ? - public init(sections: [Section]) { - self.sections = sections - - if #available(iOS 13.0, *) { - super.init(style: .insetGrouped) - } else { - super.init(style: .grouped) - } - - title = "Mocks" - tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") - } - - public required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override open func viewDidLoad() { - super.viewDidLoad() - - navigationItem.rightBarButtonItem = searchBarButtonItem - } - - // MARK: - Function - - private func applySearchToSection(data: [Section]) { - guard let searchKeyword = searchKeyword, !searchKeyword.isEmpty else { - filteredSections = data - return - } - - filteredSections = data - .compactMap { section -> Section? in - let filteredMocks = section.mocks - .filter { $0.title.lowercased().contains(searchKeyword.lowercased()) } - - if filteredMocks.isEmpty { - return nil - } - - return Section(title: section.title, mocks: filteredMocks) - } - - tableView.reloadData() - } - - @objc - private func showSearchBar() { - navigationItem.titleView = searchBar - navigationItem.rightBarButtonItem = nil - } - - private func hideSearchBar() { - navigationItem.rightBarButtonItem = searchBarButtonItem - navigationItem.titleView = nil - } - } - - extension MockPageTemplate { - override open func numberOfSections(in _: UITableView) -> Int { - filteredSections.count - } - - override open func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int { - filteredSections[section].mocks.count - } - - override open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - filteredSections[indexPath.section].mocks[indexPath.row].apply() - } - - override open func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else { - return UITableViewCell() - } - - cell.textLabel?.numberOfLines = 0 - cell.textLabel?.text = filteredSections[indexPath.section].mocks[indexPath.row].title - - return cell - } - - override open func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? { - filteredSections[section].title - } - } - - extension MockPageTemplate: UISearchBarDelegate { - public func searchBarCancelButtonClicked(_: UISearchBar) { - hideSearchBar() - } - - public func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { - searchKeyword = searchBar.text - applySearchToSection(data: sections) - hideSearchBar() - } - - public func searchBar(_: UISearchBar, textDidChange searchText: String) { - searchKeyword = searchText - applySearchToSection(data: sections) - } - } -#endif diff --git a/RxComposableArchitecture/Debugging/ReducerDebugging.swift b/RxComposableArchitecture/Debugging/ReducerDebugging.swift deleted file mode 100644 index 05a9ecd..0000000 --- a/RxComposableArchitecture/Debugging/ReducerDebugging.swift +++ /dev/null @@ -1,256 +0,0 @@ -// -// ReducerDebugging.swift -// RxComposableArchitecture -// -// Created by Wendy Liga on 19/05/20. -// - -import CasePaths -import DiffingUtility -import Dispatch - -/// Determines how the string description of an action should be printed when using the `.debug()` -/// higher-order reducer. -public enum ActionFormat { - /// Prints the action in a single line by only specifying the labels of the associated values: - /// - /// Action.screenA(.row(index:, action: .textChanged(query:))) - case labelsOnly - /// Prints the action in a multiline, pretty-printed format, including all the labels of - /// any associated values, as well as the data held in the associated values: - /// - /// Action.screenA( - /// ScreenA.row( - /// index: 1, - /// action: RowAction.textChanged( - /// query: "Hi" - /// ) - /// ) - /// ) - case prettyPrint -} - -/// A container for storing action filters. -/// -/// The logic behind having this rather than a normal closure is that it allows us to namespace and gather action filters together in a consistent manner. -/// - Note: You should be adding extensions in your modules and exposing common filters you might want to use to focus your debugging work, e.g. -/// ```swift -/// extension ActionFilter where Action == AppAction { -/// static var windowActions: Self { -/// Self(isIncluded: { -/// switch $0 { -/// case .windows: -/// return true -/// default: -/// return false -/// } -/// }) -/// } -/// } -/// ``` -public struct ActionFilter { - private let isIncluded: (Action) -> Bool - - public init(isIncluded: @escaping (Action) -> Bool) { - self.isIncluded = isIncluded - } - - public func callAsFunction(_ action: Action) -> Bool { - isIncluded(action) - } - - /// Include all actions - public static var all: Self { - .init(isIncluded: { _ in true }) - } - - /// negates the filter - public static func not(_ filter: Self) -> Self { - .init(isIncluded: { !filter($0) }) - } - - /// Allows all actions except those specified - public static func allExcept(_ actions: Self...) -> Self { - allExcept(actions) - } - - /// Allows all actions except those specified - public static func allExcept(_ actions: [Self]) -> Self { - .init(isIncluded: { action in - !actions.contains(where: { $0(action) }) - }) - } - - /// Allows any of the specified actions - public static func anyOf(_ actions: Self...) -> Self { - .anyOf(actions) - } - - /// Allows any of the specified actions - public static func anyOf(_ actions: [Self]) -> Self { - .init(isIncluded: { action in - actions.contains(where: { $0(action) }) - }) - } -} - -extension Reducer { - /// Prints debug messages describing all received actions and state mutations. - /// - /// Printing is only done in debug (`#if DEBUG`) builds. - /// - /// - Parameters: - /// - prefix: A string with which to prefix all debug messages. - /// - toDebugEnvironment: A function that transforms an environment into a debug environment by - /// describing a print function and a queue to print from. Defaults to a function that ignores - /// the environment and returns a default `DebugEnvironment` that uses Swift's `print` - /// function and a background queue. - /// - Returns: A reducer that prints debug messages for all received actions. - public func debug( - _ prefix: String = "", - actionFormat: ActionFormat = .prettyPrint, - environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in - DebugEnvironment() - } - ) -> Reducer { - debug( - prefix, - state: { $0 }, - action: .self, - actionFormat: actionFormat, - environment: toDebugEnvironment - ) - } - - /// Prints debug messages describing all received actions. - /// - /// Printing is only done in debug (`#if DEBUG`) builds. - /// - /// - Parameters: - /// - prefix: A string with which to prefix all debug messages. - /// - toDebugEnvironment: A function that transforms an environment into a debug environment by - /// describing a print function and a queue to print from. Defaults to a function that ignores - /// the environment and returns a default `DebugEnvironment` that uses Swift's `print` - /// function and a background queue. - /// - Returns: A reducer that prints debug messages for all received actions. - public func debugActions( - _ prefix: String = "", - actionFormat: ActionFormat = .prettyPrint, - environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in - DebugEnvironment() - } - ) -> Reducer { - debug( - prefix, - state: { _ in () }, - action: .self, - actionFormat: actionFormat, - environment: toDebugEnvironment - ) - } - - public func debug( - _ prefix: String = "", - actionFormat: ActionFormat = .prettyPrint, - allowedActions: ActionFilter = .all, - environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in - DebugEnvironment() - } - ) -> Reducer { - debug( - prefix, - state: { _ in () }, - action: .self, - actionFormat: actionFormat, - allowedActions: allowedActions, - environment: toDebugEnvironment - ) - } - - /// Prints debug messages describing all received local actions and local state mutations. - /// - /// Printing is only done in debug (`#if DEBUG`) builds. - /// - /// - Parameters: - /// - prefix: A string with which to prefix all debug messages. - /// - toLocalState: A function that filters state to be printed. - /// - toLocalAction: A case path that filters actions that are printed. - /// - toDebugEnvironment: A function that transforms an environment into a debug environment by - /// describing a print function and a queue to print from. Defaults to a function that ignores - /// the environment and returns a default `DebugEnvironment` that uses Swift's `print` - /// function and a background queue. - /// - Returns: A reducer that prints debug messages for all received actions. - public func debug( - _ prefix: String = "", - state toLocalState: @escaping (State) -> LocalState, - action toLocalAction: CasePath, - actionFormat: ActionFormat = .prettyPrint, - allowedActions: ActionFilter = .all, - environment toDebugEnvironment: @escaping (Environment) -> DebugEnvironment = { _ in - DebugEnvironment() - } - ) -> Reducer { - #if DEBUG - return .init { state, action, environment in - let previousState = toLocalState(state) - let effects = self.run(&state, action, environment) - guard let localAction = toLocalAction.extract(from: action) else { return effects } - let nextState = toLocalState(state) - let debugEnvironment = toDebugEnvironment(environment) - guard allowedActions(action) else { - return effects - } - - return .merge( - .fireAndForget { - debugEnvironment.queue.async { - let actionOutput = - actionFormat == .prettyPrint - ? debugOutput(localAction).indent(by: 2) - : debugCaseOutput(localAction).indent(by: 2) - let stateOutput = - LocalState.self == Void.self - ? "" - : debugDiff(previousState, nextState).map { "\($0)\n" } ?? " (No state changes)\n" - debugEnvironment.printer( - """ - \(prefix.isEmpty ? "" : "\(prefix): ")received action: - \(actionOutput) - \(stateOutput) - """ - ) - } - }, - effects - ) - } - #else - return self - #endif - } -} - -/// An environment for debug-printing reducers. -public struct DebugEnvironment { - public var printer: (String) -> Void - public var queue: DispatchQueue - - public init( - printer: @escaping (String) -> Void = { print($0) }, - queue: DispatchQueue - ) { - self.printer = printer - self.queue = queue - } - - public init( - printer: @escaping (String) -> Void = { print($0) } - ) { - self.init(printer: printer, queue: _queue) - } -} - -private let _queue = DispatchQueue( - label: "com.tokopedia.Tokopedia.DebugEnvironment", - qos: .background -) diff --git a/RxComposableArchitecture/Debugging/ReducerInstrumentation.swift b/RxComposableArchitecture/Debugging/ReducerInstrumentation.swift deleted file mode 100644 index 87ba211..0000000 --- a/RxComposableArchitecture/Debugging/ReducerInstrumentation.swift +++ /dev/null @@ -1,124 +0,0 @@ -// -// ReducerInstrumentation.swift -// RxComposableArchitecture -// -// Created by Wendy Liga on 28/05/20. -// - -import os.signpost -import RxSwift - -extension Reducer { - /// Instruments the reducer with - /// - /// ⚠ī¸ Only works on iOS 12 or newer - /// - /// [signposts](https://developer.apple.com/documentation/os/logging/recording_performance_data). - /// Each invocation of the reducer will be measured by an interval, and the lifecycle of its - /// effects will be measured with interval and event signposts. - /// - /// To use, build your app for Instruments (⌘I), create a blank instrument, and then use the "+" - /// icon at top right to add the signpost instrument. Start recording your app (red button at top - /// left) and then you should see timing information for every action sent to the store and every - /// effect executed. - /// - /// Effect instrumentation can be particularly useful for inspecting the lifecycle of long-living - /// effects. For example, if you start an effect (e.g. a location manager) in `onAppear` and - /// forget to tear down the effect in `onDisappear`, it will clearly show in Instruments that the - /// effect never completed. - /// - /// - Parameters: - /// - prefix: A string to print at the beginning of the formatted message for the signpost. - /// - log: An `OSLog` to use for signposts. - /// - Returns: A reducer that has been enhanced with instrumentation. - public func signpost( - _ prefix: String = "", - log: OSLog = OSLog( - subsystem: "com.tokopedia.Tokopedia", - category: "Reducer Instrumentation" - ) - ) -> Self { - if #available(iOS 12.0, *) { - guard log.signpostsEnabled else { return self } - - // NB: Prevent rendering as "N/A" in Instruments - let zeroWidthSpace = "\u{200B}" - - let prefix = prefix.isEmpty ? zeroWidthSpace : "[\(prefix)] " - - return Self { state, action, environment in - var actionOutput: String! - if log.signpostsEnabled { - actionOutput = debugCaseOutput(action) - os_signpost(.begin, log: log, name: "Action", "%s%s", prefix, actionOutput) - } - let effects = self.run(&state, action, environment) - if log.signpostsEnabled { - os_signpost(.end, log: log, name: "Action") - return - effects - .effectSignpost(prefix, log: log, actionOutput: actionOutput) - .eraseToEffect() - } - return effects - } - } else { - return self - } - } -} - -extension ObservableType { - @available(iOS 12.0, *) - internal func effectSignpost(_ prefix: String, log: OSLog, actionOutput: String) -> Observable { - let sid = OSSignpostID(log: log) - - return `do`( - onNext: { _ in - os_signpost(.event, log: log, name: "Effect Output", "%sOutput from %s", prefix, actionOutput) - }, - onCompleted: { - os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sFinished", prefix) - }, - onSubscribe: { - os_signpost(.begin, log: log, name: "Effect", signpostID: sid, "%sStarting from %s", prefix, actionOutput) - }, - onSubscribed: { - os_signpost(.begin, log: log, name: "Effect", signpostID: sid, "%sStarted from %s", prefix, actionOutput) - }, - onDispose: { - os_signpost(.end, log: log, name: "Effect", signpostID: sid, "%sDisposed", prefix) - } - ) - } -} - -internal func debugCaseOutput(_ value: Any) -> String { - func debugCaseOutputHelp(_ value: Any) -> String { - let mirror = Mirror(reflecting: value) - switch mirror.displayStyle { - case .enum: - guard let child = mirror.children.first else { - let childOutput = "\(value)" - return childOutput == "\(type(of: value))" ? "" : ".\(childOutput)" - } - let childOutput = debugCaseOutputHelp(child.value) - return ".\(child.label ?? "")\(childOutput.isEmpty ? "" : "(\(childOutput))")" - case .tuple: - return mirror.children.map { label, value in - let childOutput = debugCaseOutputHelp(value) - return - "\(label.map { isUnlabeledArgument($0) ? "_:" : "\($0):" } ?? "")\(childOutput.isEmpty ? "" : " \(childOutput)")" - } - .joined(separator: ", ") - default: - return "" - } - } - - return "\(type(of: value))\(debugCaseOutputHelp(value))" -} - -private func isUnlabeledArgument(_ label: String) -> Bool { - !label.contains(where: { $0 != "." && !$0.isNumber }) -} diff --git a/RxComposableArchitecture/Effect.swift b/RxComposableArchitecture/Effect.swift deleted file mode 100644 index 97592f8..0000000 --- a/RxComposableArchitecture/Effect.swift +++ /dev/null @@ -1,311 +0,0 @@ -import Foundation -import RxSwift - -/// The `Effect` type encapsulates a unit of work that can be run in the outside world, and can feed -/// data back to the `Store`. It is the perfect place to do side effects, such as network requests, -/// saving/loading from disk, creating timers, interacting with dependencies, and more. -/// -/// Effects are returned from reducers so that the `Store` can perform the effects after the reducer -/// is done running. It is important to note that `Store` is not thread safe, and so all effects -/// must receive values on the same thread, **and** if the store is being used to drive UI then it -/// must receive values on the main thread. -/// -/// An effect simply wraps a `Publisher` value and provides some convenience initializers for -/// constructing some common types of effects. -public final class Effect: ObservableType { - public typealias Element = Output - - public let upstream: Observable - - /// Initializes an effect that wraps a publisher. Each emission of the wrapped publisher will be - /// emitted by the effect. - /// - /// This initializer is useful for turning any publisher into an effect. For example: - /// - /// Effect( - /// useCase.getData - /// .eraseToEffect() - /// ) - /// - /// - Parameter publisher: A publisher. - public init(_ observable: Observable) { - upstream = observable - } - - public func subscribe( - _ observer: Observer - ) -> Disposable where Observer: ObserverType, Element == Observer.Element { - upstream.subscribe(observer) - } - - /// Initializes an effect that immediately emits the value passed in. - /// - /// - Parameter value: The value that is immediately emitted by the effect. - public convenience init(value: Output) { - self.init(Observable.just(value)) - } - - /// Initializes an effect that immediately fails with the error passed in. - /// - /// - Parameter error: The error that is immediately emitted by the effect. - public convenience init(error: Error) { - self.init(Observable.error(error)) - } - - /// An effect that does nothing and completes immediately. Useful for situations where you must - /// return an effect, but you don't need to do anything. - public static var none: Effect { - Observable.empty().eraseToEffect() - } - - /// Creates an effect that can supply a single value asynchronously in the future. - /// - /// This can be helpful for converting APIs that are callback-based into ones that deal with - /// `Effect`s. - /// - /// For example, to create an effect that delivers an integer after waiting a second: - /// - /// Effect.future { callback in - /// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - /// callback(.success(42)) - /// } - /// } - /// - /// Note that you can only deliver a single value to the `callback`. If you send more they will be - /// discarded: - /// - /// Effect.future { callback in - /// DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - /// callback(.success(42)) - /// callback(.success(1729)) // Will not be emitted by the effect - /// } - /// } - /// - /// If you need to deliver more than one value to the effect, you should use the `Effect` - /// initializer that accepts a `Subscriber` value. - /// - /// - Parameter attemptToFulfill: A closure that takes a `callback` as an argument which can be - /// used to feed it `Result` values. - public static func future( - _ attemptToFulfill: @escaping (@escaping (Result) -> Void) -> Void - ) -> Effect { - Observable.create { observer in - attemptToFulfill { result in - switch result { - case let .success(output): - observer.onNext(output) - observer.onCompleted() - case let .failure(error): - observer.onError(error) - } - } - - return Disposables.create() - } - .eraseToEffect() - } - - /// Initializes an effect that lazily executes some work in the real world and synchronously sends - /// that data back into the store. - /// - /// For example, to load a user from some JSON on the disk, one can wrap that work in an effect: - /// - /// Effect.result { - /// let fileUrl = URL( - /// fileURLWithPath: NSSearchPathForDirectoriesInDomains( - /// .documentDirectory, .userDomainMask, true - /// )[0] - /// ) - /// .appendingPathComponent("user.json") - /// - /// let result = Result { - /// let data = try Data(contentsOf: fileUrl) - /// return try JSONDecoder().decode(User.self, from: $0) - /// } - /// - /// return result - /// } - /// - /// - Parameter attemptToFulfill: A closure encapsulating some work to execute in the real world. - /// - Returns: An effect. - public static func result(_ attemptToFulfill: @escaping () -> Result) -> Effect { - Observable.create { observer in - switch attemptToFulfill() { - case let .success(output): - observer.onNext(output) - observer.onCompleted() - case let .failure(error): - observer.onError(error) - } - - return Disposables.create() - } - .eraseToEffect() - } - - /// Initializes an effect from a callback that can send as many values as it wants, and can send - /// a completion. - /// - /// This initializer is useful for bridging callback APIs, delegate APIs, and manager APIs to the - /// `Effect` type. One can wrap those APIs in an Effect so that its events are sent through the - /// effect, which allows the reducer to handle them. - /// - /// For example, one can create an effect to ask for access to `MPMediaLibrary`. It can start by - /// sending the current status immediately, and then if the current status is `notDetermined` it - /// can request authorization, and once a status is received it can send that back to the effect: - /// - /// Effect.run { subscriber in - /// subscriber.send(MPMediaLibrary.authorizationStatus()) - /// - /// guard MPMediaLibrary.authorizationStatus() == .notDetermined else { - /// subscriber.send(completion: .finished) - /// return AnyCancellable {} - /// } - /// - /// MPMediaLibrary.requestAuthorization { status in - /// subscriber.send(status) - /// subscriber.send(completion: .finished) - /// } - /// return AnyCancellable { - /// // Typically clean up resources that were created here, but this effect doesn't - /// // have any. - /// } - /// } - /// - /// - Parameter work: A closure that accepts a `Subscriber` value and returns a cancellable. When - /// the `Effect` is completed, the cancellable will be used to clean up any resources created - /// when the effect was started. - public static func run( - _ work: @escaping (AnyObserver) -> Disposable - ) -> Effect { - Observable.create(work).eraseToEffect() - } - - /// Concatenates a variadic list of effects together into a single effect, which runs the effects - /// one after the other. - /// - /// - Parameter effects: A variadic list of effects. - /// - Returns: A new effect - public static func concatenate(_ effects: Effect...) -> Effect { - .concatenate(effects) - } - - /// Concatenates a collection of effects together into a single effect, which runs the effects one - /// after the other. - /// - /// - Parameter effects: A collection of effects. - /// - Returns: A new effect - public static func concatenate( - _ effects: C - ) -> Effect where C.Element == Effect { - guard let first = effects.first else { return .none } - - return - effects - .dropFirst() - .reduce(into: first) { effects, effect in - effects = effects.concat(effect).eraseToEffect() - } - } - - /// Merges a variadic list of effects together into a single effect, which runs the effects at the - /// same time. - /// - /// - Parameter effects: A list of effects. - /// - Returns: A new effect - public static func merge( - _ effects: Effect... - ) -> Effect { - .merge(effects) - } - - /// Merges a sequence of effects together into a single effect, which runs the effects at the same - /// time. - /// - /// - Parameter effects: A sequence of effects. - /// - Returns: A new effect - public static func merge(_ effects: S) -> Effect where S.Element == Effect { - Observable - .merge(effects.map { $0.asObservable() }) - .eraseToEffect() - } - - /// Creates an effect that executes some work in the real world that doesn't need to feed data - /// back into the store. - /// - /// - Parameter work: A closure encapsulating some work to execute in the real world. - /// - Returns: An effect. - public static func fireAndForget(_ work: @escaping () -> Void) -> Effect { - Observable.deferred { - work() - return Observable.empty() - } - .eraseToEffect() - } - - /// Transforms all elements from the upstream effect with a provided closure. - /// - /// - Parameter transform: A closure that transforms the upstream effect's output to a new output. - /// - Returns: A publisher that uses the provided closure to map elements from the upstream effect - /// to new elements that it then publishes. - public func map(_ transform: @escaping (Output) -> T) -> Effect { - .init(map(transform)) - } - - public func flatMap(_ transform: @escaping (Output) -> Effect) -> Effect { - .init(flatMap(transform)) - } -} - -extension ObservableType { - /// Turns any publisher into an `Effect`. - /// - /// This can be useful for when you perform a chain of publisher transformations in a reducer, and - /// you need to convert that publisher to an effect so that you can return it from the reducer: - /// - /// case .buttonTapped: - /// return fetchUser(id: 1) - /// .filter(\.isAdmin) - /// .eraseToEffect() - /// - /// - Returns: An effect that wraps `self`. - public func eraseToEffect() -> Effect { - Effect(asObservable()) - } - - /// Turns any publisher into an `Effect` that cannot fail by wrapping its output and failure in a - /// result. - /// - /// This can be useful when you are working with a failing API but want to deliver its data to an - /// action that handles both success and failure. - /// - /// case .buttonTapped: - /// return fetchUser(id: 1) - /// .catchToEffect() - /// .map(ProfileAction.userResponse) - /// - /// - Returns: An effect that wraps `self`. - public func catchToEffect() -> Effect> { - map(Result.success) - .catchError { Observable>.just(Result.failure($0)) } - .eraseToEffect() - } - - /// Turns any publisher into an `Effect` for any output and failure type by ignoring all output - /// and any failure. - /// - /// This is useful for times you want to fire off an effect but don't want to feed any data back - /// into the system. - /// - /// case .buttonTapped: - /// return analyticsClient.track("Button Tapped") - /// .fireAndForget() - /// - /// - Returns: An effect that never produces output or errors. - public func fireAndForget( - outputType _: NewOutput.Type = NewOutput.self - ) -> Effect { - return flatMap { _ in Observable.empty() } - .eraseToEffect() - } -} diff --git a/RxComposableArchitecture/Effects/Cancellation.swift b/RxComposableArchitecture/Effects/Cancellation.swift deleted file mode 100644 index b945f5f..0000000 --- a/RxComposableArchitecture/Effects/Cancellation.swift +++ /dev/null @@ -1,91 +0,0 @@ -import Foundation -import RxSwift - -extension Effect { - /// Turns an effect into one that is capable of being canceled. - /// - /// To turn an effect into a cancellable one you must provide an identifier, which is used in - /// `Effect.cancel(id:)` to identify which in-flight effect should be canceled. Any hashable - /// value can be used for the identifier, such as a string, but you can add a bit of protection - /// against typos by defining a new type that conforms to `Hashable`, such as an empty struct: - /// - /// struct LoadUserId: Hashable {} - /// - /// case .reloadButtonTapped: - /// // Start a new effect to load the user - /// return environment.loadUser - /// .map(Action.userResponse) - /// .cancellable(id: LoadUserId(), cancelInFlight: true) - /// - /// case .cancelButtonTapped: - /// // Cancel any in-flight requests to load the user - /// return .cancel(id: LoadUserId()) - /// - /// - Parameters: - /// - id: The effect's identifier. - /// - cancelInFlight: Determines if any in-flight effect with the same identifier should be - /// canceled before starting this new one. - /// - Returns: A new effect that is capable of being canceled by an identifier. - public func cancellable(id: AnyHashable, cancelInFlight: Bool = false) -> Effect { - let effect = Observable.deferred { - cancellablesLock.lock() - defer { cancellablesLock.unlock() } - - let subject = PublishSubject() - /// Workaround for testing `testEffectSubscriberInitializer_WithCancellation` - var values: [Output] = [] - var isCaching = true - let disposable = - self - .do(onNext: { val in - guard isCaching else { return } - values.append(val) - }) - .subscribe(subject) - var cancellationDisposable: AnyDisposable! - cancellationDisposable = AnyDisposable( - Disposables.create { - cancellablesLock.sync { - subject.onCompleted() - disposable.dispose() - cancellationCancellables[id]?.remove(cancellationDisposable) - if cancellationCancellables[id]?.isEmpty == .some(true) { - cancellationCancellables[id] = nil - } - } - }) - - cancellationCancellables[id, default: []].insert( - cancellationDisposable - ) - - return Observable.from(values) - .concat(subject) - .do( - onError: { _ in cancellationDisposable.dispose() }, - onCompleted: cancellationDisposable.dispose, - onSubscribed: { isCaching = false }, - onDispose: cancellationDisposable.dispose - ) - } - .eraseToEffect() - - return cancelInFlight ? .concatenate(.cancel(id: id), effect) : effect - } - - /// An effect that will cancel any currently in-flight effect with the given identifier. - /// - /// - Parameter id: An effect identifier. - /// - Returns: A new effect that will cancel any currently in-flight effect with the given - /// identifier. - public static func cancel(id: AnyHashable) -> Effect { - return .fireAndForget { - cancellablesLock.sync { - cancellationCancellables[id]?.forEach { $0.dispose() } - } - } - } -} - -internal var cancellationCancellables: [AnyHashable: Set] = [:] -internal let cancellablesLock = NSRecursiveLock() diff --git a/RxComposableArchitecture/Effects/Debouncing.swift b/RxComposableArchitecture/Effects/Debouncing.swift deleted file mode 100644 index e8cc2af..0000000 --- a/RxComposableArchitecture/Effects/Debouncing.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Foundation -import RxSwift - -/// Turns an effect into one that can be debounced. -/// -/// To turn an effect into a debounce-able one you must provide an identifier, which is used to -/// determine which in-flight effect should be canceled in order to start a new effect. Any -/// hashable value can be used for the identifier, such as a string, but you can add a bit of -/// protection against typos by defining a new type that conforms to `Hashable`, such as an empty -/// struct: -/// -/// case let .textChanged(text): -/// struct SearchId: Hashable {} -/// -/// return environment.search(text) -/// .map(Action.searchResponse) -/// .debounce(id: SearchId(), for: 0.5, scheduler: environment.mainQueue) -/// -/// - Parameters: -/// - id: The effect's identifier. -/// - dueTime: The duration you want to debounce for. -/// scheduler: The scheduler you want to deliver the debounced output to. -/// - Returns: An effect that publishes events only after a specified time elapses. - -extension Effect { - public func debounce( - id: AnyHashable, - for dueTime: RxTimeInterval, - scheduler: SchedulerType - ) -> Effect { - Observable.just(()) - .delay(dueTime, scheduler: scheduler) - .flatMap { self } - .eraseToEffect() - .cancellable(id: id, cancelInFlight: true) - } -} diff --git a/RxComposableArchitecture/Effects/Deferring.swift b/RxComposableArchitecture/Effects/Deferring.swift deleted file mode 100644 index b1eed02..0000000 --- a/RxComposableArchitecture/Effects/Deferring.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Deffering.swift -// RxComposableArchitecture -// -// Created by Wendy Liga on 19/05/21. -// - -import RxSwift - -extension Effect { - /// Returns an effect that will be executed after given `dueTime`. - /// - /// case let .textChanged(text): - /// struct SearchId: Hashable {} - /// - /// case let .textChanged(text): - /// struct SearchId: Hashable {} - /// - /// return environment.search(text) - /// .map(Action.searchResponse) - /// .deferred(for: .milliseconds(500), scheduler: environment.mainQueue) - /// - /// - Parameters: - /// - for: The duration you want to deferred for. - /// - scheduler: The scheduler you want to deliver the deferred output to. - /// - Returns: An effect that will be executed after `dueTime` - public func deferred( - for dueTime: RxTimeInterval, - scheduler: SchedulerType - ) -> Effect { - Observable.just(()) - .delay(dueTime, scheduler: scheduler) - .flatMap { self } - .eraseToEffect() - } -} diff --git a/RxComposableArchitecture/Effects/FireAndForget.swift b/RxComposableArchitecture/Effects/FireAndForget.swift deleted file mode 100644 index 5b8a31d..0000000 --- a/RxComposableArchitecture/Effects/FireAndForget.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation -import RxSwift - -extension ObservableType { - public static func fireAndForget(_ work: @escaping () -> Void) -> Observable { - return Observable.deferred { () -> Observable in - work() - return .empty() - } - } -} - -extension ObservableType where Element == Never { - public func fireAndForget() -> Observable { - func absurd(_: Never) -> A {} - return map(absurd) - } -} diff --git a/RxComposableArchitecture/Effects/Timer.swift b/RxComposableArchitecture/Effects/Timer.swift deleted file mode 100644 index a9edb13..0000000 --- a/RxComposableArchitecture/Effects/Timer.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// Timer.swift -// RxComposableArchitecture_RxComposableArchitecture -// -// Created by Jefferson Setiawan on 02/02/21. -// - -import RxSwift - -extension Effect where Output: RxAbstractInteger { - /// Returns an effect that repeatedly emits the current time of the given scheduler on the given - /// interval. - /// - /// While it is possible to use Foundation's `Timer.publish(every:tolerance:on:in:options:)` API - /// to create a timer in the Composable Architecture, it is not advisable. This API only allows - /// creating a timer on a run loop, which means when writing tests you will need to explicitly - /// wait for time to pass in order to see how the effect evolves in your feature. - /// - /// In the Composable Architecture we test time-based effects like this by using the - /// `TestScheduler`, which allows us to explicitly and immediately advance time forward so that - /// we can see how effects emit. However, because `Timer.publish` takes a concrete `RunLoop` as - /// its scheduler, we can't substitute in a `TestScheduler` during tests`. - /// - /// That is why we provide the `Effect.timer` effect. It allows you to create a timer that works - /// with any scheduler, not just a run loop, which means you can use a `DispatchQueue` or - /// `RunLoop` when running your live app, but use a `TestScheduler` in tests. - /// - /// To start and stop a timer in your feature you can create the timer effect from an action - /// and then use the `.cancel(id:)` effect to stop the timer: - /// - /// struct AppState { - /// var count = 0 - /// } - /// - /// enum AppAction { - /// case startButtonTapped, stopButtonTapped, timerTicked - /// } - /// - /// struct AppEnvironment { - /// var mainQueue: AnySchedulerOf - /// } - /// - /// let appReducer = Reducer { state, action, env in - /// struct TimerId: Hashable {} - /// - /// switch action { - /// case .startButtonTapped: - /// return Effect.timer(id: TimerId(), every: 1, on: env.mainQueue) - /// .map { _ in .timerTicked } - /// - /// case .stopButtonTapped: - /// return .cancel(id: TimerId()) - /// - /// case let .timerTicked: - /// state.count += 1 - /// return .none - /// } - /// - /// Then to test the timer in this feature you can use a test scheduler to advance time: - /// - /// func testTimer() { - /// let scheduler = DispatchQueue.testScheduler - /// - /// let store = TestStore( - /// initialState: .init(), - /// reducer: appReducer, - /// envirnoment: .init( - /// mainQueue: scheduler.eraseToAnyScheduler() - /// ) - /// ) - /// - /// store.assert( - /// .send(.startButtonTapped), - /// - /// .do { scheduler.advance(by: .seconds(1)) }, - /// .receive(.timerTicked) { $0.count = 1 }, - /// - /// .do { scheduler.advance(by: .seconds(5)) }, - /// .receive(.timerTicked) { $0.count = 2 }, - /// .receive(.timerTicked) { $0.count = 3 }, - /// .receive(.timerTicked) { $0.count = 4 }, - /// .receive(.timerTicked) { $0.count = 5 }, - /// .receive(.timerTicked) { $0.count = 6 }, - /// - /// .send(.stopButtonTapped) - /// ) - /// } - /// - /// - Note: This effect is only meant to be used with features built in the Composable - /// Architecture, and returned from a reducer. If you want a testable alternative to - /// Foundation's `Timer.publish` you can use the publisher `Publishers.Timer` that is included - /// in this library via the - /// [`CombineSchedulers`](https://github.com/pointfreeco/combine-schedulers) module. - /// - /// - Parameters: - /// - id: The effect's identifier. - /// - interval: The time interval on which to publish events. For example, a value of `0.5` - /// publishes an event approximately every half-second. - /// - scheduler: The scheduler on which the timer runs. - public static func timer( - id: AnyHashable, - every interval: RxTimeInterval, - on scheduler: SchedulerType - ) -> Effect { - Observable - .interval(interval, scheduler: scheduler) - .eraseToEffect() - .cancellable(id: id) - } -} diff --git a/RxComposableArchitecture/Export.swift b/RxComposableArchitecture/Export.swift deleted file mode 100644 index 6a457dd..0000000 --- a/RxComposableArchitecture/Export.swift +++ /dev/null @@ -1,2 +0,0 @@ -@_exported import CasePaths -@_exported import DiffingInterface diff --git a/RxComposableArchitecture/IdentifiedArray.swift b/RxComposableArchitecture/IdentifiedArray.swift deleted file mode 100644 index 0acebec..0000000 --- a/RxComposableArchitecture/IdentifiedArray.swift +++ /dev/null @@ -1,348 +0,0 @@ -// -// IdentifiedArray.swift -// RxComposableArchitecture_RxComposableArchitecture -// -// Created by Jefferson Setiawan on 16/07/20. -// - -import DiffingInterface -import Foundation - -/// An array of elements that can be identified by a given key path. -/// -/// A useful container of state that is intended to interface with `ForEachStore`. For example, -/// your application may model a counter in an identifiable fashion: -/// -/// struct CounterState: Differentiable { -/// let id: UUID -/// var count = 0 -/// } -/// enum CounterAction { case incr, decr } -/// let counterReducer = Reducer { ... } -/// -/// This domain can be pulled back to a larger domain with the `forEach` method: -/// -/// struct AppState { var counters = IdentifiedArray(id: \.self) } -/// enum AppAction { case counter(id: UUID, action: CounterAction) } -/// let appReducer = counterReducer.forEach( -/// state: \AppState.counters, -/// action: /AppAction.counter(id:action:), -/// environment: { $0 } -/// ) -/// -/// And then SwiftUI can work with this array of identified elements in a list view: -/// -/// struct AppView: View { -/// let store: Store -/// -/// var body: some View { -/// List { -/// ForEachStore( -/// self.store.scope(state: \.counters, action: AppAction.counter(id:action)) -/// content: CounterView.init(store:) -/// ) -/// } -/// } -/// } -public struct IdentifiedArray: MutableCollection, RandomAccessCollection - where ID: Hashable { - /// A key path to a value that identifies an element. - public let id: KeyPath - - /// A raw array of each element's identifier. - public private(set) var ids: [ID] - - /// A raw array of the underlying elements. - public var elements: [Element] { Array(self) } - - // TODO: Support multiple elements with the same identifier but different data. - private var dictionary: [ID: Element] - - /// Initializes an identified array with a sequence of elements and a key - /// path to an element's identifier. - /// - /// - Parameters: - /// - elements: A sequence of elements. - /// - id: A key path to a value that identifies an element. - public init(_ elements: S, id: KeyPath) where S: Sequence, S.Element == Element { - self.id = id - - let idsAndElements = elements.map { (id: $0[keyPath: id], element: $0) } - ids = idsAndElements.map { $0.id } - dictionary = Dictionary(idsAndElements, uniquingKeysWith: { $1 }) - } - - /// Initializes an empty identified array with a key path to an element's - /// identifier. - /// - /// - Parameter id: A key path to a value that identifies an element. - public init(id: KeyPath) { - self.init([], id: id) - } - - public var startIndex: Int { ids.startIndex } - public var endIndex: Int { ids.endIndex } - - public func index(after i: Int) -> Int { - ids.index(after: i) - } - - public func index(before i: Int) -> Int { - ids.index(before: i) - } - - public subscript(position: Int) -> Element { - // NB: `_read` crashes Xcode Preview compilation. - get { dictionary[ids[position]]! } - _modify { yield &self.dictionary[self.ids[position]]! } - } - - #if DEBUG - /// Direct access to an element by its identifier. - /// - /// - Parameter id: The identifier of element to access. Must be a valid identifier for an - /// element of the array and will _not_ insert elements that are not already in the array, or - /// remove elements when passed `nil`. Use `append` or `insert(_:at:)` to insert elements. Use - /// `remove(id:)` to remove an element by its identifier. - /// - Returns: The element. - public subscript(id id: ID) -> Element? { - get { dictionary[id] } - set { - if newValue != nil, dictionary[id] == nil { - fatalError( - """ - Can't update element with identifier \(id) because no such element exists in the array. - - If you are trying to insert an element into the array, use the "append" or "insert" \ - methods. - """ - ) - } - if newValue == nil { - fatalError( - """ - Can't update element with identifier \(id) with nil. - - If you are trying to remove an element from the array, use the "remove(id:) method." - """ - ) - } - if newValue![keyPath: self.id] != id { - fatalError( - """ - Can't update element at identifier \(id) with element having mismatched identifier \ - \(newValue![keyPath: self.id]). - - If you would like to replace the element with identifier \(id) with an element with a \ - new identifier, remove the existing element and then insert the new element, instead. - """ - ) - } - dictionary[id] = newValue - } - } - - #else - public subscript(id id: ID) -> Element? { - // NB: `_read` crashes Xcode Preview compilation. - get { dictionary[id] } - _modify { yield &self.dictionary[id] } - } - #endif - - public mutating func insert(_ newElement: Element, at i: Int) { - let id = newElement[keyPath: self.id] - dictionary[id] = newElement - ids.insert(id, at: i) - } - - public mutating func insert( - contentsOf newElements: C, at i: Int - ) where C: Collection, Element == C.Element { - for newElement in newElements.reversed() { - insert(newElement, at: i) - } - } - - /// Removes and returns the element with the specified identifier. - /// - /// - Parameter id: The identifier of the element to remove. - /// - Returns: The removed element. - @discardableResult - public mutating func remove(id: ID) -> Element { - let element = dictionary[id] - assert(element != nil, "Unexpectedly found nil while removing an identified element.") - dictionary[id] = nil - ids.removeAll(where: { $0 == id }) - return element! - } - - @discardableResult - public mutating func remove(at position: Int) -> Element { - remove(id: ids.remove(at: position)) - } - - public mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows { - var ids: [ID] = [] - for (index, id) in zip(self.ids.indices, self.ids).reversed() { - if try shouldBeRemoved(dictionary[id]!) { - self.ids.remove(at: index) - ids.append(id) - } - } - for id in ids where !self.ids.contains(id) { - self.dictionary[id] = nil - } - } - - public mutating func remove(atOffsets offsets: IndexSet) { - for offset in offsets.reversed() { - _ = remove(at: offset) - } - } - - /// Unavailable, if needed, please implement this, The implementation of `move` is in SwitUI.Collection.Array -// public mutating func move(fromOffsets source: IndexSet, toOffset destination: Int) { -// self.ids.move(fromOffsets: source, toOffset: destination) -// } - - public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows { - try ids.sort { - try areInIncreasingOrder(self.dictionary[$0]!, self.dictionary[$1]!) - } - } - - public mutating func shuffle(using generator: inout T) where T: RandomNumberGenerator { - ids.shuffle(using: &generator) - } - - public mutating func shuffle() { - var rng = SystemRandomNumberGenerator() - shuffle(using: &rng) - } - - public mutating func reverse() { - ids.reverse() - } -} - -extension IdentifiedArray: CustomDebugStringConvertible { - public var debugDescription: String { - elements.debugDescription - } -} - -extension IdentifiedArray: CustomReflectable { - public var customMirror: Mirror { - Mirror(reflecting: elements) - } -} - -extension IdentifiedArray: CustomStringConvertible { - public var description: String { - elements.description - } -} - -extension IdentifiedArray: Decodable where Element: Decodable & HashDiffable, ID == Element.IdentifierType { - public init(from decoder: Decoder) throws { - self.init(try [Element](from: decoder)) - } -} - -extension IdentifiedArray: Encodable where Element: Encodable { - public func encode(to encoder: Encoder) throws { - try elements.encode(to: encoder) - } -} - -extension IdentifiedArray: Equatable where Element: Equatable {} - -extension IdentifiedArray: Hashable where Element: Hashable {} - -extension IdentifiedArray where Element: Comparable { - public mutating func sort() { - sort(by: <) - } -} - -extension IdentifiedArray: ExpressibleByArrayLiteral where Element: HashDiffable, ID == Element.IdentifierType { - public init(arrayLiteral elements: Element...) { - self.init(elements) - } -} - -extension IdentifiedArray where Element: HashDiffable, ID == Element.IdentifierType { - public init(_ elements: S) where S: Sequence, S.Element == Element { - self.init(elements, id: \.id) - } -} - -extension IdentifiedArray: RangeReplaceableCollection - where Element: HashDiffable, ID == Element.IdentifierType { - public init() { - self.init([], id: \.id) - } - - public mutating func replaceSubrange(_ subrange: R, with newElements: C) - where C: Collection, R: RangeExpression, Element == C.Element, Index == R.Bound { - let replacingIds = ids[subrange] - let newIds = newElements.map { $0.id } - ids.replaceSubrange(subrange, with: newIds) - - for element in newElements { - dictionary[element.id] = element - } - - for id in replacingIds where !ids.contains(id) { - self.dictionary[id] = nil - } - } -} - -/// A convenience type to specify an `IdentifiedArray` by an identifiable element. -public typealias IdentifiedArrayOf = IdentifiedArray - where Element: HashDiffable - -extension IdentifiedArrayOf where Element: HashDiffable, Element.IdentifierType == ID { - public func removeDuplicates() -> Self { - /// This table will contain `diffIdentifier` as the `key` and object `type` as the value - var tableOfObjectType = [AnyHashable: Any.Type]() - - var uniqueObjects = IdentifiedArrayOf() - - forEach { currentObject in - /// Get current object identifier - let currentId = currentObject.id - - /// Get current object type from Type Erasure base object - let currentObjectType = type(of: currentObject) - - /// Check if `currentId` is already registered on `Table Bank of Identifiers Type` - /// If `yes` > Get object type with current identifier from `Table Bank of Identifiers Type` - /// If `no` > Then return `nil` - let previousObjectType = tableOfObjectType[currentId] - - /// Check whether current object type is the same with previous object type(if exist) fetched from `Table Bank of Identifiers Type` - /// If `currentId` already exist on `Table Bank of Identifiers Type` but the type is different it's not counted as _**duplicates**_ - if currentObjectType != previousObjectType { - tableOfObjectType[currentId] = currentObjectType - uniqueObjects.append(currentObject) - } - } - - return uniqueObjects - } -} - -extension IdentifiedArray { - public var isNotEmpty: Bool { - return !isEmpty - } - - @inlinable - public subscript(safe index: Index) -> Element? { - guard startIndex <= index, index < endIndex else { return nil } - return self[index] - } -} diff --git a/RxComposableArchitecture/IfLet.swift b/RxComposableArchitecture/IfLet.swift deleted file mode 100644 index ce41913..0000000 --- a/RxComposableArchitecture/IfLet.swift +++ /dev/null @@ -1,78 +0,0 @@ -import RxSwift - -extension Store { - /// Subscribes to updates when a store containing optional state goes from `nil` to non-`nil` or - /// non-`nil` to `nil`. - /// - /// This is useful for handling navigation in UIKit. The state for a screen that you want to - /// navigate to can be held as an optional value in the parent, and when that value switches - /// from `nil` to non-`nil` you want to trigger a navigation and hand the detail view a `Store` - /// whose domain has been scoped to just that feature: - /// - /// class MasterViewController: UIViewController { - /// let store: Store - /// var cancellables: Set = [] - /// ... - /// func viewDidLoad() { - /// ... - /// self.store - /// .scope(state: \.optionalDetail, action: MasterAction.detail) - /// .ifLet( - /// then: { [weak self] detailStore in - /// self?.navigationController?.pushViewController( - /// DetailViewController(store: detailStore), - /// animated: true - /// ) - /// }, - /// else: { [weak self] in - /// guard let self = self else { return } - /// self.navigationController?.popToViewController(self, animated: true) - /// } - /// ) - /// .store(in: &self.cancellables) - /// } - /// } - /// - /// - Parameters: - /// - unwrap: A function that is called with a store of non-optional state whenever the store's - /// optional state goes from `nil` to non-`nil`. - /// - else: A function that is called whenever the store's optional state goes from non-`nil` to - /// `nil`. - /// - Returns: A cancellable associated with the underlying subscription. - public func ifLet( - then unwrap: @escaping (Store) -> Void, - else: @escaping () -> Void - ) -> Disposable where State == Wrapped? { - let elseDisposable = scope( - state: { state in - state - .distinctUntilChanged { ($0 != nil) == ($1 != nil) } - } - ) - .subscribe(onNext: { if $0.state == nil { `else`() } }) - - let unwrapDisposable = scope( - state: { state in - state - .distinctUntilChanged { ($0 != nil) == ($1 != nil) } - .compactMap { $0 } - }, - action: { $0 } - ) - .subscribe(onNext: unwrap) - - return CompositeDisposable(unwrapDisposable, elseDisposable) - } - - /// An overload of `ifLet(then:else:)` for the times that you do not want to handle the `else` - /// case. - /// - /// - Parameter unwrap: A function that is called with a store of non-optional state whenever the - /// store's optional state goes from `nil` to non-`nil`. - /// - Returns: A cancellable associated with the underlying subscription. - public func ifLet( - then unwrap: @escaping (Store) -> Void - ) -> Disposable where State == Wrapped? { - ifLet(then: unwrap, else: {}) - } -} diff --git a/RxComposableArchitecture/Internal/AnyDisposable.swift b/RxComposableArchitecture/Internal/AnyDisposable.swift deleted file mode 100644 index 51075fe..0000000 --- a/RxComposableArchitecture/Internal/AnyDisposable.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// AnyDisposable.swift -// RxComposableArchitecture_RxComposableArchitecture -// -// Created by Jefferson Setiawan on 02/02/21. -// - -import Foundation -import RxSwift - -internal final class AnyDisposable: Disposable, Hashable { - internal let _dispose: () -> Void - - internal init(_ disposable: Disposable) { - _dispose = disposable.dispose - } - - internal func dispose() { - _dispose() - } - - internal static func == (lhs: AnyDisposable, rhs: AnyDisposable) -> Bool { - return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) - } - - internal func hash(into hasher: inout Hasher) { - hasher.combine(ObjectIdentifier(self)) - } -} diff --git a/RxComposableArchitecture/Internal/Deprecated.swift b/RxComposableArchitecture/Internal/Deprecated.swift deleted file mode 100644 index f1516ab..0000000 --- a/RxComposableArchitecture/Internal/Deprecated.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Deprecated.swift -// RxComposableArchitecture_RxComposableArchitecture -// -// Created by Jefferson Setiawan on 22/03/21. -// - -extension Reducer { - @available(*, deprecated, renamed: "optional()") - public var optional: Reducer { - self.optional() - } -} diff --git a/RxComposableArchitecture/Internal/Locking.swift b/RxComposableArchitecture/Internal/Locking.swift deleted file mode 100644 index 202e2a1..0000000 --- a/RxComposableArchitecture/Internal/Locking.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// Locking.swift -// RxComposableArchitecture_RxComposableArchitecture -// -// Created by Jefferson Setiawan on 02/02/21. -// - -import Foundation - -extension UnsafeMutablePointer where Pointee == os_unfair_lock_s { - @inlinable @discardableResult - internal func sync(_ work: () -> R) -> R { - os_unfair_lock_lock(self) - defer { os_unfair_lock_unlock(self) } - return work() - } -} - -extension NSRecursiveLock { - @inlinable @discardableResult - internal func sync(work: () -> R) -> R { - lock() - defer { self.unlock() } - return work() - } -} diff --git a/RxComposableArchitecture/OptionalPaths.swift b/RxComposableArchitecture/OptionalPaths.swift deleted file mode 100644 index 1343b4e..0000000 --- a/RxComposableArchitecture/OptionalPaths.swift +++ /dev/null @@ -1,275 +0,0 @@ -// - -import Foundation - -public protocol WritablePath { - associatedtype Root - associatedtype Value - func extract(from root: Root) -> Value? - func set(into root: inout Root, _ value: Value) -} - -extension WritableKeyPath: WritablePath { - public func extract(from root: Root) -> Value? { - root[keyPath: self] - } - - public func set(into root: inout Root, _ value: Value) { - root[keyPath: self] = value - } -} - -extension CasePath: WritablePath { - public func set(into root: inout Root, _ value: Value) { - root = embed(value) - } -} - -extension OptionalPath: WritablePath {} - -public struct OptionalPath { - private let _extract: (Root) -> Value? - private let _set: (inout Root, Value) -> Void - - public init( - extract: @escaping (Root) -> Value?, - set: @escaping (inout Root, Value) -> Void - ) { - _extract = extract - _set = set - } - - public func extract(from root: Root) -> Value? { - _extract(root) - } - - public func set(into root: inout Root, _ value: Value) { - _set(&root, value) - } - - public init( - _ keyPath: WritableKeyPath - ) { - self.init( - extract: { $0[keyPath: keyPath] }, - set: { $0[keyPath: keyPath] = $1 } - ) - } - - public init( - _ casePath: CasePath - ) { - self.init( - extract: casePath.extract(from:), - set: { $0 = casePath.embed($1) } - ) - } - - public func appending( - path: OptionalPath - ) -> OptionalPath { - .init( - extract: { self.extract(from: $0).flatMap(path.extract(from:)) }, - set: { root, appendedValue in - guard var value = self.extract(from: root) else { return } - path.set(into: &value, appendedValue) - self.set(into: &root, value) - } - ) - } - - public func appending( - path: CasePath - ) -> OptionalPath { - appending(path: .init(path)) - } - - public func appending( - path: WritableKeyPath - ) -> OptionalPath { - .init( - extract: { self.extract(from: $0).map { $0[keyPath: path] } }, - set: { root, appendedValue in - guard var value = self.extract(from: root) else { return } - value[keyPath: path] = appendedValue - self.set(into: &root, value) - } - ) - } - - // TODO: Is it safe to keep this overload? - public func appending( - path: WritableKeyPath - ) -> OptionalPath { - appending(path: .init(path)) - } -} - -extension CasePath { - public func appending( - path: OptionalPath - ) -> OptionalPath { - OptionalPath(self).appending(path: path) - } - - public func appending( - path: WritableKeyPath - ) -> OptionalPath { - OptionalPath(self).appending(path: path) - } - - // TODO: Is it safe to keep this overload? - public func appending( - path: WritableKeyPath - ) -> OptionalPath { - OptionalPath(self).appending(path: path) - } -} - -extension WritableKeyPath { - public func appending( - path: OptionalPath - ) -> OptionalPath { - OptionalPath( - extract: { path.extract(from: $0[keyPath: self]) }, - set: { root, appendedValue in path.set(into: &root[keyPath: self], appendedValue) } - ) - } - - public func appending( - path: CasePath - ) -> OptionalPath { - appending(path: .init(path)) - } -} - -extension OptionalPath where Root == Value { - public static var `self`: OptionalPath { - .init(.self) - } -} - -extension OptionalPath where Root == Value? { - public static var some: OptionalPath { - .init(/Optional.some) - } -} - -// MARK: - Operator - -precedencegroup OptionalPathCompositionPrecedence { - associativity: right -} - -infix operator ..: OptionalPathCompositionPrecedence - -extension OptionalPath { - /// Returns a new Optional path created by appending the given optional path to this one. - /// - /// The operator version of `OptionalPath.appending(path:)`. Use this method to extend this optional path to the value type of another path. - /// - /// - Parameters: - /// - lhs: A optional path from a root to a value. - /// - rhs: A optional path from the first optional path's value to some other appended value. - /// - Returns: A new optional path from the first optional path's root to the second optional path's value. - public static func .. ( - lhs: OptionalPath, - rhs: OptionalPath - ) -> OptionalPath { - return lhs.appending(path: rhs) - } - - /// Returns a new Optional path created by appending the given writeable path to this one. - /// - /// The operator version of `OptionalPath.appending(path:)`. Use this method to extend this optional path to the value type of another path. - /// - /// - Parameters: - /// - lhs: A optional path from a root to a value. - /// - rhs: A writeable path from the first optional path's value to some other appended value. - /// - Returns: A new optional path from the first optional path's root to the second writeable path's value. - public static func .. ( - lhs: OptionalPath, - rhs: WritableKeyPath - ) -> OptionalPath { - return lhs.appending(path: rhs) - } - - /// Returns a new Optional path created by appending the given case path to this one. - /// - /// The operator version of `OptionalPath.appending(path:)`. Use this method to extend this optional path to the value type of another path. - /// - /// - Parameters: - /// - lhs: A optional path from a root to a value. - /// - rhs: A case path from the first optional path's value to some other appended value. - /// - Returns: A new optional path from the first optional path's root to the second case path's value. - public static func .. ( - lhs: OptionalPath, - rhs: CasePath - ) -> OptionalPath { - return lhs.appending(path: rhs) - } -} - -extension WritableKeyPath { - /// Returns a new Optional path created by appending the given optional path to this one. - /// - /// The operator version of `WritableKeyPath.appending(path:)`. Use this method to extend this writeable path to the value type of another optional path. - /// - /// - Parameters: - /// - lhs: A writeable path from a root to a value. - /// - rhs: A optional path from the first optional path's value to some other appended value. - /// - Returns: A new optional path from the first writeable path's root to the second optional path's value. - public static func .. ( - lhs: WritableKeyPath, - rhs: OptionalPath - ) -> OptionalPath { - return lhs.appending(path: rhs) - } - - /// Returns a new Optional path created by appending the given case path to this one. - /// - /// The operator version of `WritableKeyPath.appending(path:)`. Use this method to extend this writeable path to the value type of another case path. - /// - /// - Parameters: - /// - lhs: A writeable path from a root to a value. - /// - rhs: A case path from the first optional path's value to some other appended value. - /// - Returns: A new optional path from the first writeable path's root to the second case path's value. - public static func .. ( - lhs: WritableKeyPath, - rhs: CasePath - ) -> OptionalPath { - return lhs.appending(path: rhs) - } -} - -extension CasePath { - /// Returns a new Optional path created by appending the given optional path to this one. - /// - /// The operator version of `CasePath.appending(path:)`. Use this method to extend this case path to the value type of another optional path. - /// - /// - Parameters: - /// - lhs: A case path from a root to a value. - /// - rhs: A optional path from the first optional path's value to some other appended value. - /// - Returns: A new optional path from the first case path's root to the second optional path's value. - public static func .. ( - lhs: CasePath, - rhs: OptionalPath - ) -> OptionalPath { - return lhs.appending(path: rhs) - } - - /// Returns a new Optional path created by appending the given optional path to this one. - /// - /// The operator version of `CasePath.appending(path:)`. Use this method to extend this case path to the value type of another writeable path. - /// - /// - Parameters: - /// - lhs: A case path from a root to a value. - /// - rhs: A writeable path from the first optional path's value to some other appended value. - /// - Returns: A new optional path from the first case path's root to the second writeable path's value. - public static func .. ( - lhs: CasePath, - rhs: WritableKeyPath - ) -> OptionalPath { - return lhs.appending(path: rhs) - } -} diff --git a/RxComposableArchitecture/PropertyWrapper/SingleSelection.swift b/RxComposableArchitecture/PropertyWrapper/SingleSelection.swift deleted file mode 100644 index 5d57ae3..0000000 --- a/RxComposableArchitecture/PropertyWrapper/SingleSelection.swift +++ /dev/null @@ -1,255 +0,0 @@ -// -// SingleSelection.swift -// RxComposableArchitecture -// -// Created by Wendy Liga on 25/05/21. -// - -/** - PropertyWrapper to help manage single selection on `IdentifiedArray`. - - if you have state like this - - ```swift - struct Item: HashDiffable, Equatable { - let id: Int - var isSelected: Bool - } - - let itemReducer = Reducer... { - switch action { - case .tap: - state.isSelected = true - } - } - - struct State { - var items: IdentifiedArrayOf - } - - let stateReducer = Reducer... { - switch action { - case .item(id, .tap): - state.items = state.items.map { item in - var newItem = item - newItem.isSelected = newItem.id == id - - return newItem - } - } - } - ``` - - as you can see you need to handle the action on parent too, loop each item to select only on selected `id` - here, `SingleSelection` comes to rescue. - - ```swift - struct Item: HashDiffable, Equatable { - let id: Int - var isSelected: Bool - } - - let itemReducer = Reducer... { - switch action { - case .tap: - state.isSelected = true - } - } - - struct State { - @SingleSelection([], selection: \.isSelected) - var items: IdentifiedArrayOf - } - - let stateReducer = Reducer... { - switch action { - case .item(id, .tap): - # you don't need to handle it manually on parent side # - } - } - ``` - - ## how to use - ``` - @SingleSelection([], selection: \.isSelected) - ``` - by using `SingleSelection`, you don't need to handle the single selection manually, - just set selection from child side, and `SingleSelection` will automatically unselect previous one and select the new one. - - your array item also can conform to `Selectable`, so you don't need to manually give the keypath - - ```swift - struct Item: HashDiffable, Equatable, Selectable { - let id: Int - var isSelected: Bool - } - - struct State { - @SingleSelection - var items: IdentifiedArrayOf - } - ``` - - - Complexity: O(n) - everytime array is mutated, SingleSelection need to check if only one item is selected every time mutation happend. - */ -@propertyWrapper -public struct SingleSelection where Element: HashDiffable { - private let _getSelection: (Element) -> Bool - private let _setSelection: (inout Element, Bool) -> Void - - private var _currentSelectedId: Element.IdentifierType? - private var _wrappedValue: IdentifiedArrayOf - - public var wrappedValue: IdentifiedArrayOf { - get { - _wrappedValue - } - set { - set(newValue) - } - } - - /** - SingleSelection - - - Parameters: - - wrappedValue: initial value - - extract: map given `Element` to `isSelected` Bool - - set: closure to set new Bool value to `Element` `isSelected` - */ - public init( - _ wrappedValue: IdentifiedArrayOf = [], - extract: @escaping (Element) -> Bool, - set: @escaping (inout Element, Bool) -> Void - ) { - _wrappedValue = wrappedValue - _getSelection = extract - _setSelection = set - - // initial setup - self.set(wrappedValue) - } - - /** - SingleSelection - - - Parameters: - - wrappedValue: initial value - - selection: writeable keypath to `isSelected` value. - */ - public init( - _ wrappedValue: IdentifiedArrayOf = [], - selection path: WritableKeyPath - ) { - self.init( - wrappedValue, - extract: { $0[keyPath: path] }, - set: { $0[keyPath: path] = $1 } - ) - } - - /** - logic that will run before setting new value to wrappedValue - - here we will try to - - eleminate multiple selected item (only 1 will last) - - deselect `_currentSelectedId` if neccessary - - will keep track of current selected id on `_currentSelectedId` - */ - private mutating func set(_ values: IdentifiedArrayOf) { - /// filter all element where `isSelected` is true, and map it to its `Element.IdentifierType` or `id` - let selectedIds = values - .compactMap { value -> Element.IdentifierType? in - guard _getSelection(value) else { return nil } - return value.id - } - - /// if current selected id is nil, we will search for candidate - guard let currentSelectedId = _currentSelectedId else { - var _values = values - - // if new given array has more than 1 item selected, will select first index and delesect else. - if selectedIds.count > 1 { - // loop every other id that is selected other than intented - (1 ..< selectedIds.endIndex).forEach { offset in - let id = selectedIds[offset] - // if not nil - _values[id: id].map { value in - var value = value - _setSelection(&value, false) // unselect - _values[id: id] = value - } - } - } - - // save - _currentSelectedId = selectedIds.first - _wrappedValue = _values - - return - } - - /// if current selected id count is 1, then single selection rules is fulfilled, we don't need to continue. - guard selectedIds.count > 1 else { - // save new value - _wrappedValue = values - return - } - - // delesect current selected - var withoutCurrentSelection = values - withoutCurrentSelection[id: currentSelectedId].map { value in - var value = value - _setSelection(&value, false) - withoutCurrentSelection[id: currentSelectedId] = value - } - - // clear current selection - _currentSelectedId = nil - - // recuversivly search selected id and remove duplicate if necessary - set(withoutCurrentSelection) - } -} - -public protocol Selectable { - var isSelected: Bool { get set } -} - -extension Selectable { - public var path: WritableKeyPath { - \.isSelected - } -} - -extension SingleSelection where Element: Selectable { - /** - SingleSelection - - - Parameters: - - wrappedValue: initial value - */ - public init(_ wrappedValue: IdentifiedArrayOf = []) { - self.init( - wrappedValue, - extract: \.isSelected, - set: { $0.isSelected = $1 } - ) - } -} - -extension SingleSelection: Equatable where Element: Equatable { - // manually conform equatable - // because there're closure - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs._currentSelectedId == rhs._currentSelectedId && lhs._wrappedValue == rhs._wrappedValue - } -} - -extension SingleSelection: Hashable where Element: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(_currentSelectedId) - hasher.combine(_wrappedValue) - } -} diff --git a/RxComposableArchitecture/PropertyWrapper/UniqueElements.swift b/RxComposableArchitecture/PropertyWrapper/UniqueElements.swift deleted file mode 100644 index e789985..0000000 --- a/RxComposableArchitecture/PropertyWrapper/UniqueElements.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// UniqueElements.swift -// RxComposableArchitecture_RxComposableArchitecture -// -// Created by Kensen on 07/01/21. -// - -import DiffingInterface - -/** - Property wrapper to reduce boiler plate code to remove duplicates from a collection of HashDiffable. - - ``` - struct Element: Equatable, HashDiffable {} - - struct ParentState: Equatable { - @UniqueElements var arrayState: [Element] - @UniqueElements var identifiedArrayState: IdentifiedArrayOf - } - ``` - - Everytime we set arrayState or identifiedArrayState, it will automatically remove all duplicates, making them having only unique elements. - Using this property wrapper will help reduce data disrepancy between the source of data and components that use them - */ - -@propertyWrapper -public struct UniqueElements: Equatable where State: Collection & Equatable, State.Element: HashDiffable { - public var wrappedValue: State { - didSet { - wrappedValue = Self.getUniqueState(wrappedValue) - } - } - - public init(wrappedValue: State) { - self.wrappedValue = Self.getUniqueState(wrappedValue) - } - - private static func getUniqueState(_ state: State) -> State { - if let array = state as? [State.Element] { - return (array.removeDuplicates() as? State) ?? state - } else if let identifiedArray = state as? IdentifiedArrayOf { - return (identifiedArray.removeDuplicates() as? State) ?? state - } else { - assertionFailure("\(type(of: state)) is not supported yet") - return state - } - } -} - -extension UniqueElements: Decodable where State: Decodable { - public init(value: State) { - wrappedValue = Self.getUniqueState(value) - } - - public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - let rawValue = try container.decode(State.self) - self.init(value: rawValue) - } -} diff --git a/RxComposableArchitecture/Reducer.swift b/RxComposableArchitecture/Reducer.swift deleted file mode 100644 index 8455cb4..0000000 --- a/RxComposableArchitecture/Reducer.swift +++ /dev/null @@ -1,625 +0,0 @@ -import CasePaths -import Darwin -import DiffingInterface -import RxSwift - -public struct Reducer { - private let reducer: (inout State, Action, Environment) -> Effect - - public init(_ reducer: @escaping (inout State, Action, Environment) -> Effect) { - self.reducer = reducer - } - - public static var empty: Reducer { - Self { _, _, _ in .none } - } - - public static func combine(_ reducers: Reducer...) -> Reducer { - .combine(reducers) - } - - public static func combine(_ reducers: [Reducer]) -> Reducer { - Self { value, action, environment in - .merge(reducers.map { $0.reducer(&value, action, environment) }) - } - } - - /// Transforms a reducer that works on local state, action and environment into one that works on - /// global state, action and environment. It accomplishes this by providing 3 transformations to - /// the method: - /// - /// * A writable key path that can get/set a piece of local state from the global state. - /// * A case path that can extract/embed a local action into a global action. - /// * A function that can transform the global environment into a local environment. - /// - /// This operation is important for breaking down large reducers into small ones. When used with - /// the `combine` operator you can define many reducers that work on small pieces of domain, and - /// then _pull them back_ and _combine_ them into one big reducer that works on a large domain. - /// - /// // Global domain that holds a local domain: - /// struct AppState { var settings: SettingsState, /* rest of state */ } - /// struct AppAction { case settings(SettingsAction), /* other actions */ } - /// struct AppEnvironment { var settings: SettingsEnvironment, /* rest of dependencies */ } - /// - /// // A reducer that works on the local domain: - /// let settingsReducer = Reducer { ... } - /// - /// // Pullback the settings reducer so that it works on all of the app domain: - /// let appReducer: Reducer = .combine( - /// settingsReducer.pullback( - /// state: \.settings, - /// action: /AppAction.settings, - /// environment: { $0.settings } - /// ), - /// - /// /* other reducers */ - /// ) - /// - /// - Parameters: - /// - toLocalState: A writable path (`WritableKeyPath`, `CasePath`, or `OptionalPath`) that can - /// get/set `State` inside `GlobalState`. - /// - toLocalAction: A writable path (`WritableKeyPath`, `CasePath`, or `OptionalPath`) that can - /// get/set `Action` inside `GlobalAction`. - /// - toLocalEnvironment: A function that transforms `GlobalEnvironment` into `Environment`. - /// - Returns: A reducer that works on `GlobalState`, `GlobalAction`, `GlobalEnvironment`. - public func pullback( - state toLocalState: WritableKeyPath, - action toLocalAction: CasePath, - environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment - ) -> Reducer { - .init { globalState, globalAction, globalEnvironment in - guard let localAction = toLocalAction.extract(from: globalAction) else { return .none } - return self.reducer( - &globalState[keyPath: toLocalState], - localAction, - toLocalEnvironment(globalEnvironment) - ) - .map(toLocalAction.embed) - } - } - - public func pullback( - state toLocalState: StatePath, - action toLocalAction: ActionPath, - environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment - ) -> Reducer - where - StatePath: WritablePath, StatePath.Root == GlobalState, StatePath.Value == State, - ActionPath: WritablePath, ActionPath.Root == GlobalAction, ActionPath.Value == Action { - return .init { globalState, globalAction, globalEnvironment in - guard - var localState = toLocalState.extract(from: globalState), - let localAction = toLocalAction.extract(from: globalAction) - else { return .none } - let effect = - self - .reducer(&localState, localAction, toLocalEnvironment(globalEnvironment)) - .map { localAction -> GlobalAction in - var globalAction = globalAction - toLocalAction.set(into: &globalAction, localAction) - return globalAction - } - toLocalState.set(into: &globalState, localState) - return effect - } - } - - /// Transforms a reducer that works on non-optional state into one that works on optional state by - /// only running the non-optional reducer when state is non-nil. - /// - /// Often used in tandem with `pullback` to transform a reducer on a non-optional child domain - /// into a reducer that can be combined with a reducer on a parent domain that contains some - /// optional child domain: - /// - /// // Global domain that holds an optional local domain: - /// struct AppState { var modal: ModalState? } - /// enum AppAction { case modal(ModalAction) } - /// struct AppEnvironment { var mainQueue: AnySchedulerOf } - /// - /// // A reducer that works on the non-optional local domain: - /// let modalReducer = Reducer.combine( - /// modalReducer.optional().pullback( - /// state: \.modal, - /// action: /AppAction.modal, - /// environment: { ModalEnvironment(mainQueue: $0.mainQueue) } - /// ), - /// Reducer { state, action, environment in - /// ... - /// } - /// ) - /// - /// Take care when combining optional reducers into parent domains. An optional reducer cannot - /// process actions in its domain when its state is `nil`. If a child action is sent to an - /// optional reducer when child state is `nil`, it is generally considered a logic error. There - /// are a few ways in which these errors can sneak into a code base: - /// - /// * A parent reducer sets child state to `nil` when processing a child action and runs - /// _before_ the child reducer: - /// - /// let parentReducer = Reducer.combine( - /// // When combining reducers, the parent reducer runs first - /// Reducer { state, action, environment in - /// switch action { - /// case .child(.didDisappear): - /// // And `nil`s out child state when processing a child action - /// state.child = nil - /// return .none - /// ... - /// } - /// }, - /// // Before the child reducer runs - /// childReducer.optional().pullback(...) - /// ) - /// - /// let childReducer = Reducer< - /// ChildState, ChildAction, ChildEnvironment - /// > { state, action environment in - /// case .didDisappear: - /// // This action is never received here because child state is `nil` in the parent - /// ... - /// } - /// - /// To ensure that a child reducer can process any action that a parent may use to `nil` out - /// its state, combine it _before_ the parent: - /// - /// let parentReducer = Reducer.combine( - /// // The child runs first - /// childReducer.optional().pullback(...), - /// // The parent runs after - /// Reducer { state, action, environment in - /// ... - /// } - /// ) - /// - /// * A child effect feeds a child action back into the store when child state is `nil`: - /// - /// let childReducer = Reducer< - /// ChildState, ChildAction, ChildEnvironment - /// > { state, action environment in - /// switch action { - /// case .onAppear: - /// // An effect may want to feed its result back to the child domain in an action - /// return environment.apiClient - /// .request() - /// .map(ChildAction.response) - /// - /// case let .response(response): - /// // But the child cannot process this action if its state is `nil` in the parent - /// ... - /// } - /// } - /// - /// It is perfectly reasonable to ignore the result of an effect when child state is `nil`, - /// for example one-off effects that you don't want to cancel. However, many long-living - /// effects _should_ be explicitly canceled when tearing down a child domain: - /// - /// let childReducer = Reducer< - /// ChildState, ChildAction, ChildEnvironment - /// > { state, action environment in - /// struct MotionId: Hashable {} - /// - /// switch action { - /// case .onAppear: - /// // Mark long-living effects that shouldn't outlive their domain cancellable - /// return environment.motionClient - /// .start() - /// .map(ChildAction.motion) - /// .cancellable(id: MotionId()) - /// - /// case .onDisappear: - /// // And explicitly cancel them when the domain is torn down - /// return .cancel(id: MotionId()) - /// ... - /// } - /// } - /// - /// * A view store sends a child action when child state is `nil`: - /// - /// WithViewStore(self.parentStore) { parentViewStore in - /// // If child state is `nil`, it cannot process this action. - /// Button("Child Action") { parentViewStore.send(.child(.action)) } - /// ... - /// } - /// - /// Use `Store.scope` with`IfLetStore` or `Store.ifLet` to ensure that views can only send - /// child actions when the child domain is non-`nil`. - /// - /// IfLetStore( - /// self.parentStore.scope(state: { $0.child }, action: { .child($0) } - /// ) { childStore in - /// // This destination only appears when child state is non-`nil` - /// WithViewStore(childStore) { childViewStore in - /// // So this action can only be sent when child state is non-`nil` - /// Button("Child Action") { childViewStore.send(.action) } - /// } - /// ... - /// } - /// - /// - See also: `IfLetStore`, a SwiftUI helper for transforming a store on optional state into a - /// store on non-optional state. - /// - See also: `Store.ifLet`, a UIKit helper for doing imperative work with a store on optional - /// state. - /// - /// - Parameter breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer - /// but state is `nil`. This is generally considered a logic error, as a child reducer cannot - /// process a child action for unavailable child state. - /// - Returns: A reducer that works on optional state. - public func optional( - breakpointOnNil: Bool = true, - _ file: StaticString = #file, - _ line: UInt = #line - ) -> Reducer< - State?, Action, Environment - > { - .init { state, action, environment in - guard state != nil else { - #if DEBUG - if breakpointOnNil { - fputs( - """ - --- - Warning: Reducer.optional@\(file):\(line) - - "\(debugCaseOutput(action))" was received by an optional reducer when its state was \ - "nil". This is generally considered an application logic error, and can happen for a \ - few reasons: - - * The optional reducer was combined with or run from another reducer that set \ - "\(State.self)" to "nil" before the optional reducer ran. Combine or run optional \ - reducers before reducers that can set their state to "nil". This ensures that \ - optional reducers can handle their actions while their state is still non-"nil". - - * An in-flight effect emitted this action while state was "nil". While it may be \ - perfectly reasonable to ignore this action, you may want to cancel the associated \ - effect before state is set to "nil", especially if it is a long-living effect. - - * This action was sent to the store while state was "nil". Make sure that actions \ - for this reducer can only be sent to a view store when state is non-"nil". In \ - SwiftUI applications, use "IfLetStore". - --- - - """, - stderr - ) - raise(SIGTRAP) - } - #endif - return .none - } - return self.reducer(&state!, action, environment) - } - } - - /// A version of `pullback` that transforms a reducer that works on an element into one that works - /// on a collection of elements. - /// - /// // Global domain that holds a collection of local domains: - /// struct AppState { var todos: [Todo] } - /// enum AppAction { case todo(index: Int, action: TodoAction) } - /// struct AppEnvironment { var mainQueue: AnySchedulerOf } - /// - /// // A reducer that works on a local domain: - /// let todoReducer = Reducer { ... } - /// - /// // Pullback the local todo reducer so that it works on all of the app domain: - /// let appReducer = Reducer.combine( - /// todoReducer.forEach( - /// state: \.todos, - /// action: /AppAction.todo(index:action:), - /// environment: { _ in TodoEnvironment() } - /// ), - /// Reducer { state, action, environment in - /// ... - /// } - /// ) - /// - /// Take care when combining `forEach` reducers into parent domains, as order matters. Always - /// combine `forEach` reducers _before_ parent reducers that can modify the collection. - /// - /// - Parameters: - /// - toLocalState: A key path that can get/set an array of `State` elements inside. - /// `GlobalState`. - /// - toLocalAction: A case path that can extract/embed `(Int, Action)` from `GlobalAction`. - /// - toLocalEnvironment: A function that transforms `GlobalEnvironment` into `Environment`. - /// - breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer but the - /// index is out of bounds. This is generally considered a logic error, as a child reducer - /// cannot process a child action for unavailable child state. - /// - Returns: A reducer that works on `GlobalState`, `GlobalAction`, `GlobalEnvironment`. - public func forEach( - state toLocalState: WritableKeyPath, - action toLocalAction: CasePath, - environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment, - breakpointOnNil: Bool = true, - _ file: StaticString = #file, - _ line: UInt = #line - ) -> Reducer { - .init { globalState, globalAction, globalEnvironment in - guard let (index, localAction) = toLocalAction.extract(from: globalAction) else { - return .none - } - if index >= globalState[keyPath: toLocalState].endIndex { - #if DEBUG - if breakpointOnNil { - fputs( - """ - --- - Warning: Reducer.forEach@\(file):\(line) - - "\(debugCaseOutput(localAction))" was received by a "forEach" reducer at index \ - \(index) when its state contained no element at this index. This is generally \ - considered an application logic error, and can happen for a few reasons: - - * This "forEach" reducer was combined with or run from another reducer that removed \ - the element at this index when it handled this action. To fix this make sure that \ - this "forEach" reducer is run before any other reducers that can move or remove \ - elements from state. This ensures that "forEach" reducers can handle their actions \ - for the element at the intended index. - - * An in-flight effect emitted this action while state contained no element at this \ - index. While it may be perfectly reasonable to ignore this action, you may want to \ - cancel the associated effect when moving or removing an element. If your "forEach" \ - reducer returns any long-living effects, you should use the identifier-based \ - "forEach" instead. - - * This action was sent to the store while its state contained no element at this \ - index. To fix this make sure that actions for this reducer can only be sent to a \ - view store when its state contains an element at this index. In SwiftUI \ - applications, use "ForEachStore". - --- - - """, - stderr - ) - raise(SIGTRAP) - } - #endif - return .none - } - return self.reducer( - &globalState[keyPath: toLocalState][index], - localAction, - toLocalEnvironment(globalEnvironment) - ) - .map { toLocalAction.embed((index, $0)) } - } - } - - public func forEach( - state toLocalState: WritableKeyPath, - action toLocalAction: CasePath, - environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment, - breakpointOnNil: Bool = true, - _ file: StaticString = #file, - _ line: UInt = #line - ) -> Reducer { - .init { globalState, globalAction, globalEnvironment in - guard let (key, localAction) = toLocalAction.extract(from: globalAction) else { return .none } - if globalState[keyPath: toLocalState][key] == nil { - #if DEBUG - if breakpointOnNil { - fputs( - """ - --- - Warning: Reducer.forEach@\(file):\(line) - - "\(debugCaseOutput(localAction))" was received by a "forEach" reducer at key \(key) \ - when its state contained no element at this key. This is generally considered an \ - application logic error, and can happen for a few reasons: - - * This "forEach" reducer was combined with or run from another reducer that removed \ - the element at this key when it handled this action. To fix this make sure that this \ - "forEach" reducer is run before any other reducers that can move or remove elements \ - from state. This ensures that "forEach" reducers can handle their actions for the \ - element at the intended key. - - * An in-flight effect emitted this action while state contained no element at this \ - key. It may be perfectly reasonable to ignore this action, but you also may want to \ - cancel the effect it originated from when removing a value from the dictionary, \ - especially if it is a long-living effect. - - * This action was sent to the store while its state contained no element at this \ - key. To fix this make sure that actions for this reducer can only be sent to a view \ - store when its state contains an element at this key. - --- - - """, - stderr - ) - raise(SIGTRAP) - } - #endif - return .none - } - return self.reducer( - &globalState[keyPath: toLocalState][key]!, - localAction, - toLocalEnvironment(globalEnvironment) - ) - .map { toLocalAction.embed((key, $0)) } - } - } - - /// A version of `pullback` that transforms a reducer that works on an element into one that works - /// on an identified array of elements. - /// - /// // Global domain that holds a collection of local domains: - /// struct AppState { var todos: IdentifiedArrayOf } - /// enum AppAction { case todo(id: Todo.ID, action: TodoAction) } - /// struct AppEnvironment { var mainQueue: AnySchedulerOf } - /// - /// // A reducer that works on a local domain: - /// let todoReducer = Reducer { ... } - /// - /// // Pullback the local todo reducer so that it works on all of the app domain: - /// let appReducer = Reducer.combine( - /// todoReducer.forEach( - /// state: \.todos, - /// action: /AppAction.todo(id:action:), - /// environment: { _ in TodoEnvironment() } - /// ), - /// Reducer { state, action, environment in - /// ... - /// } - /// ) - /// - /// Take care when combining `forEach` reducers into parent domains, as order matters. Always - /// combine `forEach` reducers _before_ parent reducers that can modify the collection. - /// - /// - Parameters: - /// - toLocalState: A key path that can get/set a collection of `State` elements inside - /// `GlobalState`. - /// - toLocalAction: A case path that can extract/embed `(Collection.Index, Action)` from - /// `GlobalAction`. - /// - toLocalEnvironment: A function that transforms `GlobalEnvironment` into `Environment`. - /// - breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer but the - /// identified array does not contain an element with the action's identifier. This is - /// generally considered a logic error, as a child reducer cannot process a child action - /// for unavailable child state. - /// - Returns: A reducer that works on `GlobalState`, `GlobalAction`, `GlobalEnvironment`. - public func forEach( - state toLocalState: WritableKeyPath>, - action toLocalAction: CasePath, - environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment, - breakpointOnNil: Bool = true, - _ file: StaticString = #file, - _ line: UInt = #line - ) -> Reducer { - .init { globalState, globalAction, globalEnvironment in - guard let (id, localAction) = toLocalAction.extract(from: globalAction) else { return .none } - if globalState[keyPath: toLocalState][id: id] == nil { - #if DEBUG - if breakpointOnNil { - fputs( - """ - --- - Warning: Reducer.forEach@\(file):\(line) - - "\(debugCaseOutput(localAction))" was received by a "forEach" reducer at id \(id) \ - when its state contained no element at this id. This is generally considered an \ - application logic error, and can happen for a few reasons: - - * This "forEach" reducer was combined with or run from another reducer that removed \ - the element at this id when it handled this action. To fix this make sure that this \ - "forEach" reducer is run before any other reducers that can move or remove elements \ - from state. This ensures that "forEach" reducers can handle their actions for the \ - element at the intended id. - - * An in-flight effect emitted this action while state contained no element at this \ - id. It may be perfectly reasonable to ignore this action, but you also may want to \ - cancel the effect it originated from when removing an element from the identified \ - array, especially if it is a long-living effect. - - * This action was sent to the store while its state contained no element at this id. \ - To fix this make sure that actions for this reducer can only be sent to a view store \ - when its state contains an element at this id. In SwiftUI applications, use \ - "ForEachStore". - --- - - """, - stderr - ) - raise(SIGTRAP) - } - #endif - return .none - } - return - self - .reducer( - &globalState[keyPath: toLocalState][id: id]!, - localAction, - toLocalEnvironment(globalEnvironment) - ) - .map { toLocalAction.embed((id, $0)) } - } - } - - public func callAsFunction( - _ state: inout State, - _ action: Action, - _ environment: Environment - ) -> Effect { - func environmentToUse() -> Environment { - #if DEBUG - if let bootstrappedEnvironment = Bootstrap.get(environment: type(of: environment)) { - return bootstrappedEnvironment - } else { - return environment - } - #else - return environment - #endif - } - - return reducer(&state, action, environmentToUse()) - } - - public func combined(with other: Reducer) -> Reducer { - .combine(self, other) - } - - /// Runs the reducer. - /// - /// - Parameters: - /// - state: Mutable state. - /// - action: An action. - /// - environment: An environment. - /// - debug: any additional action when executing reducer - /// - Returns: An effect that can emit zero or more actions. - public func run( - _ state: inout State, - _ action: Action, - _ environment: Environment, - _ debug: (State) -> Void = { _ in } - ) -> Effect { - let reducer = self.reducer(&state, action, environment) - debug(state) - - return reducer - } -} - -extension Reducer where Environment == Void { - public func callAsFunction( - _ state: inout State, - _ action: Action - ) -> Effect { - callAsFunction(&state, action, ()) - } -} - -extension Reducer where State: HashDiffable { - /** - Pullback reducer for reducer which store is part of `Differentiable` array. - */ - public func forEach( - state toLocalState: WritableKeyPath, - action toLocalAction: CasePath, - environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment - ) -> Reducer - where Identifier == State.IdentifierType { - .init { globalState, globalAction, globalEnvironment in - guard let (identifier, localAction) = toLocalAction.extract(from: globalAction) else { - return .none - } - - // search index of identifier - guard let index = globalState[keyPath: toLocalState].firstIndex(where: { $0.id == identifier }) - else { - assertionFailure("\(identifier) is not exist on Global State") - return .none - } - - // return redux - return self.reducer( - &globalState[keyPath: toLocalState][index], - localAction, - toLocalEnvironment(globalEnvironment) - ) - .map { toLocalAction.embed((identifier, $0)) } - } - } -} diff --git a/RxComposableArchitecture/RxComposableArchitecture.docc/RxComposableArchitecture.md b/RxComposableArchitecture/RxComposableArchitecture.docc/RxComposableArchitecture.md deleted file mode 100755 index 78e511e..0000000 --- a/RxComposableArchitecture/RxComposableArchitecture.docc/RxComposableArchitecture.md +++ /dev/null @@ -1,13 +0,0 @@ -# ``RxComposableArchitecture`` - -Summary - -## Overview - -Text - -## Topics - -### Group - -- ``Symbol`` \ No newline at end of file diff --git a/RxComposableArchitecture/RxComposableArchitecture.h b/RxComposableArchitecture/RxComposableArchitecture.h deleted file mode 100644 index cf131d9..0000000 --- a/RxComposableArchitecture/RxComposableArchitecture.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// RxComposableArchitecture.h -// RxComposableArchitecture -// -// Created by Andrey Yoshua Manik on 21/02/22. -// - -#import - -//! Project version number for RxComposableArchitecture. -FOUNDATION_EXPORT double RxComposableArchitectureVersionNumber; - -//! Project version string for RxComposableArchitecture. -FOUNDATION_EXPORT const unsigned char RxComposableArchitectureVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/RxComposableArchitecture/Stateless.swift b/RxComposableArchitecture/Stateless.swift deleted file mode 100644 index a7ee78d..0000000 --- a/RxComposableArchitecture/Stateless.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Stateless.swift -// RxComposableArchitecture_RxComposableArchitecture -// -// Created by Jefferson Setiawan on 21/05/21. -// - -import Foundation - -public struct Stateless: Equatable { - public init() {} -} diff --git a/RxComposableArchitecture/Store.swift b/RxComposableArchitecture/Store.swift deleted file mode 100644 index 5bdb446..0000000 --- a/RxComposableArchitecture/Store.swift +++ /dev/null @@ -1,284 +0,0 @@ -import DiffingInterface -import Foundation -import RxRelay -import RxSwift - -public final class Store { - public private(set) var state: State { - get { return relay.value } - set { relay.accept(newValue) } - } - - private var isSending = false - private var synchronousActionsToSend: [Action] = [] - private var bufferedActions: [Action] = [] - - private let reducer: (inout State, Action) -> Effect - - private let disposeBag = DisposeBag() - internal var effectDisposables = CompositeDisposable() - private let relay: BehaviorRelay - - public var observable: Observable { - return relay.asObservable() - } - - private init( - initialState: State, - reducer: @escaping (inout State, Action) -> Effect - ) { - relay = BehaviorRelay(value: initialState) - self.reducer = reducer - state = initialState - } - - public convenience init( - initialState: State, - reducer: Reducer, - environment: Environment - ) { - self.init( - initialState: initialState, - reducer: { reducer.callAsFunction(&$0, $1, environment) } - ) - } - - public func send(_ action: Action) { - if !isSending { - synchronousActionsToSend.append(action) - } else { - bufferedActions.append(action) - return - } - - while !synchronousActionsToSend.isEmpty || !bufferedActions.isEmpty { - let action = !synchronousActionsToSend.isEmpty - ? synchronousActionsToSend.removeFirst() - : bufferedActions.removeFirst() - - isSending = true - let effect = reducer(&state, action) - isSending = false - - var didComplete = false - var isProcessingEffects = true - var disposeKey: CompositeDisposable.DisposeKey? - - let effectDisposable = effect.subscribe( - onNext: { [weak self] action in - if isProcessingEffects { - self?.synchronousActionsToSend.append(action) - } else { - self?.send(action) - } - }, - onError: { err in - assertionFailure("Error during effect handling: \(err.localizedDescription)") - }, - onCompleted: { [weak self] in - didComplete = true - if let disposeKey = disposeKey { - self?.effectDisposables.remove(for: disposeKey) - } - } - ) - - isProcessingEffects = false - - if !didComplete { - disposeKey = effectDisposables.insert(effectDisposable) - } - } - } - - public func scope( - state toLocalState: @escaping (State) -> LocalState, - action fromLocalAction: @escaping (LocalAction) -> Action - ) -> Store { - let localStore = Store( - initialState: toLocalState(state), - reducer: { localState, localAction in - self.send(fromLocalAction(localAction)) - localState = toLocalState(self.state) - return .none - } - ) - - relay - .subscribe(onNext: { [weak localStore] newValue in - localStore?.state = toLocalState(newValue) - }) - .disposed(by: localStore.disposeBag) - - return localStore - } - - public func scope( - state toLocalState: @escaping (State) -> LocalState - ) -> Store { - scope(state: toLocalState, action: { $0 }) - } - - /// Scopes the store to a publisher of stores of more local state and local actions. - /// - /// - Parameters: - /// - toLocalState: A function that transforms a publisher of `State` into a publisher of - /// `LocalState`. - /// - fromLocalAction: A function that transforms `LocalAction` into `Action`. - /// - Returns: A publisher of stores with its domain (state and action) transformed. - public func scope( - state toLocalState: @escaping (Observable) -> Observable, - action fromLocalAction: @escaping (LocalAction) -> Action - ) -> Observable> { - func extractLocalState(_ state: State) -> LocalState? { - var localState: LocalState? - _ = toLocalState(Observable.just(state)).subscribe(onNext: { localState = $0 }) - return localState - } - - return toLocalState(relay.asObservable()) - .map { localState in - let localStore = Store( - initialState: localState, - reducer: { localState, localAction in - self.send(fromLocalAction(localAction)) - localState = extractLocalState(self.state) ?? localState - return .none - } - ) - - self.relay.asObservable() - .subscribe(onNext: { [weak localStore] state in - guard let localStore = localStore else { return } - localStore.state = extractLocalState(state) ?? localStore.state - }) - .disposed(by: self.disposeBag) - - return localStore - } - } - - /// Scopes the store to a publisher of stores of more local state and local actions. - /// - /// - Parameter toLocalState: A function that transforms a publisher of `State` into a publisher - /// of `LocalState`. - /// - Returns: A publisher of stores with its domain (state and action) - /// transformed. - public func scope( - state toLocalState: @escaping (Observable) -> Observable - ) -> Observable> { - scope(state: toLocalState, action: { $0 }) - } - - /// Returns a "stateless" store by erasing state to `Void`. - public var stateless: Store { - scope(state: { _ in () }) - } - - /// Returns an "actionless" store by erasing action to `Never`. - public var actionless: Store { - func absurd(_: Never) -> A {} - return scope(state: { $0 }, action: absurd) - } - - public func subscribe( - _ toLocalState: @escaping (State) -> LocalState, - removeDuplicates isDuplicate: @escaping (LocalState, LocalState) -> Bool - ) -> Effect { - return relay.map(toLocalState).distinctUntilChanged(isDuplicate).eraseToEffect() - } - - public func subscribe( - _ toLocalState: @escaping (State) -> LocalState - ) -> Effect { - return relay.map(toLocalState).distinctUntilChanged().eraseToEffect() - } -} - -extension Store where State: Equatable { - public func subscribe() -> Effect { - return relay.distinctUntilChanged().eraseToEffect() - } -} - -extension Store where State: Collection, State.Element: HashDiffable, State: Equatable, State.Element: Equatable { - /** - A version of scope that scope an collection of sub store. - - This is kinda a version of `ForEachStoreNode`, not composing `WithViewStore` but creates the sub store. - - ## Example - ``` - struct AppState { var todos: [Todo] } - struct AppAction { case todo(index: Int, action: TodoAction } - - store.subscribe(\.todos) - .drive(onNext: { todos in - self.todoNodes = zip(todos.indices, todos).map { (offset, _) in - TodoNode(with: store.scope( - identifier: identifier, - action: Action.todo(index:action:) - ) - } - }) - .disposed(by: disposeBag) - ``` - - But with example above, you created the entire node again and again and it's not the efficient way. - You can do some diffing and only creating spesific index, and rest is handle by diffing. - - - Parameters: - - identifier: the identifier from `IdentifierType` make sure index is in bounds of the collection - - action: A function to transform `LocalAction` to `Action`. `LocalAction` should have `(CollectionIndex, LocalAction)` signature. - - - Returns: A new store with its domain (state and domain) transformed based on the index you set - */ - public func scope( - at identifier: State.Element.IdentifierType, - action fromLocalAction: @escaping (LocalAction) -> Action - ) -> Store? { - let toLocalState: (State.Element.IdentifierType, State) -> State.Element? = { identifier, state in - /** - if current state is IdentifiedArray, use pre exist subscript by identifier, to improve performance - */ - if let identifiedArray = state as? IdentifiedArrayOf { - return identifiedArray[id: identifier] - } else { - return state.first(where: { $0.id == identifier }) - } - } - - // skip if element on parent state wasn't found - guard let element = toLocalState(identifier, state) else { return nil } - - let localStore = Store( - initialState: element, - reducer: { localState, localAction in - self.send(fromLocalAction(localAction)) - guard let finalState = toLocalState(identifier, self.state) else { - return .none - } - - localState = finalState - return .none - } - ) - - // reflect changes on store parent to local store - relay - .distinctUntilChanged() - .flatMapLatest { newValue -> Observable in - guard let newElement = toLocalState(identifier, newValue) else { - return .empty() - } - - return .just(newElement) - } - .subscribe(onNext: { [weak localStore] newValue in - localStore?.state = newValue - }) - .disposed(by: localStore.disposeBag) - - return localStore - } -} diff --git a/RxComposableArchitecture/development-podspecs/CasePaths.podspec.json b/RxComposableArchitecture/development-podspecs/CasePaths.podspec.json deleted file mode 100644 index f8f0ea3..0000000 --- a/RxComposableArchitecture/development-podspecs/CasePaths.podspec.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "CasePaths", - "version": "0.1.0", - "authors": "local pod", - "homepage": "https://github.com/pointfreeco/swift-case-paths", - "summary": "local pod", - "license": { - "type": "MIT", - "file": "LICENSE" - }, - "platforms": { - "ios": "10.0" - }, - "source": { - "git": "https://github.com/pointfreeco/swift-case-paths", - "tag": "0.1.3" - }, - "source_files": "Sources/**/*.swift", - "swift_version": "5.0" -} \ No newline at end of file diff --git a/RxComposableArchitectureExample.xcodeproj/project.pbxproj b/RxComposableArchitectureExample.xcodeproj/project.pbxproj deleted file mode 100644 index 117c735..0000000 --- a/RxComposableArchitectureExample.xcodeproj/project.pbxproj +++ /dev/null @@ -1,1836 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXBuildFile section */ - 16BF42554966E67266B4076B /* Pods_RxComposableArchitectureExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D35E75E6027094F0586CB60E /* Pods_RxComposableArchitectureExample.framework */; }; - 2A32D642256972C1FF0BAB1F /* Pods_TestSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 74CA63C0EA127FBD45E7C596 /* Pods_TestSupport.framework */; }; - 48DAC5ED27C381FD001D45D0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC5EC27C381FD001D45D0 /* AppDelegate.swift */; }; - 48DAC5EF27C381FD001D45D0 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC5EE27C381FD001D45D0 /* SceneDelegate.swift */; }; - 48DAC5F127C381FD001D45D0 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC5F027C381FD001D45D0 /* ViewController.swift */; }; - 48DAC5F427C381FD001D45D0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 48DAC5F227C381FD001D45D0 /* Main.storyboard */; }; - 48DAC5F627C381FF001D45D0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 48DAC5F527C381FF001D45D0 /* Assets.xcassets */; }; - 48DAC5F927C381FF001D45D0 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 48DAC5F727C381FF001D45D0 /* LaunchScreen.storyboard */; }; - 48DAC60927C3843A001D45D0 /* RxComposableArchitecture.docc in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC60827C3843A001D45D0 /* RxComposableArchitecture.docc */; }; - 48DAC60A27C3843A001D45D0 /* RxComposableArchitecture.h in Headers */ = {isa = PBXBuildFile; fileRef = 48DAC60727C3843A001D45D0 /* RxComposableArchitecture.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48DAC60D27C3843A001D45D0 /* RxComposableArchitecture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC60527C3843A001D45D0 /* RxComposableArchitecture.framework */; }; - 48DAC60E27C3843A001D45D0 /* RxComposableArchitecture.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC60527C3843A001D45D0 /* RxComposableArchitecture.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 48DAC61C27C38663001D45D0 /* DiffingInterface.docc in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC61B27C38663001D45D0 /* DiffingInterface.docc */; }; - 48DAC61D27C38663001D45D0 /* DiffingInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 48DAC61A27C38663001D45D0 /* DiffingInterface.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48DAC62027C38663001D45D0 /* DiffingInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC61827C38663001D45D0 /* DiffingInterface.framework */; }; - 48DAC62127C38663001D45D0 /* DiffingInterface.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC61827C38663001D45D0 /* DiffingInterface.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 48DAC62927C38677001D45D0 /* DiffingInterface+Primitives.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC62527C38677001D45D0 /* DiffingInterface+Primitives.swift */; }; - 48DAC62A27C38677001D45D0 /* HashDiffing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC62627C38677001D45D0 /* HashDiffing.swift */; }; - 48DAC62B27C38677001D45D0 /* HashDiffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC62727C38677001D45D0 /* HashDiffable.swift */; }; - 48DAC62C27C38677001D45D0 /* AnyHashDiffable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC62827C38677001D45D0 /* AnyHashDiffable.swift */; }; - 48DAC63627C386C0001D45D0 /* DiffingUtility.docc in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC63527C386C0001D45D0 /* DiffingUtility.docc */; }; - 48DAC63727C386C0001D45D0 /* DiffingUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = 48DAC63427C386C0001D45D0 /* DiffingUtility.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48DAC63A27C386C0001D45D0 /* DiffingUtility.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC63227C386C0001D45D0 /* DiffingUtility.framework */; }; - 48DAC63B27C386C0001D45D0 /* DiffingUtility.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC63227C386C0001D45D0 /* DiffingUtility.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 48DAC64127C386CF001D45D0 /* Diff.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC63F27C386CF001D45D0 /* Diff.swift */; }; - 48DAC64227C386CF001D45D0 /* Debug.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC64027C386CF001D45D0 /* Debug.swift */; }; - 48DAC64327C386E0001D45D0 /* DiffingInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC61827C38663001D45D0 /* DiffingInterface.framework */; }; - 48DAC64827C386E3001D45D0 /* DiffingUtility.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC63227C386C0001D45D0 /* DiffingUtility.framework */; }; - 48DAC6EF27C38B10001D45D0 /* IfLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6D427C38B0F001D45D0 /* IfLet.swift */; }; - 48DAC6F027C38B10001D45D0 /* Binding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6D527C38B0F001D45D0 /* Binding.swift */; }; - 48DAC6F127C38B10001D45D0 /* Effect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6D627C38B0F001D45D0 /* Effect.swift */; }; - 48DAC6F227C38B10001D45D0 /* Export.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6D727C38B0F001D45D0 /* Export.swift */; }; - 48DAC6F327C38B10001D45D0 /* Store.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6D827C38B0F001D45D0 /* Store.swift */; }; - 48DAC6F427C38B10001D45D0 /* IdentifiedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6D927C38B0F001D45D0 /* IdentifiedArray.swift */; }; - 48DAC6F527C38B10001D45D0 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6DB27C38B0F001D45D0 /* Deprecated.swift */; }; - 48DAC6F627C38B10001D45D0 /* AnyDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6DC27C38B0F001D45D0 /* AnyDisposable.swift */; }; - 48DAC6F727C38B10001D45D0 /* Locking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6DD27C38B0F001D45D0 /* Locking.swift */; }; - 48DAC6F827C38B10001D45D0 /* Reducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6DE27C38B10001D45D0 /* Reducer.swift */; }; - 48DAC6F927C38B10001D45D0 /* Stateless.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6DF27C38B10001D45D0 /* Stateless.swift */; }; - 48DAC6FA27C38B10001D45D0 /* MockPageTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6E127C38B10001D45D0 /* MockPageTemplate.swift */; }; - 48DAC6FB27C38B10001D45D0 /* Bootstrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6E227C38B10001D45D0 /* Bootstrapping.swift */; }; - 48DAC6FC27C38B10001D45D0 /* ReducerInstrumentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6E327C38B10001D45D0 /* ReducerInstrumentation.swift */; }; - 48DAC6FD27C38B10001D45D0 /* ReducerDebugging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6E427C38B10001D45D0 /* ReducerDebugging.swift */; }; - 48DAC6FE27C38B10001D45D0 /* SingleSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6E627C38B10001D45D0 /* SingleSelection.swift */; }; - 48DAC6FF27C38B10001D45D0 /* UniqueElements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6E727C38B10001D45D0 /* UniqueElements.swift */; }; - 48DAC70027C38B10001D45D0 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6E927C38B10001D45D0 /* Timer.swift */; }; - 48DAC70127C38B10001D45D0 /* Cancellation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6EA27C38B10001D45D0 /* Cancellation.swift */; }; - 48DAC70227C38B10001D45D0 /* Deferring.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6EB27C38B10001D45D0 /* Deferring.swift */; }; - 48DAC70327C38B10001D45D0 /* FireAndForget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6EC27C38B10001D45D0 /* FireAndForget.swift */; }; - 48DAC70427C38B10001D45D0 /* Debouncing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6ED27C38B10001D45D0 /* Debouncing.swift */; }; - 48DAC70527C38B10001D45D0 /* OptionalPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC6EE27C38B10001D45D0 /* OptionalPaths.swift */; }; - 48DAC70F27C38CCB001D45D0 /* RxComposableArchitectureTests.docc in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC70E27C38CCB001D45D0 /* RxComposableArchitectureTests.docc */; }; - 48DAC71027C38CCB001D45D0 /* RxComposableArchitectureTests.h in Headers */ = {isa = PBXBuildFile; fileRef = 48DAC70D27C38CCB001D45D0 /* RxComposableArchitectureTests.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48DAC71327C38CCB001D45D0 /* RxComposableArchitectureTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC70B27C38CCB001D45D0 /* RxComposableArchitectureTests.framework */; }; - 48DAC71427C38CCB001D45D0 /* RxComposableArchitectureTests.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC70B27C38CCB001D45D0 /* RxComposableArchitectureTests.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 48DAC72627C38CDE001D45D0 /* IdentifiedArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC71827C38CDE001D45D0 /* IdentifiedArrayTests.swift */; }; - 48DAC72727C38CDE001D45D0 /* EffectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC71927C38CDE001D45D0 /* EffectTests.swift */; }; - 48DAC72827C38CDE001D45D0 /* EffectDebounceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC71A27C38CDE001D45D0 /* EffectDebounceTests.swift */; }; - 48DAC72927C38CDE001D45D0 /* LCRNG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC71B27C38CDE001D45D0 /* LCRNG.swift */; }; - 48DAC72A27C38CDE001D45D0 /* EffectCancellationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC71C27C38CDE001D45D0 /* EffectCancellationTests.swift */; }; - 48DAC72B27C38CDE001D45D0 /* RxComposableArchitectureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC71D27C38CDE001D45D0 /* RxComposableArchitectureTests.swift */; }; - 48DAC72C27C38CDE001D45D0 /* TimerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC71E27C38CDE001D45D0 /* TimerTests.swift */; }; - 48DAC72D27C38CDE001D45D0 /* EffectDeferredTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC71F27C38CDE001D45D0 /* EffectDeferredTests.swift */; }; - 48DAC72E27C38CDE001D45D0 /* ReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC72027C38CDE001D45D0 /* ReducerTests.swift */; }; - 48DAC72F27C38CDE001D45D0 /* DebugTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC72127C38CDE001D45D0 /* DebugTests.swift */; }; - 48DAC73027C38CDE001D45D0 /* SingleSelectionSelectableTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC72227C38CDE001D45D0 /* SingleSelectionSelectableTypeTests.swift */; }; - 48DAC73127C38CDE001D45D0 /* StoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC72327C38CDE001D45D0 /* StoreTests.swift */; }; - 48DAC73227C38CDE001D45D0 /* MemoryManagementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC72427C38CDE001D45D0 /* MemoryManagementTests.swift */; }; - 48DAC73327C38CDE001D45D0 /* SingleSelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC72527C38CDE001D45D0 /* SingleSelectionTests.swift */; }; - 48DAC73427C38CFE001D45D0 /* RxComposableArchitecture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC60527C3843A001D45D0 /* RxComposableArchitecture.framework */; }; - 48DAC74227C38D97001D45D0 /* TestSupport.docc in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC74127C38D97001D45D0 /* TestSupport.docc */; }; - 48DAC74327C38D97001D45D0 /* TestSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 48DAC74027C38D97001D45D0 /* TestSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48DAC74627C38D97001D45D0 /* TestSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC73E27C38D97001D45D0 /* TestSupport.framework */; }; - 48DAC74727C38D97001D45D0 /* TestSupport.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC73E27C38D97001D45D0 /* TestSupport.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 48DAC75427C38DAA001D45D0 /* TestScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC74B27C38DAA001D45D0 /* TestScheduler.swift */; }; - 48DAC75527C38DAA001D45D0 /* Export.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC74C27C38DAA001D45D0 /* Export.swift */; }; - 48DAC75627C38DAA001D45D0 /* Unwrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC74D27C38DAA001D45D0 /* Unwrap.swift */; }; - 48DAC75727C38DAA001D45D0 /* Annotating.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC74E27C38DAA001D45D0 /* Annotating.swift */; }; - 48DAC75827C38DAA001D45D0 /* FailingEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC74F27C38DAA001D45D0 /* FailingEffect.swift */; }; - 48DAC75927C38DAA001D45D0 /* VirtualTimeScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC75127C38DAA001D45D0 /* VirtualTimeScheduler.swift */; }; - 48DAC75A27C38DAA001D45D0 /* PriorityQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC75227C38DAA001D45D0 /* PriorityQueue.swift */; }; - 48DAC75B27C38DAA001D45D0 /* TestStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC75327C38DAA001D45D0 /* TestStore.swift */; }; - 48DAC75C27C38DB1001D45D0 /* TestSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC73E27C38D97001D45D0 /* TestSupport.framework */; }; - 48DAC76227C38E73001D45D0 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC76127C38E73001D45D0 /* XCTest.framework */; platformFilter = ios; }; - 48DAC76527C38E81001D45D0 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC76127C38E73001D45D0 /* XCTest.framework */; platformFilter = ios; }; - 48DAC77127C38EAD001D45D0 /* DiffingTestSupport.docc in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC77027C38EAD001D45D0 /* DiffingTestSupport.docc */; }; - 48DAC77227C38EAD001D45D0 /* DiffingTestSupport.h in Headers */ = {isa = PBXBuildFile; fileRef = 48DAC76F27C38EAD001D45D0 /* DiffingTestSupport.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48DAC77527C38EAD001D45D0 /* DiffingTestSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC76D27C38EAD001D45D0 /* DiffingTestSupport.framework */; }; - 48DAC77627C38EAD001D45D0 /* DiffingTestSupport.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC76D27C38EAD001D45D0 /* DiffingTestSupport.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 48DAC77C27C38EC9001D45D0 /* XCTDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC77A27C38EC9001D45D0 /* XCTDecode.swift */; }; - 48DAC77D27C38EC9001D45D0 /* XCTPrettyEqual.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48DAC77B27C38EC9001D45D0 /* XCTPrettyEqual.swift */; }; - 48DAC77E27C38EF7001D45D0 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC76127C38E73001D45D0 /* XCTest.framework */; platformFilter = ios; }; - 48DAC78127C38F26001D45D0 /* RxComposableArchitecture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC60527C3843A001D45D0 /* RxComposableArchitecture.framework */; }; - 48DAC78627C38F4E001D45D0 /* DiffingTestSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC76D27C38EAD001D45D0 /* DiffingTestSupport.framework */; }; - 48DAC78B27C38FAF001D45D0 /* DiffingUtility.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48DAC63227C386C0001D45D0 /* DiffingUtility.framework */; }; - 7BA5C006372D1DB429138D8B /* Pods_RxComposableArchitectureTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 165D1F46AB6664F68B7E57BB /* Pods_RxComposableArchitectureTests.framework */; }; - E4EF80B30828B9F434A161B4 /* Pods_RxComposableArchitecture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 99350B5DA80BAC5D18EA3B8D /* Pods_RxComposableArchitecture.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 48DAC60B27C3843A001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC60427C3843A001D45D0; - remoteInfo = RxComposableArchitecture; - }; - 48DAC61E27C38663001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC61727C38663001D45D0; - remoteInfo = DiffingInterface; - }; - 48DAC63827C386C0001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC63127C386C0001D45D0; - remoteInfo = DiffingUtility; - }; - 48DAC64527C386E0001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC61727C38663001D45D0; - remoteInfo = DiffingInterface; - }; - 48DAC64A27C386E3001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC63127C386C0001D45D0; - remoteInfo = DiffingUtility; - }; - 48DAC71127C38CCB001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC70A27C38CCB001D45D0; - remoteInfo = RxComposableArchitectureTests; - }; - 48DAC73627C38CFE001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC60427C3843A001D45D0; - remoteInfo = RxComposableArchitecture; - }; - 48DAC74427C38D97001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC73D27C38D97001D45D0; - remoteInfo = TestSupport; - }; - 48DAC75E27C38DB1001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC73D27C38D97001D45D0; - remoteInfo = TestSupport; - }; - 48DAC77327C38EAD001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC76C27C38EAD001D45D0; - remoteInfo = DiffingTestSupport; - }; - 48DAC78327C38F26001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC60427C3843A001D45D0; - remoteInfo = RxComposableArchitecture; - }; - 48DAC78827C38F4E001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC76C27C38EAD001D45D0; - remoteInfo = DiffingTestSupport; - }; - 48DAC78D27C38FAF001D45D0 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 48DAC5E127C381FD001D45D0 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 48DAC63127C386C0001D45D0; - remoteInfo = DiffingUtility; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 48DAC60F27C3843A001D45D0 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 48DAC74727C38D97001D45D0 /* TestSupport.framework in Embed Frameworks */, - 48DAC71427C38CCB001D45D0 /* RxComposableArchitectureTests.framework in Embed Frameworks */, - 48DAC62127C38663001D45D0 /* DiffingInterface.framework in Embed Frameworks */, - 48DAC60E27C3843A001D45D0 /* RxComposableArchitecture.framework in Embed Frameworks */, - 48DAC77627C38EAD001D45D0 /* DiffingTestSupport.framework in Embed Frameworks */, - 48DAC63B27C386C0001D45D0 /* DiffingUtility.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 165D1F46AB6664F68B7E57BB /* Pods_RxComposableArchitectureTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxComposableArchitectureTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 1CDF88EE99C341A71671F250 /* Pods-RxComposableArchitectureExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxComposableArchitectureExample.release.xcconfig"; path = "Target Support Files/Pods-RxComposableArchitectureExample/Pods-RxComposableArchitectureExample.release.xcconfig"; sourceTree = ""; }; - 48DAC5E927C381FD001D45D0 /* RxComposableArchitectureExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxComposableArchitectureExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 48DAC5EC27C381FD001D45D0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 48DAC5EE27C381FD001D45D0 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - 48DAC5F027C381FD001D45D0 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; - 48DAC5F327C381FD001D45D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 48DAC5F527C381FF001D45D0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 48DAC5F827C381FF001D45D0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 48DAC5FA27C381FF001D45D0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 48DAC60527C3843A001D45D0 /* RxComposableArchitecture.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxComposableArchitecture.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 48DAC60727C3843A001D45D0 /* RxComposableArchitecture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RxComposableArchitecture.h; sourceTree = ""; }; - 48DAC60827C3843A001D45D0 /* RxComposableArchitecture.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = RxComposableArchitecture.docc; sourceTree = ""; }; - 48DAC61827C38663001D45D0 /* DiffingInterface.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DiffingInterface.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 48DAC61A27C38663001D45D0 /* DiffingInterface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DiffingInterface.h; sourceTree = ""; }; - 48DAC61B27C38663001D45D0 /* DiffingInterface.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = DiffingInterface.docc; sourceTree = ""; }; - 48DAC62527C38677001D45D0 /* DiffingInterface+Primitives.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DiffingInterface+Primitives.swift"; sourceTree = ""; }; - 48DAC62627C38677001D45D0 /* HashDiffing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HashDiffing.swift; sourceTree = ""; }; - 48DAC62727C38677001D45D0 /* HashDiffable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HashDiffable.swift; sourceTree = ""; }; - 48DAC62827C38677001D45D0 /* AnyHashDiffable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyHashDiffable.swift; sourceTree = ""; }; - 48DAC63227C386C0001D45D0 /* DiffingUtility.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DiffingUtility.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 48DAC63427C386C0001D45D0 /* DiffingUtility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DiffingUtility.h; sourceTree = ""; }; - 48DAC63527C386C0001D45D0 /* DiffingUtility.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = DiffingUtility.docc; sourceTree = ""; }; - 48DAC63F27C386CF001D45D0 /* Diff.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Diff.swift; sourceTree = ""; }; - 48DAC64027C386CF001D45D0 /* Debug.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Debug.swift; sourceTree = ""; }; - 48DAC6D427C38B0F001D45D0 /* IfLet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IfLet.swift; sourceTree = ""; }; - 48DAC6D527C38B0F001D45D0 /* Binding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Binding.swift; sourceTree = ""; }; - 48DAC6D627C38B0F001D45D0 /* Effect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Effect.swift; sourceTree = ""; }; - 48DAC6D727C38B0F001D45D0 /* Export.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Export.swift; sourceTree = ""; }; - 48DAC6D827C38B0F001D45D0 /* Store.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Store.swift; sourceTree = ""; }; - 48DAC6D927C38B0F001D45D0 /* IdentifiedArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifiedArray.swift; sourceTree = ""; }; - 48DAC6DB27C38B0F001D45D0 /* Deprecated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deprecated.swift; sourceTree = ""; }; - 48DAC6DC27C38B0F001D45D0 /* AnyDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDisposable.swift; sourceTree = ""; }; - 48DAC6DD27C38B0F001D45D0 /* Locking.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Locking.swift; sourceTree = ""; }; - 48DAC6DE27C38B10001D45D0 /* Reducer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reducer.swift; sourceTree = ""; }; - 48DAC6DF27C38B10001D45D0 /* Stateless.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stateless.swift; sourceTree = ""; }; - 48DAC6E127C38B10001D45D0 /* MockPageTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockPageTemplate.swift; sourceTree = ""; }; - 48DAC6E227C38B10001D45D0 /* Bootstrapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bootstrapping.swift; sourceTree = ""; }; - 48DAC6E327C38B10001D45D0 /* ReducerInstrumentation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReducerInstrumentation.swift; sourceTree = ""; }; - 48DAC6E427C38B10001D45D0 /* ReducerDebugging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReducerDebugging.swift; sourceTree = ""; }; - 48DAC6E627C38B10001D45D0 /* SingleSelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleSelection.swift; sourceTree = ""; }; - 48DAC6E727C38B10001D45D0 /* UniqueElements.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniqueElements.swift; sourceTree = ""; }; - 48DAC6E927C38B10001D45D0 /* Timer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = ""; }; - 48DAC6EA27C38B10001D45D0 /* Cancellation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cancellation.swift; sourceTree = ""; }; - 48DAC6EB27C38B10001D45D0 /* Deferring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Deferring.swift; sourceTree = ""; }; - 48DAC6EC27C38B10001D45D0 /* FireAndForget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireAndForget.swift; sourceTree = ""; }; - 48DAC6ED27C38B10001D45D0 /* Debouncing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Debouncing.swift; sourceTree = ""; }; - 48DAC6EE27C38B10001D45D0 /* OptionalPaths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalPaths.swift; sourceTree = ""; }; - 48DAC70B27C38CCB001D45D0 /* RxComposableArchitectureTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RxComposableArchitectureTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 48DAC70D27C38CCB001D45D0 /* RxComposableArchitectureTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RxComposableArchitectureTests.h; sourceTree = ""; }; - 48DAC70E27C38CCB001D45D0 /* RxComposableArchitectureTests.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = RxComposableArchitectureTests.docc; sourceTree = ""; }; - 48DAC71827C38CDE001D45D0 /* IdentifiedArrayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentifiedArrayTests.swift; sourceTree = ""; }; - 48DAC71927C38CDE001D45D0 /* EffectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectTests.swift; sourceTree = ""; }; - 48DAC71A27C38CDE001D45D0 /* EffectDebounceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectDebounceTests.swift; sourceTree = ""; }; - 48DAC71B27C38CDE001D45D0 /* LCRNG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCRNG.swift; sourceTree = ""; }; - 48DAC71C27C38CDE001D45D0 /* EffectCancellationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectCancellationTests.swift; sourceTree = ""; }; - 48DAC71D27C38CDE001D45D0 /* RxComposableArchitectureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxComposableArchitectureTests.swift; sourceTree = ""; }; - 48DAC71E27C38CDE001D45D0 /* TimerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimerTests.swift; sourceTree = ""; }; - 48DAC71F27C38CDE001D45D0 /* EffectDeferredTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectDeferredTests.swift; sourceTree = ""; }; - 48DAC72027C38CDE001D45D0 /* ReducerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReducerTests.swift; sourceTree = ""; }; - 48DAC72127C38CDE001D45D0 /* DebugTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugTests.swift; sourceTree = ""; }; - 48DAC72227C38CDE001D45D0 /* SingleSelectionSelectableTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleSelectionSelectableTypeTests.swift; sourceTree = ""; }; - 48DAC72327C38CDE001D45D0 /* StoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreTests.swift; sourceTree = ""; }; - 48DAC72427C38CDE001D45D0 /* MemoryManagementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoryManagementTests.swift; sourceTree = ""; }; - 48DAC72527C38CDE001D45D0 /* SingleSelectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleSelectionTests.swift; sourceTree = ""; }; - 48DAC73E27C38D97001D45D0 /* TestSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TestSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 48DAC74027C38D97001D45D0 /* TestSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestSupport.h; sourceTree = ""; }; - 48DAC74127C38D97001D45D0 /* TestSupport.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = TestSupport.docc; sourceTree = ""; }; - 48DAC74B27C38DAA001D45D0 /* TestScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestScheduler.swift; sourceTree = ""; }; - 48DAC74C27C38DAA001D45D0 /* Export.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Export.swift; sourceTree = ""; }; - 48DAC74D27C38DAA001D45D0 /* Unwrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Unwrap.swift; sourceTree = ""; }; - 48DAC74E27C38DAA001D45D0 /* Annotating.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Annotating.swift; sourceTree = ""; }; - 48DAC74F27C38DAA001D45D0 /* FailingEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FailingEffect.swift; sourceTree = ""; }; - 48DAC75127C38DAA001D45D0 /* VirtualTimeScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VirtualTimeScheduler.swift; sourceTree = ""; }; - 48DAC75227C38DAA001D45D0 /* PriorityQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PriorityQueue.swift; sourceTree = ""; }; - 48DAC75327C38DAA001D45D0 /* TestStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestStore.swift; sourceTree = ""; }; - 48DAC76127C38E73001D45D0 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; - 48DAC76D27C38EAD001D45D0 /* DiffingTestSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DiffingTestSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 48DAC76F27C38EAD001D45D0 /* DiffingTestSupport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DiffingTestSupport.h; sourceTree = ""; }; - 48DAC77027C38EAD001D45D0 /* DiffingTestSupport.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = DiffingTestSupport.docc; sourceTree = ""; }; - 48DAC77A27C38EC9001D45D0 /* XCTDecode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTDecode.swift; sourceTree = ""; }; - 48DAC77B27C38EC9001D45D0 /* XCTPrettyEqual.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XCTPrettyEqual.swift; sourceTree = ""; }; - 593494F11362312C1943BCAA /* Pods-RxComposableArchitecture.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxComposableArchitecture.release.xcconfig"; path = "Target Support Files/Pods-RxComposableArchitecture/Pods-RxComposableArchitecture.release.xcconfig"; sourceTree = ""; }; - 5D9BE14880FA5F45EA62F238 /* Pods-RxComposableArchitecture.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxComposableArchitecture.debug.xcconfig"; path = "Target Support Files/Pods-RxComposableArchitecture/Pods-RxComposableArchitecture.debug.xcconfig"; sourceTree = ""; }; - 6E5147DCC852345C4F52D0F9 /* Pods-RxComposableArchitectureTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxComposableArchitectureTests.debug.xcconfig"; path = "Target Support Files/Pods-RxComposableArchitectureTests/Pods-RxComposableArchitectureTests.debug.xcconfig"; sourceTree = ""; }; - 74CA63C0EA127FBD45E7C596 /* Pods_TestSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TestSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 99350B5DA80BAC5D18EA3B8D /* Pods_RxComposableArchitecture.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxComposableArchitecture.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B711F2F591349482FACDBE8C /* Pods-TestSupport.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TestSupport.debug.xcconfig"; path = "Target Support Files/Pods-TestSupport/Pods-TestSupport.debug.xcconfig"; sourceTree = ""; }; - BAC720BB1276FD4A58E0995F /* Pods-RxComposableArchitectureExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxComposableArchitectureExample.debug.xcconfig"; path = "Target Support Files/Pods-RxComposableArchitectureExample/Pods-RxComposableArchitectureExample.debug.xcconfig"; sourceTree = ""; }; - CE869D92EC2BD944F657ABC9 /* Pods-RxComposableArchitectureTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxComposableArchitectureTests.release.xcconfig"; path = "Target Support Files/Pods-RxComposableArchitectureTests/Pods-RxComposableArchitectureTests.release.xcconfig"; sourceTree = ""; }; - D35E75E6027094F0586CB60E /* Pods_RxComposableArchitectureExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxComposableArchitectureExample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - F1387F3EA85FC5DB51814A48 /* Pods-TestSupport.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TestSupport.release.xcconfig"; path = "Target Support Files/Pods-TestSupport/Pods-TestSupport.release.xcconfig"; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 48DAC5E627C381FD001D45D0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC74627C38D97001D45D0 /* TestSupport.framework in Frameworks */, - 48DAC60D27C3843A001D45D0 /* RxComposableArchitecture.framework in Frameworks */, - 48DAC77527C38EAD001D45D0 /* DiffingTestSupport.framework in Frameworks */, - 16BF42554966E67266B4076B /* Pods_RxComposableArchitectureExample.framework in Frameworks */, - 48DAC62027C38663001D45D0 /* DiffingInterface.framework in Frameworks */, - 48DAC63A27C386C0001D45D0 /* DiffingUtility.framework in Frameworks */, - 48DAC71327C38CCB001D45D0 /* RxComposableArchitectureTests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC60227C3843A001D45D0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC64327C386E0001D45D0 /* DiffingInterface.framework in Frameworks */, - E4EF80B30828B9F434A161B4 /* Pods_RxComposableArchitecture.framework in Frameworks */, - 48DAC64827C386E3001D45D0 /* DiffingUtility.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC61527C38663001D45D0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC62F27C386C0001D45D0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC70827C38CCB001D45D0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC75C27C38DB1001D45D0 /* TestSupport.framework in Frameworks */, - 48DAC73427C38CFE001D45D0 /* RxComposableArchitecture.framework in Frameworks */, - 7BA5C006372D1DB429138D8B /* Pods_RxComposableArchitectureTests.framework in Frameworks */, - 48DAC76227C38E73001D45D0 /* XCTest.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC73B27C38D97001D45D0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC78127C38F26001D45D0 /* RxComposableArchitecture.framework in Frameworks */, - 48DAC78627C38F4E001D45D0 /* DiffingTestSupport.framework in Frameworks */, - 48DAC76527C38E81001D45D0 /* XCTest.framework in Frameworks */, - 2A32D642256972C1FF0BAB1F /* Pods_TestSupport.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC76A27C38EAD001D45D0 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC77E27C38EF7001D45D0 /* XCTest.framework in Frameworks */, - 48DAC78B27C38FAF001D45D0 /* DiffingUtility.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 162C0FF428D521412006FBCF /* Pods */ = { - isa = PBXGroup; - children = ( - 5D9BE14880FA5F45EA62F238 /* Pods-RxComposableArchitecture.debug.xcconfig */, - 593494F11362312C1943BCAA /* Pods-RxComposableArchitecture.release.xcconfig */, - BAC720BB1276FD4A58E0995F /* Pods-RxComposableArchitectureExample.debug.xcconfig */, - 1CDF88EE99C341A71671F250 /* Pods-RxComposableArchitectureExample.release.xcconfig */, - 6E5147DCC852345C4F52D0F9 /* Pods-RxComposableArchitectureTests.debug.xcconfig */, - CE869D92EC2BD944F657ABC9 /* Pods-RxComposableArchitectureTests.release.xcconfig */, - B711F2F591349482FACDBE8C /* Pods-TestSupport.debug.xcconfig */, - F1387F3EA85FC5DB51814A48 /* Pods-TestSupport.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - 48DAC5E027C381FD001D45D0 = { - isa = PBXGroup; - children = ( - 48DAC5EB27C381FD001D45D0 /* RxComposableArchitectureExample */, - 48DAC60627C3843A001D45D0 /* RxComposableArchitecture */, - 48DAC61927C38663001D45D0 /* DiffingInterface */, - 48DAC63327C386C0001D45D0 /* DiffingUtility */, - 48DAC70C27C38CCB001D45D0 /* RxComposableArchitectureTests */, - 48DAC73F27C38D97001D45D0 /* TestSupport */, - 48DAC76E27C38EAD001D45D0 /* DiffingTestSupport */, - 48DAC5EA27C381FD001D45D0 /* Products */, - 162C0FF428D521412006FBCF /* Pods */, - C1F4AC2AB7BCDCBDA46BF5ED /* Frameworks */, - ); - sourceTree = ""; - }; - 48DAC5EA27C381FD001D45D0 /* Products */ = { - isa = PBXGroup; - children = ( - 48DAC5E927C381FD001D45D0 /* RxComposableArchitectureExample.app */, - 48DAC60527C3843A001D45D0 /* RxComposableArchitecture.framework */, - 48DAC61827C38663001D45D0 /* DiffingInterface.framework */, - 48DAC63227C386C0001D45D0 /* DiffingUtility.framework */, - 48DAC70B27C38CCB001D45D0 /* RxComposableArchitectureTests.framework */, - 48DAC73E27C38D97001D45D0 /* TestSupport.framework */, - 48DAC76D27C38EAD001D45D0 /* DiffingTestSupport.framework */, - ); - name = Products; - sourceTree = ""; - }; - 48DAC5EB27C381FD001D45D0 /* RxComposableArchitectureExample */ = { - isa = PBXGroup; - children = ( - 48DAC5EC27C381FD001D45D0 /* AppDelegate.swift */, - 48DAC5EE27C381FD001D45D0 /* SceneDelegate.swift */, - 48DAC5F027C381FD001D45D0 /* ViewController.swift */, - 48DAC5F227C381FD001D45D0 /* Main.storyboard */, - 48DAC5F527C381FF001D45D0 /* Assets.xcassets */, - 48DAC5F727C381FF001D45D0 /* LaunchScreen.storyboard */, - 48DAC5FA27C381FF001D45D0 /* Info.plist */, - ); - path = RxComposableArchitectureExample; - sourceTree = ""; - }; - 48DAC60627C3843A001D45D0 /* RxComposableArchitecture */ = { - isa = PBXGroup; - children = ( - 48DAC6D527C38B0F001D45D0 /* Binding.swift */, - 48DAC6E027C38B10001D45D0 /* Debugging */, - 48DAC6D627C38B0F001D45D0 /* Effect.swift */, - 48DAC6E827C38B10001D45D0 /* Effects */, - 48DAC6D727C38B0F001D45D0 /* Export.swift */, - 48DAC6D927C38B0F001D45D0 /* IdentifiedArray.swift */, - 48DAC6D427C38B0F001D45D0 /* IfLet.swift */, - 48DAC6DA27C38B0F001D45D0 /* Internal */, - 48DAC6EE27C38B10001D45D0 /* OptionalPaths.swift */, - 48DAC6E527C38B10001D45D0 /* PropertyWrapper */, - 48DAC6DE27C38B10001D45D0 /* Reducer.swift */, - 48DAC6DF27C38B10001D45D0 /* Stateless.swift */, - 48DAC6D827C38B0F001D45D0 /* Store.swift */, - 48DAC60727C3843A001D45D0 /* RxComposableArchitecture.h */, - 48DAC60827C3843A001D45D0 /* RxComposableArchitecture.docc */, - ); - path = RxComposableArchitecture; - sourceTree = ""; - }; - 48DAC61927C38663001D45D0 /* DiffingInterface */ = { - isa = PBXGroup; - children = ( - 48DAC62827C38677001D45D0 /* AnyHashDiffable.swift */, - 48DAC62527C38677001D45D0 /* DiffingInterface+Primitives.swift */, - 48DAC62727C38677001D45D0 /* HashDiffable.swift */, - 48DAC62627C38677001D45D0 /* HashDiffing.swift */, - 48DAC61A27C38663001D45D0 /* DiffingInterface.h */, - 48DAC61B27C38663001D45D0 /* DiffingInterface.docc */, - ); - path = DiffingInterface; - sourceTree = ""; - }; - 48DAC63327C386C0001D45D0 /* DiffingUtility */ = { - isa = PBXGroup; - children = ( - 48DAC64027C386CF001D45D0 /* Debug.swift */, - 48DAC63F27C386CF001D45D0 /* Diff.swift */, - 48DAC63427C386C0001D45D0 /* DiffingUtility.h */, - 48DAC63527C386C0001D45D0 /* DiffingUtility.docc */, - ); - path = DiffingUtility; - sourceTree = ""; - }; - 48DAC6DA27C38B0F001D45D0 /* Internal */ = { - isa = PBXGroup; - children = ( - 48DAC6DB27C38B0F001D45D0 /* Deprecated.swift */, - 48DAC6DC27C38B0F001D45D0 /* AnyDisposable.swift */, - 48DAC6DD27C38B0F001D45D0 /* Locking.swift */, - ); - path = Internal; - sourceTree = ""; - }; - 48DAC6E027C38B10001D45D0 /* Debugging */ = { - isa = PBXGroup; - children = ( - 48DAC6E127C38B10001D45D0 /* MockPageTemplate.swift */, - 48DAC6E227C38B10001D45D0 /* Bootstrapping.swift */, - 48DAC6E327C38B10001D45D0 /* ReducerInstrumentation.swift */, - 48DAC6E427C38B10001D45D0 /* ReducerDebugging.swift */, - ); - path = Debugging; - sourceTree = ""; - }; - 48DAC6E527C38B10001D45D0 /* PropertyWrapper */ = { - isa = PBXGroup; - children = ( - 48DAC6E627C38B10001D45D0 /* SingleSelection.swift */, - 48DAC6E727C38B10001D45D0 /* UniqueElements.swift */, - ); - path = PropertyWrapper; - sourceTree = ""; - }; - 48DAC6E827C38B10001D45D0 /* Effects */ = { - isa = PBXGroup; - children = ( - 48DAC6E927C38B10001D45D0 /* Timer.swift */, - 48DAC6EA27C38B10001D45D0 /* Cancellation.swift */, - 48DAC6EB27C38B10001D45D0 /* Deferring.swift */, - 48DAC6EC27C38B10001D45D0 /* FireAndForget.swift */, - 48DAC6ED27C38B10001D45D0 /* Debouncing.swift */, - ); - path = Effects; - sourceTree = ""; - }; - 48DAC70C27C38CCB001D45D0 /* RxComposableArchitectureTests */ = { - isa = PBXGroup; - children = ( - 48DAC72127C38CDE001D45D0 /* DebugTests.swift */, - 48DAC71C27C38CDE001D45D0 /* EffectCancellationTests.swift */, - 48DAC71A27C38CDE001D45D0 /* EffectDebounceTests.swift */, - 48DAC71F27C38CDE001D45D0 /* EffectDeferredTests.swift */, - 48DAC71927C38CDE001D45D0 /* EffectTests.swift */, - 48DAC71827C38CDE001D45D0 /* IdentifiedArrayTests.swift */, - 48DAC71B27C38CDE001D45D0 /* LCRNG.swift */, - 48DAC72427C38CDE001D45D0 /* MemoryManagementTests.swift */, - 48DAC72027C38CDE001D45D0 /* ReducerTests.swift */, - 48DAC71D27C38CDE001D45D0 /* RxComposableArchitectureTests.swift */, - 48DAC72227C38CDE001D45D0 /* SingleSelectionSelectableTypeTests.swift */, - 48DAC72527C38CDE001D45D0 /* SingleSelectionTests.swift */, - 48DAC72327C38CDE001D45D0 /* StoreTests.swift */, - 48DAC71E27C38CDE001D45D0 /* TimerTests.swift */, - 48DAC70D27C38CCB001D45D0 /* RxComposableArchitectureTests.h */, - 48DAC70E27C38CCB001D45D0 /* RxComposableArchitectureTests.docc */, - ); - path = RxComposableArchitectureTests; - sourceTree = ""; - }; - 48DAC73F27C38D97001D45D0 /* TestSupport */ = { - isa = PBXGroup; - children = ( - 48DAC74E27C38DAA001D45D0 /* Annotating.swift */, - 48DAC74C27C38DAA001D45D0 /* Export.swift */, - 48DAC74F27C38DAA001D45D0 /* FailingEffect.swift */, - 48DAC75027C38DAA001D45D0 /* Internal */, - 48DAC74B27C38DAA001D45D0 /* TestScheduler.swift */, - 48DAC75327C38DAA001D45D0 /* TestStore.swift */, - 48DAC74D27C38DAA001D45D0 /* Unwrap.swift */, - 48DAC74027C38D97001D45D0 /* TestSupport.h */, - 48DAC74127C38D97001D45D0 /* TestSupport.docc */, - ); - path = TestSupport; - sourceTree = ""; - }; - 48DAC75027C38DAA001D45D0 /* Internal */ = { - isa = PBXGroup; - children = ( - 48DAC75127C38DAA001D45D0 /* VirtualTimeScheduler.swift */, - 48DAC75227C38DAA001D45D0 /* PriorityQueue.swift */, - ); - path = Internal; - sourceTree = ""; - }; - 48DAC76E27C38EAD001D45D0 /* DiffingTestSupport */ = { - isa = PBXGroup; - children = ( - 48DAC76F27C38EAD001D45D0 /* DiffingTestSupport.h */, - 48DAC77A27C38EC9001D45D0 /* XCTDecode.swift */, - 48DAC77B27C38EC9001D45D0 /* XCTPrettyEqual.swift */, - 48DAC77027C38EAD001D45D0 /* DiffingTestSupport.docc */, - ); - path = DiffingTestSupport; - sourceTree = ""; - }; - C1F4AC2AB7BCDCBDA46BF5ED /* Frameworks */ = { - isa = PBXGroup; - children = ( - 48DAC76127C38E73001D45D0 /* XCTest.framework */, - 99350B5DA80BAC5D18EA3B8D /* Pods_RxComposableArchitecture.framework */, - D35E75E6027094F0586CB60E /* Pods_RxComposableArchitectureExample.framework */, - 165D1F46AB6664F68B7E57BB /* Pods_RxComposableArchitectureTests.framework */, - 74CA63C0EA127FBD45E7C596 /* Pods_TestSupport.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 48DAC60027C3843A001D45D0 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC60A27C3843A001D45D0 /* RxComposableArchitecture.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC61327C38663001D45D0 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC61D27C38663001D45D0 /* DiffingInterface.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC62D27C386C0001D45D0 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC63727C386C0001D45D0 /* DiffingUtility.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC70627C38CCB001D45D0 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC71027C38CCB001D45D0 /* RxComposableArchitectureTests.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC73927C38D97001D45D0 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC74327C38D97001D45D0 /* TestSupport.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC76827C38EAD001D45D0 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC77227C38EAD001D45D0 /* DiffingTestSupport.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 48DAC5E827C381FD001D45D0 /* RxComposableArchitectureExample */ = { - isa = PBXNativeTarget; - buildConfigurationList = 48DAC5FD27C381FF001D45D0 /* Build configuration list for PBXNativeTarget "RxComposableArchitectureExample" */; - buildPhases = ( - 472D0183AEC591766F1B65EC /* [CP] Check Pods Manifest.lock */, - 48DAC5E527C381FD001D45D0 /* Sources */, - 48DAC5E627C381FD001D45D0 /* Frameworks */, - 48DAC5E727C381FD001D45D0 /* Resources */, - 48DAC60F27C3843A001D45D0 /* Embed Frameworks */, - 6855932ADD0A9E42C5273DBC /* [CP] Embed Pods Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - 48DAC60C27C3843A001D45D0 /* PBXTargetDependency */, - 48DAC61F27C38663001D45D0 /* PBXTargetDependency */, - 48DAC63927C386C0001D45D0 /* PBXTargetDependency */, - 48DAC71227C38CCB001D45D0 /* PBXTargetDependency */, - 48DAC74527C38D97001D45D0 /* PBXTargetDependency */, - 48DAC77427C38EAD001D45D0 /* PBXTargetDependency */, - ); - name = RxComposableArchitectureExample; - productName = RxComposableArchitectureExample; - productReference = 48DAC5E927C381FD001D45D0 /* RxComposableArchitectureExample.app */; - productType = "com.apple.product-type.application"; - }; - 48DAC60427C3843A001D45D0 /* RxComposableArchitecture */ = { - isa = PBXNativeTarget; - buildConfigurationList = 48DAC61227C3843A001D45D0 /* Build configuration list for PBXNativeTarget "RxComposableArchitecture" */; - buildPhases = ( - 99804DB9C0CFD37309D68FFE /* [CP] Check Pods Manifest.lock */, - 48DAC60027C3843A001D45D0 /* Headers */, - 48DAC60127C3843A001D45D0 /* Sources */, - 48DAC60227C3843A001D45D0 /* Frameworks */, - 48DAC60327C3843A001D45D0 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 48DAC64627C386E0001D45D0 /* PBXTargetDependency */, - 48DAC64B27C386E3001D45D0 /* PBXTargetDependency */, - ); - name = RxComposableArchitecture; - productName = RxComposableArchitecture; - productReference = 48DAC60527C3843A001D45D0 /* RxComposableArchitecture.framework */; - productType = "com.apple.product-type.framework"; - }; - 48DAC61727C38663001D45D0 /* DiffingInterface */ = { - isa = PBXNativeTarget; - buildConfigurationList = 48DAC62427C38663001D45D0 /* Build configuration list for PBXNativeTarget "DiffingInterface" */; - buildPhases = ( - 48DAC61327C38663001D45D0 /* Headers */, - 48DAC61427C38663001D45D0 /* Sources */, - 48DAC61527C38663001D45D0 /* Frameworks */, - 48DAC61627C38663001D45D0 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = DiffingInterface; - productName = DiffingInterface; - productReference = 48DAC61827C38663001D45D0 /* DiffingInterface.framework */; - productType = "com.apple.product-type.framework"; - }; - 48DAC63127C386C0001D45D0 /* DiffingUtility */ = { - isa = PBXNativeTarget; - buildConfigurationList = 48DAC63C27C386C0001D45D0 /* Build configuration list for PBXNativeTarget "DiffingUtility" */; - buildPhases = ( - 48DAC62D27C386C0001D45D0 /* Headers */, - 48DAC62E27C386C0001D45D0 /* Sources */, - 48DAC62F27C386C0001D45D0 /* Frameworks */, - 48DAC63027C386C0001D45D0 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = DiffingUtility; - productName = DiffingUtility; - productReference = 48DAC63227C386C0001D45D0 /* DiffingUtility.framework */; - productType = "com.apple.product-type.framework"; - }; - 48DAC70A27C38CCB001D45D0 /* RxComposableArchitectureTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 48DAC71727C38CCB001D45D0 /* Build configuration list for PBXNativeTarget "RxComposableArchitectureTests" */; - buildPhases = ( - E1FE4262446CDF5AE252ADE1 /* [CP] Check Pods Manifest.lock */, - 48DAC70627C38CCB001D45D0 /* Headers */, - 48DAC70727C38CCB001D45D0 /* Sources */, - 48DAC70827C38CCB001D45D0 /* Frameworks */, - 48DAC70927C38CCB001D45D0 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 48DAC73727C38CFE001D45D0 /* PBXTargetDependency */, - 48DAC75F27C38DB1001D45D0 /* PBXTargetDependency */, - ); - name = RxComposableArchitectureTests; - productName = RxComposableArchitectureTests; - productReference = 48DAC70B27C38CCB001D45D0 /* RxComposableArchitectureTests.framework */; - productType = "com.apple.product-type.framework"; - }; - 48DAC73D27C38D97001D45D0 /* TestSupport */ = { - isa = PBXNativeTarget; - buildConfigurationList = 48DAC74A27C38D97001D45D0 /* Build configuration list for PBXNativeTarget "TestSupport" */; - buildPhases = ( - 85CEF4D1A5EC629B0C6FB655 /* [CP] Check Pods Manifest.lock */, - 48DAC73927C38D97001D45D0 /* Headers */, - 48DAC73A27C38D97001D45D0 /* Sources */, - 48DAC73B27C38D97001D45D0 /* Frameworks */, - 48DAC73C27C38D97001D45D0 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 48DAC78427C38F26001D45D0 /* PBXTargetDependency */, - 48DAC78927C38F4E001D45D0 /* PBXTargetDependency */, - ); - name = TestSupport; - productName = TestSupport; - productReference = 48DAC73E27C38D97001D45D0 /* TestSupport.framework */; - productType = "com.apple.product-type.framework"; - }; - 48DAC76C27C38EAD001D45D0 /* DiffingTestSupport */ = { - isa = PBXNativeTarget; - buildConfigurationList = 48DAC77727C38EAD001D45D0 /* Build configuration list for PBXNativeTarget "DiffingTestSupport" */; - buildPhases = ( - 48DAC76827C38EAD001D45D0 /* Headers */, - 48DAC76927C38EAD001D45D0 /* Sources */, - 48DAC76A27C38EAD001D45D0 /* Frameworks */, - 48DAC76B27C38EAD001D45D0 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 48DAC78E27C38FAF001D45D0 /* PBXTargetDependency */, - ); - name = DiffingTestSupport; - productName = DiffingTestSupport; - productReference = 48DAC76D27C38EAD001D45D0 /* DiffingTestSupport.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 48DAC5E127C381FD001D45D0 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1320; - LastUpgradeCheck = 1320; - TargetAttributes = { - 48DAC5E827C381FD001D45D0 = { - CreatedOnToolsVersion = 13.2.1; - }; - 48DAC60427C3843A001D45D0 = { - CreatedOnToolsVersion = 13.2.1; - }; - 48DAC61727C38663001D45D0 = { - CreatedOnToolsVersion = 13.2.1; - }; - 48DAC63127C386C0001D45D0 = { - CreatedOnToolsVersion = 13.2.1; - }; - 48DAC70A27C38CCB001D45D0 = { - CreatedOnToolsVersion = 13.2.1; - }; - 48DAC73D27C38D97001D45D0 = { - CreatedOnToolsVersion = 13.2.1; - }; - 48DAC76C27C38EAD001D45D0 = { - CreatedOnToolsVersion = 13.2.1; - }; - }; - }; - buildConfigurationList = 48DAC5E427C381FD001D45D0 /* Build configuration list for PBXProject "RxComposableArchitectureExample" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 48DAC5E027C381FD001D45D0; - productRefGroup = 48DAC5EA27C381FD001D45D0 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 48DAC5E827C381FD001D45D0 /* RxComposableArchitectureExample */, - 48DAC60427C3843A001D45D0 /* RxComposableArchitecture */, - 48DAC61727C38663001D45D0 /* DiffingInterface */, - 48DAC63127C386C0001D45D0 /* DiffingUtility */, - 48DAC70A27C38CCB001D45D0 /* RxComposableArchitectureTests */, - 48DAC73D27C38D97001D45D0 /* TestSupport */, - 48DAC76C27C38EAD001D45D0 /* DiffingTestSupport */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 48DAC5E727C381FD001D45D0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC5F927C381FF001D45D0 /* LaunchScreen.storyboard in Resources */, - 48DAC5F627C381FF001D45D0 /* Assets.xcassets in Resources */, - 48DAC5F427C381FD001D45D0 /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC60327C3843A001D45D0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC61627C38663001D45D0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC63027C386C0001D45D0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC70927C38CCB001D45D0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC73C27C38D97001D45D0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC76B27C38EAD001D45D0 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 472D0183AEC591766F1B65EC /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RxComposableArchitectureExample-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 6855932ADD0A9E42C5273DBC /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-RxComposableArchitectureExample/Pods-RxComposableArchitectureExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-RxComposableArchitectureExample/Pods-RxComposableArchitectureExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RxComposableArchitectureExample/Pods-RxComposableArchitectureExample-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 85CEF4D1A5EC629B0C6FB655 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-TestSupport-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - 99804DB9C0CFD37309D68FFE /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RxComposableArchitecture-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - E1FE4262446CDF5AE252ADE1 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RxComposableArchitectureTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 48DAC5E527C381FD001D45D0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC5F127C381FD001D45D0 /* ViewController.swift in Sources */, - 48DAC5ED27C381FD001D45D0 /* AppDelegate.swift in Sources */, - 48DAC5EF27C381FD001D45D0 /* SceneDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC60127C3843A001D45D0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC6F227C38B10001D45D0 /* Export.swift in Sources */, - 48DAC6F127C38B10001D45D0 /* Effect.swift in Sources */, - 48DAC6F627C38B10001D45D0 /* AnyDisposable.swift in Sources */, - 48DAC6F827C38B10001D45D0 /* Reducer.swift in Sources */, - 48DAC70527C38B10001D45D0 /* OptionalPaths.swift in Sources */, - 48DAC6FB27C38B10001D45D0 /* Bootstrapping.swift in Sources */, - 48DAC6F427C38B10001D45D0 /* IdentifiedArray.swift in Sources */, - 48DAC70127C38B10001D45D0 /* Cancellation.swift in Sources */, - 48DAC70427C38B10001D45D0 /* Debouncing.swift in Sources */, - 48DAC6FC27C38B10001D45D0 /* ReducerInstrumentation.swift in Sources */, - 48DAC6FD27C38B10001D45D0 /* ReducerDebugging.swift in Sources */, - 48DAC70027C38B10001D45D0 /* Timer.swift in Sources */, - 48DAC6EF27C38B10001D45D0 /* IfLet.swift in Sources */, - 48DAC6F527C38B10001D45D0 /* Deprecated.swift in Sources */, - 48DAC6F327C38B10001D45D0 /* Store.swift in Sources */, - 48DAC6FF27C38B10001D45D0 /* UniqueElements.swift in Sources */, - 48DAC6F027C38B10001D45D0 /* Binding.swift in Sources */, - 48DAC60927C3843A001D45D0 /* RxComposableArchitecture.docc in Sources */, - 48DAC6FE27C38B10001D45D0 /* SingleSelection.swift in Sources */, - 48DAC70227C38B10001D45D0 /* Deferring.swift in Sources */, - 48DAC6FA27C38B10001D45D0 /* MockPageTemplate.swift in Sources */, - 48DAC6F927C38B10001D45D0 /* Stateless.swift in Sources */, - 48DAC70327C38B10001D45D0 /* FireAndForget.swift in Sources */, - 48DAC6F727C38B10001D45D0 /* Locking.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC61427C38663001D45D0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC61C27C38663001D45D0 /* DiffingInterface.docc in Sources */, - 48DAC62927C38677001D45D0 /* DiffingInterface+Primitives.swift in Sources */, - 48DAC62B27C38677001D45D0 /* HashDiffable.swift in Sources */, - 48DAC62A27C38677001D45D0 /* HashDiffing.swift in Sources */, - 48DAC62C27C38677001D45D0 /* AnyHashDiffable.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC62E27C386C0001D45D0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC64127C386CF001D45D0 /* Diff.swift in Sources */, - 48DAC64227C386CF001D45D0 /* Debug.swift in Sources */, - 48DAC63627C386C0001D45D0 /* DiffingUtility.docc in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC70727C38CCB001D45D0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC73127C38CDE001D45D0 /* StoreTests.swift in Sources */, - 48DAC73227C38CDE001D45D0 /* MemoryManagementTests.swift in Sources */, - 48DAC73027C38CDE001D45D0 /* SingleSelectionSelectableTypeTests.swift in Sources */, - 48DAC72F27C38CDE001D45D0 /* DebugTests.swift in Sources */, - 48DAC72627C38CDE001D45D0 /* IdentifiedArrayTests.swift in Sources */, - 48DAC72B27C38CDE001D45D0 /* RxComposableArchitectureTests.swift in Sources */, - 48DAC72A27C38CDE001D45D0 /* EffectCancellationTests.swift in Sources */, - 48DAC72827C38CDE001D45D0 /* EffectDebounceTests.swift in Sources */, - 48DAC73327C38CDE001D45D0 /* SingleSelectionTests.swift in Sources */, - 48DAC72927C38CDE001D45D0 /* LCRNG.swift in Sources */, - 48DAC72D27C38CDE001D45D0 /* EffectDeferredTests.swift in Sources */, - 48DAC72E27C38CDE001D45D0 /* ReducerTests.swift in Sources */, - 48DAC70F27C38CCB001D45D0 /* RxComposableArchitectureTests.docc in Sources */, - 48DAC72727C38CDE001D45D0 /* EffectTests.swift in Sources */, - 48DAC72C27C38CDE001D45D0 /* TimerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC73A27C38D97001D45D0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC75727C38DAA001D45D0 /* Annotating.swift in Sources */, - 48DAC75827C38DAA001D45D0 /* FailingEffect.swift in Sources */, - 48DAC75627C38DAA001D45D0 /* Unwrap.swift in Sources */, - 48DAC75927C38DAA001D45D0 /* VirtualTimeScheduler.swift in Sources */, - 48DAC75B27C38DAA001D45D0 /* TestStore.swift in Sources */, - 48DAC74227C38D97001D45D0 /* TestSupport.docc in Sources */, - 48DAC75A27C38DAA001D45D0 /* PriorityQueue.swift in Sources */, - 48DAC75427C38DAA001D45D0 /* TestScheduler.swift in Sources */, - 48DAC75527C38DAA001D45D0 /* Export.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 48DAC76927C38EAD001D45D0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 48DAC77D27C38EC9001D45D0 /* XCTPrettyEqual.swift in Sources */, - 48DAC77127C38EAD001D45D0 /* DiffingTestSupport.docc in Sources */, - 48DAC77C27C38EC9001D45D0 /* XCTDecode.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 48DAC60C27C3843A001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC60427C3843A001D45D0 /* RxComposableArchitecture */; - targetProxy = 48DAC60B27C3843A001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC61F27C38663001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC61727C38663001D45D0 /* DiffingInterface */; - targetProxy = 48DAC61E27C38663001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC63927C386C0001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC63127C386C0001D45D0 /* DiffingUtility */; - targetProxy = 48DAC63827C386C0001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC64627C386E0001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC61727C38663001D45D0 /* DiffingInterface */; - targetProxy = 48DAC64527C386E0001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC64B27C386E3001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC63127C386C0001D45D0 /* DiffingUtility */; - targetProxy = 48DAC64A27C386E3001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC71227C38CCB001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC70A27C38CCB001D45D0 /* RxComposableArchitectureTests */; - targetProxy = 48DAC71127C38CCB001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC73727C38CFE001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC60427C3843A001D45D0 /* RxComposableArchitecture */; - targetProxy = 48DAC73627C38CFE001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC74527C38D97001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC73D27C38D97001D45D0 /* TestSupport */; - targetProxy = 48DAC74427C38D97001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC75F27C38DB1001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC73D27C38D97001D45D0 /* TestSupport */; - targetProxy = 48DAC75E27C38DB1001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC77427C38EAD001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC76C27C38EAD001D45D0 /* DiffingTestSupport */; - targetProxy = 48DAC77327C38EAD001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC78427C38F26001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC60427C3843A001D45D0 /* RxComposableArchitecture */; - targetProxy = 48DAC78327C38F26001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC78927C38F4E001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC76C27C38EAD001D45D0 /* DiffingTestSupport */; - targetProxy = 48DAC78827C38F4E001D45D0 /* PBXContainerItemProxy */; - }; - 48DAC78E27C38FAF001D45D0 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 48DAC63127C386C0001D45D0 /* DiffingUtility */; - targetProxy = 48DAC78D27C38FAF001D45D0 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 48DAC5F227C381FD001D45D0 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 48DAC5F327C381FD001D45D0 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 48DAC5F727C381FF001D45D0 /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 48DAC5F827C381FF001D45D0 /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 48DAC5FB27C381FF001D45D0 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 48DAC5FC27C381FF001D45D0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.2; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 48DAC5FE27C381FF001D45D0 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = BAC720BB1276FD4A58E0995F /* Pods-RxComposableArchitectureExample.debug.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = RxComposableArchitectureExample/Info.plist; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.RxComposableArchitectureExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 48DAC5FF27C381FF001D45D0 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 1CDF88EE99C341A71671F250 /* Pods-RxComposableArchitectureExample.release.xcconfig */; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = RxComposableArchitectureExample/Info.plist; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.RxComposableArchitectureExample; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 48DAC61027C3843A001D45D0 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 5D9BE14880FA5F45EA62F238 /* Pods-RxComposableArchitecture.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.RxComposableArchitecture; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 48DAC61127C3843A001D45D0 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 593494F11362312C1943BCAA /* Pods-RxComposableArchitecture.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.RxComposableArchitecture; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 48DAC62227C38663001D45D0 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.DiffingInterface; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 48DAC62327C38663001D45D0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.DiffingInterface; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 48DAC63D27C386C0001D45D0 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.DiffingUtility; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 48DAC63E27C386C0001D45D0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.DiffingUtility; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 48DAC71527C38CCB001D45D0 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 6E5147DCC852345C4F52D0F9 /* Pods-RxComposableArchitectureTests.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.RxComposableArchitectureTests; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 48DAC71627C38CCB001D45D0 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = CE869D92EC2BD944F657ABC9 /* Pods-RxComposableArchitectureTests.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.RxComposableArchitectureTests; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 48DAC74827C38D97001D45D0 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = B711F2F591349482FACDBE8C /* Pods-TestSupport.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.TestSupport; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 48DAC74927C38D97001D45D0 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = F1387F3EA85FC5DB51814A48 /* Pods-TestSupport.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.TestSupport; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - 48DAC77827C38EAD001D45D0 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.DiffingTestSupport; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - 48DAC77927C38EAD001D45D0 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.tokopedia.DiffingTestSupport; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 48DAC5E427C381FD001D45D0 /* Build configuration list for PBXProject "RxComposableArchitectureExample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 48DAC5FB27C381FF001D45D0 /* Debug */, - 48DAC5FC27C381FF001D45D0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 48DAC5FD27C381FF001D45D0 /* Build configuration list for PBXNativeTarget "RxComposableArchitectureExample" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 48DAC5FE27C381FF001D45D0 /* Debug */, - 48DAC5FF27C381FF001D45D0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 48DAC61227C3843A001D45D0 /* Build configuration list for PBXNativeTarget "RxComposableArchitecture" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 48DAC61027C3843A001D45D0 /* Debug */, - 48DAC61127C3843A001D45D0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 48DAC62427C38663001D45D0 /* Build configuration list for PBXNativeTarget "DiffingInterface" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 48DAC62227C38663001D45D0 /* Debug */, - 48DAC62327C38663001D45D0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 48DAC63C27C386C0001D45D0 /* Build configuration list for PBXNativeTarget "DiffingUtility" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 48DAC63D27C386C0001D45D0 /* Debug */, - 48DAC63E27C386C0001D45D0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 48DAC71727C38CCB001D45D0 /* Build configuration list for PBXNativeTarget "RxComposableArchitectureTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 48DAC71527C38CCB001D45D0 /* Debug */, - 48DAC71627C38CCB001D45D0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 48DAC74A27C38D97001D45D0 /* Build configuration list for PBXNativeTarget "TestSupport" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 48DAC74827C38D97001D45D0 /* Debug */, - 48DAC74927C38D97001D45D0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 48DAC77727C38EAD001D45D0 /* Build configuration list for PBXNativeTarget "DiffingTestSupport" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 48DAC77827C38EAD001D45D0 /* Debug */, - 48DAC77927C38EAD001D45D0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 48DAC5E127C381FD001D45D0 /* Project object */; -} diff --git a/RxComposableArchitectureExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/RxComposableArchitectureExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/RxComposableArchitectureExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/RxComposableArchitectureExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/RxComposableArchitectureExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/RxComposableArchitectureExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/RxComposableArchitectureExample.xcodeproj/xcshareddata/xcschemes/DiffingUtility.xcscheme b/RxComposableArchitectureExample.xcodeproj/xcshareddata/xcschemes/DiffingUtility.xcscheme deleted file mode 100644 index f5faecb..0000000 --- a/RxComposableArchitectureExample.xcodeproj/xcshareddata/xcschemes/DiffingUtility.xcscheme +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RxComposableArchitectureExample.xcodeproj/xcuserdata/andreyyoshuamanik.xcuserdatad/xcschemes/xcschememanagement.plist b/RxComposableArchitectureExample.xcodeproj/xcuserdata/andreyyoshuamanik.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 6b68456..0000000 --- a/RxComposableArchitectureExample.xcodeproj/xcuserdata/andreyyoshuamanik.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,52 +0,0 @@ - - - - - SchemeUserState - - DiffingInterface.xcscheme_^#shared#^_ - - orderHint - 14 - - DiffingTestSupport.xcscheme_^#shared#^_ - - orderHint - 9 - - DiffingUtility.xcscheme_^#shared#^_ - - orderHint - 0 - - RxComposableArchitecture.xcscheme_^#shared#^_ - - orderHint - 11 - - RxComposableArchitectureExample.xcscheme_^#shared#^_ - - orderHint - 12 - - RxComposableArchitectureTests.xcscheme_^#shared#^_ - - orderHint - 13 - - TestSupport.xcscheme_^#shared#^_ - - orderHint - 10 - - - SuppressBuildableAutocreation - - 48DAC63127C386C0001D45D0 - - primary - - - - - diff --git a/RxComposableArchitectureExample.xcworkspace/contents.xcworkspacedata b/RxComposableArchitectureExample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 732b828..0000000 --- a/RxComposableArchitectureExample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/RxComposableArchitectureExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/RxComposableArchitectureExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/RxComposableArchitectureExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/RxComposableArchitectureExample/AppDelegate.swift b/RxComposableArchitectureExample/AppDelegate.swift deleted file mode 100644 index 40569f5..0000000 --- a/RxComposableArchitectureExample/AppDelegate.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// AppDelegate.swift -// RxComposableArchitectureExample -// -// Created by Andrey Yoshua Manik on 21/02/22. -// - -import UIKit - -@main -class AppDelegate: UIResponder, UIApplicationDelegate { - - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - return true - } - - // MARK: UISceneSession Lifecycle - - func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { - // Called when a new scene session is being created. - // Use this method to select a configuration to create the new scene with. - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) - } - - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { - // Called when the user discards a scene session. - // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. - // Use this method to release any resources that were specific to the discarded scenes, as they will not return. - } - - -} - diff --git a/RxComposableArchitectureExample/Assets.xcassets/AccentColor.colorset/Contents.json b/RxComposableArchitectureExample/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/RxComposableArchitectureExample/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/RxComposableArchitectureExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/RxComposableArchitectureExample/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 9221b9b..0000000 --- a/RxComposableArchitectureExample/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "images" : [ - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/RxComposableArchitectureExample/Assets.xcassets/Contents.json b/RxComposableArchitectureExample/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/RxComposableArchitectureExample/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/RxComposableArchitectureExample/Base.lproj/LaunchScreen.storyboard b/RxComposableArchitectureExample/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 865e932..0000000 --- a/RxComposableArchitectureExample/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RxComposableArchitectureExample/Base.lproj/Main.storyboard b/RxComposableArchitectureExample/Base.lproj/Main.storyboard deleted file mode 100644 index 25a7638..0000000 --- a/RxComposableArchitectureExample/Base.lproj/Main.storyboard +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/RxComposableArchitectureExample/Info.plist b/RxComposableArchitectureExample/Info.plist deleted file mode 100644 index dd3c9af..0000000 --- a/RxComposableArchitectureExample/Info.plist +++ /dev/null @@ -1,25 +0,0 @@ - - - - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneConfigurationName - Default Configuration - UISceneDelegateClassName - $(PRODUCT_MODULE_NAME).SceneDelegate - UISceneStoryboardFile - Main - - - - - - diff --git a/RxComposableArchitectureExample/SceneDelegate.swift b/RxComposableArchitectureExample/SceneDelegate.swift deleted file mode 100644 index e445b2d..0000000 --- a/RxComposableArchitectureExample/SceneDelegate.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// SceneDelegate.swift -// RxComposableArchitectureExample -// -// Created by Andrey Yoshua Manik on 21/02/22. -// - -import UIKit - -class SceneDelegate: UIResponder, UIWindowSceneDelegate { - - var window: UIWindow? - - - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { - // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. - // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. - // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). - guard let _ = (scene as? UIWindowScene) else { return } - } - - func sceneDidDisconnect(_ scene: UIScene) { - // Called as the scene is being released by the system. - // This occurs shortly after the scene enters the background, or when its session is discarded. - // Release any resources associated with this scene that can be re-created the next time the scene connects. - // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). - } - - func sceneDidBecomeActive(_ scene: UIScene) { - // Called when the scene has moved from an inactive state to an active state. - // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. - } - - func sceneWillResignActive(_ scene: UIScene) { - // Called when the scene will move from an active state to an inactive state. - // This may occur due to temporary interruptions (ex. an incoming phone call). - } - - func sceneWillEnterForeground(_ scene: UIScene) { - // Called as the scene transitions from the background to the foreground. - // Use this method to undo the changes made on entering the background. - } - - func sceneDidEnterBackground(_ scene: UIScene) { - // Called as the scene transitions from the foreground to the background. - // Use this method to save data, release shared resources, and store enough scene-specific state information - // to restore the scene back to its current state. - } - - -} - diff --git a/RxComposableArchitectureExample/ViewController.swift b/RxComposableArchitectureExample/ViewController.swift deleted file mode 100644 index 5d057aa..0000000 --- a/RxComposableArchitectureExample/ViewController.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// ViewController.swift -// RxComposableArchitectureExample -// -// Created by Andrey Yoshua Manik on 21/02/22. -// - -import UIKit - -class ViewController: UIViewController { - - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - } - - -} - diff --git a/RxComposableArchitectureTests/DebugTests.swift b/RxComposableArchitectureTests/DebugTests.swift deleted file mode 100644 index 972e459..0000000 --- a/RxComposableArchitectureTests/DebugTests.swift +++ /dev/null @@ -1,748 +0,0 @@ -import DiffingUtility -import RxSwift -import XCTest - -@testable import RxComposableArchitecture - -internal final class DebugTests: XCTestCase { - internal func testCollection() { - XCTAssertEqual( - debugOutput([1, 2, 3]), - """ - [ - 1, - 2, - 3, - ] - """ - ) - - XCTAssertEqual( - debugOutput([[1, 2, 3], [4, 5, 6]]), - """ - [ - [ - 1, - 2, - 3, - ], - [ - 4, - 5, - 6, - ], - ] - """ - ) - } - - internal func testSet() { - XCTAssertEqual( - debugOutput(Set([1, 2, 3])), - """ - Set([ - 1, - 2, - 3, - ]) - """ - ) - - XCTAssertEqual( - debugOutput( - Set([ - Set([1, 2, 3]), - Set([4, 5, 6]) - ]) - ), - """ - Set([ - Set([ - 1, - 2, - 3, - ]), - Set([ - 4, - 5, - 6, - ]), - ]) - """ - ) - } - - internal func testDictionary() { - XCTAssertEqual( - debugOutput(["Blob": 1, "Blob Jr.": 2, "Blob Sr.": 3]), - """ - [ - "Blob Jr.": 2, - "Blob Sr.": 3, - "Blob": 1, - ] - """ - ) - - XCTAssertEqual( - debugOutput([1: ["Blob": 1], 2: ["Blob Jr.": 2], 3: ["Blob Sr.": 3]]), - """ - [ - 1: [ - "Blob": 1, - ], - 2: [ - "Blob Jr.": 2, - ], - 3: [ - "Blob Sr.": 3, - ], - ] - """ - ) - } - - internal func testTuple() { - XCTAssertEqual( - debugOutput((1, "2", 3.0)), - """ - ( - 1, - "2", - 3.0 - ) - """ - ) - - XCTAssertEqual( - debugOutput(((1, "2", 3.0), ("4" as Character, "5" as UnicodeScalar))), - """ - ( - ( - 1, - "2", - 3.0 - ), - ( - "4", - "5" - ) - ) - """ - ) - - XCTAssertEqual( - debugOutput(()), - """ - () - """ - ) - } - - internal func testStruct() { - struct User { - var id: Int - var name: String - } - - XCTAssertEqual( - debugOutput( - User(id: 1, name: "Blob") - ), - """ - User( - id: 1, - name: "Blob" - ) - """ - ) - } - - internal func testClass() { - class User { - var id = 1 - var name = "Blob" - } - - XCTAssertEqual( - debugOutput(User()), - """ - User( - id: 1, - name: "Blob" - ) - """ - ) - } - - internal func testEnum() { - enum Enum { - case caseWithNoAssociatedValues - case caseWithOneAssociatedValue(Int) - case caseWithOneLabeledAssociatedValue(one: Int) - case caseWithTwoLabeledAssociatedValues(one: Int, two: String) - case caseWithTuple((one: Int, two: String)) - } - - XCTAssertEqual( - debugOutput(Enum.caseWithNoAssociatedValues), - """ - Enum.caseWithNoAssociatedValues - """ - ) - XCTAssertEqual( - debugOutput(Enum.caseWithOneAssociatedValue(1)), - """ - Enum.caseWithOneAssociatedValue( - 1 - ) - """ - ) - XCTAssertEqual( - debugOutput(Enum.caseWithOneLabeledAssociatedValue(one: 1)), - """ - Enum.caseWithOneLabeledAssociatedValue( - one: 1 - ) - """ - ) - XCTAssertEqual( - debugOutput(Enum.caseWithTwoLabeledAssociatedValues(one: 1, two: "Blob")), - """ - Enum.caseWithTwoLabeledAssociatedValues( - one: 1, - two: "Blob" - ) - """ - ) - // NB: Fails due to https://bugs.swift.org/browse/SR-12409 - // XCTAssertEqual( - // debugOutput(Enum.caseWithTuple((one: 1, two: "Blob"))), - // """ - // Enum.caseWithTuple( - // ( - // one: 1, - // two: "Blob" - // ) - // ) - // """ - // ) - } - - internal func testObject() { - XCTAssertEqual( - debugOutput(NSObject()), - """ - NSObject() - """ - ) - } - - internal func testDebugOutputConvertible() { - XCTAssertEqual( - debugOutput(Date(timeIntervalSinceReferenceDate: 0)), - "2001-01-01T00:00:00Z" - ) - XCTAssertEqual( - debugOutput(URL(string: "https://www.pointfree.co")!), - "https://www.pointfree.co" - ) - XCTAssertEqual( - debugOutput(DispatchQueue.main), - "DispatchQueue.main" - ) - XCTAssertEqual( - debugOutput(DispatchQueue.global()), - "DispatchQueue.global()" - ) - XCTAssertEqual( - debugOutput(DispatchQueue.global(qos: .background)), - "DispatchQueue.global(qos: .background)" - ) - XCTAssertEqual( - debugOutput(DispatchQueue(label: "co.pointfree", qos: .background)), - #"DispatchQueue(label: "co.pointfree", qos: .background)"# - ) - XCTAssertEqual( - debugOutput(OperationQueue.main), - "OperationQueue.main" - ) - XCTAssertEqual( - debugOutput(OperationQueue()), - "OperationQueue()" - ) - XCTAssertEqual( - debugOutput(RunLoop.main), - "RunLoop.main" - ) - } - - #if compiler(<5.5) - internal func testNestedDump() { - struct User { - var id: UUID - var name: String - var createdAt: Date - var favoritePrimes: [Int] - var friends: [User] - } - - enum AppState { - case loggedOut(login: String, password: String) - case loggedIn(User) - } - - XCTAssertEqual( - debugOutput( - AppState.loggedIn( - User( - id: UUID(uuidString: "DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF")!, - name: "Blob", - createdAt: Date(timeIntervalSinceReferenceDate: 0), - favoritePrimes: [7, 11], - friends: [ - User( - id: UUID(uuidString: "CAFEBEEF-CAFE-BEEF-CAFE-BEEFCAFEBEEF")!, - name: "Blob Jr.", - createdAt: Date(timeIntervalSinceReferenceDate: 60 * 60 * 24 * 365), - favoritePrimes: [2, 3, 5], - friends: [] - ), - User( - id: UUID(uuidString: "D00DBEEF-D00D-BEEF-D00D-BEEFD00DBEEF")!, - name: "Blob Sr.", - createdAt: Date(timeIntervalSinceReferenceDate: 60 * 60 * 48 * 365), - favoritePrimes: [23], - friends: [] - ) - ] - ) - ) - ), - """ - AppState.loggedIn( - User( - id: DEADBEEF-DEAD-BEEF-DEAD-BEEFDEADBEEF, - name: "Blob", - createdAt: 2001-01-01T00:00:00Z, - favoritePrimes: [ - 7, - 11, - ], - friends: [ - User( - id: CAFEBEEF-CAFE-BEEF-CAFE-BEEFCAFEBEEF, - name: "Blob Jr.", - createdAt: 2002-01-01T00:00:00Z, - favoritePrimes: [ - 2, - 3, - 5, - ], - friends: [ - ] - ), - User( - id: D00DBEEF-D00D-BEEF-D00D-BEEFD00DBEEF, - name: "Blob Sr.", - createdAt: 2003-01-01T00:00:00Z, - favoritePrimes: [ - 23, - ], - friends: [ - ] - ), - ] - ) - ) - """ - ) - } - #endif - - internal func testRecursiveOutput() { - class Foo { - var foo: Foo? - } - let foo = Foo() - foo.foo = foo - XCTAssertEqual( - debugOutput(foo), - """ - Foo( - foo: Foo(↩ī¸Ž) - ) - """ - ) - } - - internal func testStructDiff() { - let before = """ - AppState( - login: LoginState( - alertData: nil, - email: "blob@pointfree.co", - isFormValid: true, - isLoginRequestInFlight: true, - password: "password", - twoFactor: nil - ), - newGame: nil - ) - """ - - let after = """ - AppState( - login: nil, - newGame: NewGameState( - game: nil, - oPlayerName: "", - xPlayerName: "" - ) - ) - """ - - XCTAssertEqual( - debugDiff(before, after, printer: { $0 })!, - """ -   AppState( - − login: LoginState( - − alertData: nil, - − email: "blob@pointfree.co", - − isFormValid: true, - − isLoginRequestInFlight: true, - − password: "password", - − twoFactor: nil - − ), - − newGame: nil - + login: nil, - + newGame: NewGameState( - + game: nil, - + oPlayerName: "", - + xPlayerName: "" - + ) -   ) - """ - ) - } - - internal func testArrayDiff() { - let before = """ - [ - Todo( - isComplete: true, - description: "Milk", - id: 00000000-0000-0000-0000-000000000000 - ), - Todo( - isComplete: false, - description: "Eggs", - id: 00000000-0000-0000-0000-000000000001 - ), - ] - """ - - let after = """ - [ - Todo( - isComplete: false, - description: "Eggs", - id: 00000000-0000-0000-0000-000000000001 - ), - Todo( - isComplete: true, - description: "Milk", - id: 00000000-0000-0000-0000-000000000000 - ), - ] - """ - - XCTAssertEqual( - debugDiff(before, after, printer: { $0 })!, - """ -   [ - − Todo( - − isComplete: true, - − description: "Milk", - − id: 00000000-0000-0000-0000-000000000000 - − ), -   Todo( -   isComplete: false, -   description: "Eggs", -   id: 00000000-0000-0000-0000-000000000001 -   ), - + Todo( - + isComplete: true, - + description: "Milk", - + id: 00000000-0000-0000-0000-000000000000 - + ), -   ] - """ - ) - } - - internal func testComplexDiff() { - let before = """ - AppState( - login: LoginState( - alertData: nil, - email: "blob@pointfree.co", - isFormValid: true, - isLoginRequestInFlight: true, - password: "password", - twoFactor: nil - ), - newGame: nil, - todos: [ - Todo( - isComplete: true, - description: "Milk", - id: 00000000-0000-0000-0000-000000000000 - ), - Todo( - isComplete: false, - description: "Eggs", - id: 00000000-0000-0000-0000-000000000001 - ), - ] - ) - """ - - let after = """ - AppState( - login: nil, - newGame: NewGameState( - game: nil, - oPlayerName: "", - xPlayerName: "" - ), - todos: [ - Todo( - isComplete: false, - description: "Eggs", - id: 00000000-0000-0000-0000-000000000001 - ), - Todo( - isComplete: true, - description: "Milk", - id: 00000000-0000-0000-0000-000000000000 - ), - ] - ) - """ - - XCTAssertEqual( - debugDiff(before, after, printer: { $0 })!, - """ -   AppState( - − login: LoginState( - − alertData: nil, - − email: "blob@pointfree.co", - − isFormValid: true, - − isLoginRequestInFlight: true, - − password: "password", - − twoFactor: nil - + login: nil, - + newGame: NewGameState( - + game: nil, - + oPlayerName: "", - + xPlayerName: "" -   ), - − newGame: nil, -   todos: [ - − Todo( - − isComplete: true, - − description: "Milk", - − id: 00000000-0000-0000-0000-000000000000 - − ), -   Todo( -   isComplete: false, -   description: "Eggs", -   id: 00000000-0000-0000-0000-000000000001 -   ), - + Todo( - + isComplete: true, - + description: "Milk", - + id: 00000000-0000-0000-0000-000000000000 - + ), -   ] -   ) - """ - ) - } - - internal func testComplexDiff2() { - let before = """ - AppState( - login: LoginState( - alertData: nil, - email: "a", - isFormValid: true, - isLoginRequestInFlight: true, - password: "a", - twoFactor: nil - ), - newGame: nil - ) - """ - - let after = """ - AppState( - login: LoginState( - alertData: AlertData( - title: "The operation couldn’t be completed. (AuthenticationClient.AuthenticationError error 0.)" - ), - email: "a", - isFormValid: true, - isLoginRequestInFlight: false, - password: "a", - twoFactor: nil - ), - newGame: nil - ) - """ - - XCTAssertEqual( - debugDiff(before, after, printer: { $0 })!, - """ -   AppState( -   login: LoginState( - − alertData: nil, - + alertData: AlertData( - + title: "The operation couldn’t be completed. (AuthenticationClient.AuthenticationError error 0.)" - + ), -   email: "a", -   isFormValid: true, - − isLoginRequestInFlight: true, - + isLoginRequestInFlight: false, -   password: "a", -   twoFactor: nil -   ), -   newGame: nil -   ) - """ - ) - } - - internal func testComplexDiff3() { - let before = """ - B - B - """ - - let after = """ - A - B - """ - - XCTAssertEqual( - debugDiff(before, after, printer: { $0 })!, - """ - − B - + A -   B - """ - ) - } - - internal func testDebugCaseOutput() { - enum Action { - case action1(Bool, label: String) - case action2(Bool, Int, String) - case screenA(ScreenA) - - enum ScreenA { - case row(index: Int, action: RowAction) - - enum RowAction { - case tapped - case textChanged(query: String) - } - } - } - - XCTAssertEqual( - debugCaseOutput(Action.action1(true, label: "Blob")), - "Action.action1(_:, label:)" - ) - - XCTAssertEqual( - debugCaseOutput(Action.action2(true, 1, "Blob")), - "Action.action2(_:, _:, _:)" - ) - - XCTAssertEqual( - debugCaseOutput(Action.screenA(.row(index: 1, action: .tapped))), - "Action.screenA(.row(index:, action: .tapped))" - ) - - XCTAssertEqual( - debugCaseOutput(Action.screenA(.row(index: 1, action: .textChanged(query: "Hi")))), - "Action.screenA(.row(index:, action: .textChanged(query:)))" - ) - } - - internal func testDebugOutput() { - enum Action { - case action1(Bool, label: String) - case action2(Bool, Int, String) - case screenA(ScreenA) - - enum ScreenA { - case row(index: Int, action: RowAction) - - enum RowAction { - case tapped - case textChanged(query: String) - } - } - } - - XCTAssertEqual( - debugOutput(Action.action1(true, label: "Blob")), - """ - Action.action1( - true, - label: "Blob" - ) - """ - ) - - XCTAssertEqual( - debugOutput(Action.action2(true, 1, "Blob")), - """ - Action.action2( - true, - 1, - "Blob" - ) - """ - ) - - XCTAssertEqual( - debugOutput(Action.screenA(.row(index: 1, action: .tapped))), - """ - Action.screenA( - ScreenA.row( - index: 1, - action: RowAction.tapped - ) - ) - """ - ) - - XCTAssertEqual( - debugOutput(Action.screenA(.row(index: 1, action: .textChanged(query: "Hi")))), - """ - Action.screenA( - ScreenA.row( - index: 1, - action: RowAction.textChanged( - query: "Hi" - ) - ) - ) - """ - ) - } -} diff --git a/RxComposableArchitectureTests/EffectCancellationTests.swift b/RxComposableArchitectureTests/EffectCancellationTests.swift deleted file mode 100644 index 561f659..0000000 --- a/RxComposableArchitectureTests/EffectCancellationTests.swift +++ /dev/null @@ -1,257 +0,0 @@ -import RxSwift -import TestSupport -import XCTest - -@testable import RxComposableArchitecture - -internal final class EffectCancellationTests: XCTestCase { - private var disposeBag = DisposeBag() - - override internal func tearDown() { - super.tearDown() - disposeBag = DisposeBag() - } - - internal func testCancellation() { - struct CancelToken: Hashable {} - var values: [Int] = [] - - let subject = PublishSubject() - let effect = Effect(subject) - .cancellable(id: CancelToken()) - - effect.subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, []) - subject.onNext(1) - XCTAssertEqual(values, [1]) - subject.onNext(2) - XCTAssertEqual(values, [1, 2]) - - Effect.cancel(id: CancelToken()) - .subscribe() - .disposed(by: disposeBag) - - subject.onNext(3) - XCTAssertEqual(values, [1, 2]) - } - - internal func testCancelInFlight() { - struct CancelToken: Hashable {} - var values: [Int] = [] - - let subject = PublishSubject() - Effect(subject) - .cancellable(id: CancelToken(), cancelInFlight: true) - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, []) - subject.onNext(1) - XCTAssertEqual(values, [1]) - subject.onNext(2) - XCTAssertEqual(values, [1, 2]) - - Effect(subject) - .cancellable(id: CancelToken(), cancelInFlight: true) - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - subject.onNext(3) - XCTAssertEqual(values, [1, 2, 3]) - subject.onNext(4) - XCTAssertEqual(values, [1, 2, 3, 4]) - } - - internal func testCancellationAfterDelay() { - struct CancelToken: Hashable {} - var value: Int? - - Observable.just(1) - .delay(.milliseconds(500), scheduler: MainScheduler.instance) - .eraseToEffect() - .cancellable(id: CancelToken()) - .subscribe(onNext: { value = $0 }) - .disposed(by: disposeBag) - - XCTAssertEqual(value, nil) - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { - _ = Effect.cancel(id: CancelToken()) - .subscribe() - .disposed(by: self.disposeBag) - } - - _ = XCTWaiter.wait(for: [expectation(description: "")], timeout: 0.1) - - XCTAssertEqual(value, nil) - } - - internal func testCancellationAfterDelay_WithTestScheduler() { - struct CancelToken: Hashable {} - - let scheduler = TestScheduler(initialClock: 0) - - var value: Int? - - Observable.just(1) - .delay(.seconds(2), scheduler: scheduler) - .eraseToEffect() - .cancellable(id: CancelToken()) - .subscribe(onNext: { value = $0 }) - .disposed(by: disposeBag) - - XCTAssertEqual(value, nil) - - scheduler.advance(by: .seconds(1)) - - Effect.cancel(id: CancelToken()) - .subscribe() - .disposed(by: disposeBag) - - scheduler.advance(to: 1000) - - XCTAssertEqual(value, nil) - } - - internal func testCancellablesCleanUp_OnComplete() { - Observable.just(1) - .eraseToEffect() - .cancellable(id: 1) - .subscribe() - .disposed(by: disposeBag) - - XCTAssertTrue(cancellationCancellables.isEmpty) - } - - internal func testCancellablesCleanUp_OnCancel() { - let scheduler = TestScheduler(initialClock: 0) - - Observable.just(1) - .delay(.seconds(1), scheduler: scheduler) - .eraseToEffect() - .cancellable(id: 1) - .subscribe() - .disposed(by: disposeBag) - - Effect.cancel(id: 1) - .subscribe() - .disposed(by: disposeBag) - - XCTAssertTrue(cancellationCancellables.isEmpty) - } - - internal func testDoubleCancellation() { - struct CancelToken: Hashable {} - var values: [Int] = [] - - let subject = PublishSubject() - let effect = Effect(subject) - .cancellable(id: CancelToken()) - .cancellable(id: CancelToken()) - - effect - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, []) - subject.onNext(1) - XCTAssertEqual(values, [1]) - - _ = Effect.cancel(id: CancelToken()) - .subscribe() - .disposed(by: disposeBag) - - subject.onNext(2) - XCTAssertEqual(values, [1]) - } - - internal func testCompleteBeforeCancellation() { - struct CancelToken: Hashable {} - var values: [Int] = [] - - let subject = PublishSubject() - let effect = Effect(subject) - .cancellable(id: CancelToken()) - - effect - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - subject.onNext(1) - XCTAssertEqual(values, [1]) - - subject.onCompleted() - XCTAssertEqual(values, [1]) - - Effect.cancel(id: CancelToken()) - .subscribe() - .disposed(by: disposeBag) - - XCTAssertEqual(values, [1]) - } - - internal func testNestedCancels() { - var effect = Observable.never() - .eraseToEffect() - .cancellable(id: 1) - - for _ in 1 ... .random(in: 1 ... 1000) { - effect = effect.cancellable(id: 1) - } - - effect - .subscribe(onNext: { _ in }) - .disposed(by: disposeBag) - - disposeBag = DisposeBag() - - XCTAssertTrue(cancellationCancellables.isEmpty) - } - - internal func testSharedId() { - let scheduler = TestScheduler(initialClock: 0) - - let effect1 = Observable.just(1) - .delay(.seconds(1), scheduler: scheduler) - .eraseToEffect() - .cancellable(id: "id") - - let effect2 = Observable.just(2) - .delay(.seconds(2), scheduler: scheduler) - .eraseToEffect() - .cancellable(id: "id") - - var expectedOutput: [Int] = [] - effect1 - .subscribe(onNext: { expectedOutput.append($0) }) - .disposed(by: disposeBag) - effect2 - .subscribe(onNext: { expectedOutput.append($0) }) - .disposed(by: disposeBag) - - XCTAssertEqual(expectedOutput, []) - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(expectedOutput, [1]) - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(expectedOutput, [1, 2]) - } - - internal func testImmediateCancellation() { - let scheduler = TestScheduler(initialClock: 0) - - var expectedOutput: [Int] = [] - // Don't hold onto cancellable so that it is deallocated immediately. - let d = Observable.deferred { .just(1) } - .delay(.seconds(1), scheduler: scheduler) - .eraseToEffect() - .cancellable(id: "id") - .subscribe(onNext: { expectedOutput.append($0) }) - d.dispose() - - XCTAssertEqual(expectedOutput, []) - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(expectedOutput, []) - } -} diff --git a/RxComposableArchitectureTests/EffectDebounceTests.swift b/RxComposableArchitectureTests/EffectDebounceTests.swift deleted file mode 100644 index 69069ae..0000000 --- a/RxComposableArchitectureTests/EffectDebounceTests.swift +++ /dev/null @@ -1,88 +0,0 @@ -import Foundation -import RxSwift -import TestSupport -import XCTest - -internal final class EffectDebounceTests: XCTestCase { - private let disposeBag = DisposeBag() - - internal func testDebounce() { - let scheduler = TestScheduler(initialClock: 0) - var values: [Int] = [] - - func runDebouncedEffect(value: Int) { - struct CancelToken: Hashable {} - Observable.just(value) - .eraseToEffect() - .debounce(id: CancelToken(), for: .seconds(1), scheduler: scheduler) - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - } - - runDebouncedEffect(value: 1) - - // Nothing emits right away. - XCTAssertEqual(values, []) - - // Waiting half the time also emits nothing - scheduler.advance(by: .milliseconds(500)) - XCTAssertEqual(values, []) - - // Run another debounced effect. - runDebouncedEffect(value: 2) - - // Waiting half the time emits nothing because the first debounced effect has been canceled. - scheduler.advance(by: .milliseconds(500)) - - XCTAssertEqual(values, []) - - // Run another debounced effect. - runDebouncedEffect(value: 3) - - // Waiting half the time emits nothing because the second debounced effect has been canceled. - scheduler.advance(by: .milliseconds(500)) - XCTAssertEqual(values, []) - - // Waiting the rest of the time emits the final effect value. - scheduler.advance(by: .milliseconds(500)) - XCTAssertEqual(values, [3]) - - // Running out the scheduler - scheduler.run() - XCTAssertEqual(values, [3]) - } - - internal func testDebounceIsLazy() { - let scheduler = TestScheduler(initialClock: 0) - var values: [Int] = [] - var effectRuns = 0 - - func runDebouncedEffect(value: Int) { - struct CancelToken: Hashable {} - - Observable.deferred { () -> Observable in - effectRuns += 1 - return .just(value) - } - .eraseToEffect() - .debounce(id: CancelToken(), for: .seconds(1), scheduler: scheduler) - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - } - - runDebouncedEffect(value: 1) - - XCTAssertEqual(values, []) - XCTAssertEqual(effectRuns, 0) - - scheduler.advance(by: .milliseconds(500)) - - XCTAssertEqual(values, []) - XCTAssertEqual(effectRuns, 0) - - scheduler.advance(by: .milliseconds(500)) - - XCTAssertEqual(values, [1]) - XCTAssertEqual(effectRuns, 1) - } -} diff --git a/RxComposableArchitectureTests/EffectDeferredTests.swift b/RxComposableArchitectureTests/EffectDeferredTests.swift deleted file mode 100644 index d9ba6f9..0000000 --- a/RxComposableArchitectureTests/EffectDeferredTests.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// EffectDeferredTests.swift -// RxComposableArchitectureTests -// -// Created by Wendy Liga on 18/06/21. -// - -import Foundation -import RxSwift -import TestSupport -import XCTest - -internal final class EffectDeferredTests: XCTestCase { - private let disposeBag = DisposeBag() - - internal func testDeferred() { - let scheduler = TestScheduler(initialClock: 0) - var values: [Int] = [] - - func runDeferredEffect(value: Int) { - Observable.just(value) - .eraseToEffect() - .deferred(for: .seconds(1), scheduler: scheduler) - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - } - - runDeferredEffect(value: 1) - - // Nothing emits right away. - XCTAssertEqual(values, []) - - // Waiting half the time also emits nothing - scheduler.advance(by: .milliseconds(500)) - XCTAssertEqual(values, []) - - // Run another deferred effect. - runDeferredEffect(value: 2) - - // Waiting half the time emits first deferred effect received. - scheduler.advance(by: .milliseconds(500)) - XCTAssertEqual(values, [1]) - - // Run another deferred effect. - runDeferredEffect(value: 3) - - // Waiting half the time emits second deferred effect received. - scheduler.advance(by: .milliseconds(500)) - XCTAssertEqual(values, [1, 2]) - - // Waiting the rest of the time emits the final effect value. - scheduler.advance(by: .milliseconds(500)) - XCTAssertEqual(values, [1, 2, 3]) - - // Running out the scheduler - scheduler.run() - XCTAssertEqual(values, [1, 2, 3]) - } - - internal func testDeferredIsLazy() { - let scheduler = TestScheduler(initialClock: 0) - var values: [Int] = [] - var effectRuns = 0 - - func runDeferredEffect(value: Int) { - Observable.deferred { () -> Observable in - effectRuns += 1 - return .just(value) - } - .eraseToEffect() - .deferred(for: .seconds(1), scheduler: scheduler) - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - } - - runDeferredEffect(value: 1) - - XCTAssertEqual(values, []) - XCTAssertEqual(effectRuns, 0) - - scheduler.advance(by: .milliseconds(500)) - - XCTAssertEqual(values, []) - XCTAssertEqual(effectRuns, 0) - - scheduler.advance(by: .milliseconds(500)) - - XCTAssertEqual(values, [1]) - XCTAssertEqual(effectRuns, 1) - } -} diff --git a/RxComposableArchitectureTests/EffectTests.swift b/RxComposableArchitectureTests/EffectTests.swift deleted file mode 100644 index 0a26962..0000000 --- a/RxComposableArchitectureTests/EffectTests.swift +++ /dev/null @@ -1,165 +0,0 @@ -// -// EffectTests.swift -// RxComposableArchitecture_RxComposableArchitectureTests -// -// Created by Jefferson Setiawan on 02/02/21. -// - -import RxSwift -import TestSupport -import XCTest - -@testable import RxComposableArchitecture - -internal final class EffectTests: XCTestCase { - private var disposeBag = DisposeBag() - private let scheduler = TestScheduler(initialClock: 0) - - internal func testConcatenate() { - var values: [Int] = [] - - let effect = Effect.concatenate( - Effect(value: 1).delay(.seconds(1), scheduler: scheduler).eraseToEffect(), - Effect(value: 2).delay(.seconds(2), scheduler: scheduler).eraseToEffect(), - Effect(value: 3).delay(.seconds(3), scheduler: scheduler).eraseToEffect() - ) - - effect - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, []) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(values, [1]) - - scheduler.advance(by: .seconds(2)) - XCTAssertEqual(values, [1, 2]) - - scheduler.advance(by: .seconds(3)) - XCTAssertEqual(values, [1, 2, 3]) - - scheduler.run() - XCTAssertEqual(values, [1, 2, 3]) - } - - internal func testConcatenateOneEffect() { - var values: [Int] = [] - - let effect = Effect.concatenate( - Effect(value: 1).delay(.seconds(1), scheduler: scheduler).eraseToEffect() - ) - - effect - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, []) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(values, [1]) - - scheduler.run() - XCTAssertEqual(values, [1]) - } - - internal func testMerge() { - let effect = Effect.merge( - Effect(value: 1).delay(.seconds(1), scheduler: scheduler).eraseToEffect(), - Effect(value: 2).delay(.seconds(2), scheduler: scheduler).eraseToEffect(), - Effect(value: 3).delay(.seconds(3), scheduler: scheduler).eraseToEffect() - ) - - var values: [Int] = [] - effect - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, []) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(values, [1]) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(values, [1, 2]) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(values, [1, 2, 3]) - } - - internal func testEffectSubscriberInitializer() { - let effect = Effect.run { subscriber in - subscriber.onNext(1) - subscriber.onNext(2) - - self.scheduler.scheduleRelative((), dueTime: .seconds(1)) { - subscriber.onNext(3) - return Disposables.create() - } - .disposed(by: self.disposeBag) - - self.scheduler.scheduleRelative((), dueTime: .seconds(2)) { - subscriber.onNext(4) - subscriber.onCompleted() - return Disposables.create() - } - .disposed(by: self.disposeBag) - - return Disposables.create() - } - - var values: [Int] = [] - var isComplete = false - effect - .subscribe(onNext: { values.append($0) }, onCompleted: { isComplete = true }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, [1, 2]) - XCTAssertEqual(isComplete, false) - - scheduler.advance(by: .seconds(1)) - - XCTAssertEqual(values, [1, 2, 3]) - XCTAssertEqual(isComplete, false) - - scheduler.advance(by: .seconds(1)) - - XCTAssertEqual(values, [1, 2, 3, 4]) - XCTAssertEqual(isComplete, true) - } - - internal func testEffectSubscriberInitializer_WithCancellation() { - struct CancelId: Hashable {} - - let effect = Effect.run { subscriber in - subscriber.onNext(1) - - self.scheduler.scheduleRelative((), dueTime: .seconds(1)) { - subscriber.onNext(2) - return Disposables.create() - } - .disposed(by: self.disposeBag) - - return Disposables.create() - } - .cancellable(id: CancelId()) - - var values: [Int] = [] - var isComplete = false - effect - .subscribe(onNext: { values.append($0) }, onCompleted: { isComplete = true }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, [1]) - XCTAssertEqual(isComplete, false) - - Effect.cancel(id: CancelId()) - .subscribe(onNext: {}) - .disposed(by: disposeBag) - - scheduler.advance(by: .seconds(1)) - - XCTAssertEqual(values, [1]) - XCTAssertEqual(isComplete, true) - } -} diff --git a/RxComposableArchitectureTests/IdentifiedArrayTests.swift b/RxComposableArchitectureTests/IdentifiedArrayTests.swift deleted file mode 100644 index e250db6..0000000 --- a/RxComposableArchitectureTests/IdentifiedArrayTests.swift +++ /dev/null @@ -1,191 +0,0 @@ -import XCTest - -@testable import RxComposableArchitecture - -internal final class IdentifiedArrayTests: XCTestCase { - internal func testIdSubscript() { - let array: IdentifiedArray = [User(id: 1, name: "Blob")] - - XCTAssertEqual(array[id: 1], .some(User(id: 1, name: "Blob"))) - } - - internal func testRemoveId() { - var array: IdentifiedArray = [User(id: 1, name: "Blob")] - - XCTAssertEqual(array.remove(id: 1), User(id: 1, name: "Blob")) - XCTAssertEqual(array, []) - } - - internal func testInsert() { - var array: IdentifiedArray = [User(id: 1, name: "Blob")] - - array.insert(User(id: 2, name: "Blob Jr."), at: 0) - XCTAssertEqual(array, [User(id: 2, name: "Blob Jr."), User(id: 1, name: "Blob")]) - } - - internal func testInsertContentsOf() { - var array: IdentifiedArray = [User(id: 1, name: "Blob")] - - array.insert(contentsOf: [User(id: 3, name: "Blob Sr."), User(id: 2, name: "Blob Jr.")], at: 0) - XCTAssertEqual( - array, - [User(id: 3, name: "Blob Sr."), User(id: 2, name: "Blob Jr."), User(id: 1, name: "Blob")] - ) - } - - internal func testRemoveAt() { - var array: IdentifiedArray = [ - User(id: 3, name: "Blob Sr."), - User(id: 2, name: "Blob Jr."), - User(id: 1, name: "Blob") - ] - - array.remove(at: 1) - XCTAssertEqual(array, [User(id: 3, name: "Blob Sr."), User(id: 1, name: "Blob")]) - } - - internal func testRemoveAllWhere() { - var array: IdentifiedArray = [ - User(id: 3, name: "Blob Sr."), - User(id: 2, name: "Blob Jr."), - User(id: 1, name: "Blob") - ] - - array.removeAll(where: { $0.name.starts(with: "Blob ") }) - XCTAssertEqual(array, [User(id: 1, name: "Blob")]) - } - - internal func testRemoveAtOffsets() { - var array: IdentifiedArray = [ - User(id: 3, name: "Blob Sr."), - User(id: 2, name: "Blob Jr."), - User(id: 1, name: "Blob") - ] - - array.remove(atOffsets: [0, 2]) - XCTAssertEqual(array, [User(id: 2, name: "Blob Jr.")]) - } - - internal func testReplaceSubrange() { - var array: IdentifiedArray = [ - User(id: 3, name: "Blob Sr."), - User(id: 2, name: "Blob Jr."), - User(id: 1, name: "Blob"), - User(id: 2, name: "Blob Jr.") - ] - - array.replaceSubrange( - 0 ... 1, - with: [ - User(id: 4, name: "Flob IV"), - User(id: 5, name: "Flob V") - ] - ) - - XCTAssertEqual( - array, - [ - User(id: 4, name: "Flob IV"), User(id: 5, name: "Flob V"), User(id: 1, name: "Blob"), - User(id: 2, name: "Blob Jr.") - ] - ) - } - - internal func testSortBy() { - var array: IdentifiedArray = [ - ComparableValue(id: 1, value: 100), - ComparableValue(id: 2, value: 50), - ComparableValue(id: 3, value: 75) - ] - - array.sort { $0.value < $1.value } - - XCTAssertEqual([2, 3, 1], array.ids) - XCTAssertEqual( - [ - ComparableValue(id: 2, value: 50), - ComparableValue(id: 3, value: 75), - ComparableValue(id: 1, value: 100) - ], array - ) - } - - internal func testSort() { - var array: IdentifiedArray = [ - ComparableValue(id: 1, value: 100), - ComparableValue(id: 2, value: 50), - ComparableValue(id: 3, value: 75) - ] - - array.sort() - - XCTAssertEqual([2, 3, 1], array.ids) - XCTAssertEqual( - [ - ComparableValue(id: 2, value: 50), - ComparableValue(id: 3, value: 75), - ComparableValue(id: 1, value: 100) - ], array - ) - } - - // Account for randomness API changes in Swift 5.3 (https://twitter.com/mbrandonw/status/1262388756847505410) - // TODO: Try swapping out the LCRNG for a Xoshiro generator - #if swift(>=5.3) - internal func testShuffle() { - var array: IdentifiedArray = [ - User(id: 1, name: "Blob"), - User(id: 2, name: "Blob Jr."), - User(id: 3, name: "Blob Sr."), - User(id: 4, name: "Foo Jr."), - User(id: 5, name: "Bar Jr.") - ] - var lcrng = LCRNG(seed: 0) - array.shuffle(using: &lcrng) - XCTAssertEqual( - [ - User(id: 1, name: "Blob"), - User(id: 3, name: "Blob Sr."), - User(id: 5, name: "Bar Jr."), - User(id: 4, name: "Foo Jr."), - User(id: 2, name: "Blob Jr.") - ], - array.elements - ) - XCTAssertEqual([1, 3, 5, 4, 2], array.ids) - } - #endif - - internal func testReverse() { - var array: IdentifiedArray = [ - ComparableValue(id: 1, value: 100), - ComparableValue(id: 2, value: 50), - ComparableValue(id: 3, value: 75) - ] - - array.reverse() - - XCTAssertEqual([3, 2, 1], array.ids) - XCTAssertEqual( - [ - ComparableValue(id: 3, value: 75), - ComparableValue(id: 2, value: 50), - ComparableValue(id: 1, value: 100) - ], array - ) - } -} - -internal struct User: Equatable, HashDiffable { - internal let id: Int - internal var name: String -} - -internal struct ComparableValue: Comparable, HashDiffable { - internal let id: Int - internal let value: Int - - internal static func < (lhs: ComparableValue, rhs: ComparableValue) -> Bool { - return lhs.value < rhs.value - } -} diff --git a/RxComposableArchitectureTests/LCRNG.swift b/RxComposableArchitectureTests/LCRNG.swift deleted file mode 100644 index da3d330..0000000 --- a/RxComposableArchitectureTests/LCRNG.swift +++ /dev/null @@ -1,15 +0,0 @@ -/// A linear congruential random number generator. -public struct LCRNG: RandomNumberGenerator { - public var seed: UInt64 - - @inlinable - public init(seed: UInt64) { - self.seed = seed - } - - @inlinable - public mutating func next() -> UInt64 { - seed = 2_862_933_555_777_941_757 &* seed &+ 3_037_000_493 - return seed - } -} diff --git a/RxComposableArchitectureTests/MemoryManagementTests.swift b/RxComposableArchitectureTests/MemoryManagementTests.swift deleted file mode 100644 index e1a9ee8..0000000 --- a/RxComposableArchitectureTests/MemoryManagementTests.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation -import RxComposableArchitecture -import RxSwift -import XCTest - -internal final class MemoryManagementTests: XCTestCase { - internal func testOwnership_ScopeHoldsOntoParent() { - let disposeBag = DisposeBag() - - let counterReducer = Reducer { state, _, _ in - state += 1 - return .none - } - let store = Store(initialState: 0, reducer: counterReducer, environment: ()) - .scope(state: { "\($0)" }) - .scope(state: { Int($0)! }) - - var count = 0 - - store.subscribe { $0 }.subscribe(onNext: { count = $0 }).disposed(by: disposeBag) - - XCTAssertEqual(count, 0) - store.send(()) - XCTAssertEqual(count, 1) - } -} diff --git a/RxComposableArchitectureTests/ReducerTests.swift b/RxComposableArchitectureTests/ReducerTests.swift deleted file mode 100644 index cefccc2..0000000 --- a/RxComposableArchitectureTests/ReducerTests.swift +++ /dev/null @@ -1,125 +0,0 @@ -import Foundation -import RxComposableArchitecture -import RxSwift -import TestSupport -import XCTest - -internal final class ReducerTests: XCTestCase { - internal func testCallableAsFunction() { - let reducer = Reducer { state, _, _ in - state += 1 - return .none - } - - var state = 0 - _ = reducer.run(&state, (), ()) - XCTAssertEqual(state, 1) - } - - internal func testCombine_EffectsAreMerged() { - typealias Scheduler = TestScheduler - enum Action: Equatable { - case increment - } - - var fastValue: Int? - let fastReducer = Reducer { state, _, scheduler in - state += 1 - return Effect.fireAndForget { fastValue = 42 } - .delay(.seconds(1), scheduler: scheduler) - .eraseToEffect() - } - - var slowValue: Int? - let slowReducer = Reducer { state, _, scheduler in - state += 1 - return Effect.fireAndForget { slowValue = 1729 } - .delay(.seconds(2), scheduler: scheduler) - .eraseToEffect() - } - - let scheduler = TestScheduler(initialClock: 0) - let store = TestStore( - initialState: 0, - reducer: .combine(fastReducer, slowReducer), - environment: scheduler - ) - - store.assert( - .send(.increment) { - $0 = 2 - }, - // Waiting a second causes the fast effect to fire. - .do { scheduler.advance(by: .seconds(1)) }, - .do { XCTAssertEqual(fastValue, 42) }, - // Waiting one more second causes the slow effect to fire. This proves that the effects - // are merged together, as opposed to concatenated. - .do { scheduler.advance(by: .seconds(1)) }, - .do { XCTAssertEqual(slowValue, 1729) } - ) - } - - internal func testCombine() { - enum Action: Equatable { - case increment - } - - var childEffectExecuted = false - let childReducer = Reducer { state, _, _ in - state += 1 - return Effect.fireAndForget { childEffectExecuted = true } - } - - var mainEffectExecuted = false - let mainReducer = Reducer { state, _, _ in - state += 1 - return Effect.fireAndForget { mainEffectExecuted = true } - } - .combined(with: childReducer) - - let store = TestStore( - initialState: 0, - reducer: mainReducer, - environment: () - ) - - store.assert( - .send(.increment) { - $0 = 2 - } - ) - - XCTAssertTrue(childEffectExecuted) - XCTAssertTrue(mainEffectExecuted) - } - - internal func testDefaultSignpost() { - let disposeBag = DisposeBag() - - let reducer = Reducer.empty.signpost(log: .default) - var n = 0 - - // swiftformat:disable:next redundantParens - let effect = reducer.run(&n, (), ()) - let expectation = self.expectation(description: "effect") - effect - .subscribe(onCompleted: { expectation.fulfill() }) - .disposed(by: disposeBag) - wait(for: [expectation], timeout: 0.1) - } - - internal func testDisabledSignpost() { - let disposeBag = DisposeBag() - - let reducer = Reducer.empty.signpost(log: .disabled) - var n = 0 - - // swiftformat:disable:next redundantParens - let effect = reducer.run(&n, (), ()) - let expectation = self.expectation(description: "effect") - effect - .subscribe(onCompleted: { expectation.fulfill() }) - .disposed(by: disposeBag) - wait(for: [expectation], timeout: 0.1) - } -} diff --git a/RxComposableArchitectureTests/RxComposableArchitectureTests.docc/RxComposableArchitectureTests.md b/RxComposableArchitectureTests/RxComposableArchitectureTests.docc/RxComposableArchitectureTests.md deleted file mode 100755 index 3105fd5..0000000 --- a/RxComposableArchitectureTests/RxComposableArchitectureTests.docc/RxComposableArchitectureTests.md +++ /dev/null @@ -1,13 +0,0 @@ -# ``RxComposableArchitectureTests`` - -Summary - -## Overview - -Text - -## Topics - -### Group - -- ``Symbol`` \ No newline at end of file diff --git a/RxComposableArchitectureTests/RxComposableArchitectureTests.h b/RxComposableArchitectureTests/RxComposableArchitectureTests.h deleted file mode 100644 index ac878d7..0000000 --- a/RxComposableArchitectureTests/RxComposableArchitectureTests.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// RxComposableArchitectureTests.h -// RxComposableArchitectureTests -// -// Created by Andrey Yoshua Manik on 21/02/22. -// - -#import - -//! Project version number for RxComposableArchitectureTests. -FOUNDATION_EXPORT double RxComposableArchitectureTestsVersionNumber; - -//! Project version string for RxComposableArchitectureTests. -FOUNDATION_EXPORT const unsigned char RxComposableArchitectureTestsVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/RxComposableArchitectureTests/RxComposableArchitectureTests.swift b/RxComposableArchitectureTests/RxComposableArchitectureTests.swift deleted file mode 100644 index 32bd341..0000000 --- a/RxComposableArchitectureTests/RxComposableArchitectureTests.swift +++ /dev/null @@ -1,168 +0,0 @@ -// -// RxComposableArchitectureTests.swift -// RxComposableArchitectureTests -// -// Created by module_generator on 06/05/20. -// Copyright Š 2020 module_generator. All rights reserved. -// - -import RxComposableArchitecture -import RxSwift -import TestSupport -import XCTest - -internal class RxComposableArchitectureTests: XCTestCase { - internal func testScheduling() { - enum CounterAction: Equatable { - case incrAndSquareLater - case incrNow - case squareNow - } - - let counterReducer = Reducer { - state, action, scheduler in - switch action { - case .incrAndSquareLater: - return .merge( - Effect(value: .incrNow) - .delay(.seconds(2), scheduler: scheduler) - .eraseToEffect(), - Effect(value: .squareNow) - .delay(.seconds(1), scheduler: scheduler) - .eraseToEffect(), - Effect(value: .squareNow) - .delay(.seconds(2), scheduler: scheduler) - .eraseToEffect() - ) - case .incrNow: - state += 1 - return .none - case .squareNow: - state *= state - return .none - } - } - - let scheduler = TestScheduler(initialClock: 0) - - let store = TestStore( - initialState: 2, - reducer: counterReducer, - environment: scheduler - ) - - store.assert( - .send(.incrAndSquareLater), - .do { scheduler.advance(by: .seconds(1)) }, - .receive(.squareNow) { $0 = 4 }, - .do { scheduler.advance(by: .seconds(1)) }, - .receive(.incrNow) { $0 = 5 }, - .receive(.squareNow) { $0 = 25 } - ) - - store.assert( - .send(.incrAndSquareLater), - .do { scheduler.advance(by: .seconds(2)) }, - .receive(.squareNow) { $0 = 625 }, - .receive(.incrNow) { $0 = 626 }, - .receive(.squareNow) { $0 = 391_876 } - ) - } - - internal func testLongLivingEffects() { - typealias Environment = ( - startEffect: Effect, - stopEffect: Effect - ) - - enum Action { case end, incr, start } - - let reducer = Reducer { state, action, environment in - switch action { - case .end: - return environment.stopEffect.fireAndForget() - case .incr: - state += 1 - return .none - case .start: - return environment.startEffect.map { Action.incr } - } - } - - let subject = PublishSubject() - - let store = TestStore( - initialState: 0, - reducer: reducer, - environment: ( - startEffect: subject.eraseToEffect(), - stopEffect: .fireAndForget { subject.onCompleted() } - ) - ) - - store.assert( - .send(.start), - .send(.incr) { $0 = 1 }, - .do { subject.onNext(()) }, - .receive(.incr) { $0 = 2 }, - .send(.end) - ) - } - - internal func testCancellation() { - enum Action: Equatable { - case cancel - case incr - case response(Int) - } - - struct Environment { - let fetch: (Int) -> Effect - let mainQueue: TestScheduler - } - - let reducer = Reducer { state, action, environment in - struct CancelId: Hashable {} - - switch action { - case .cancel: - return .cancel(id: CancelId()) - - case .incr: - state += 1 - return environment.fetch(state) - .observeOn(environment.mainQueue) - .map(Action.response) - .eraseToEffect() - .cancellable(id: CancelId()) - - case let .response(value): - state = value - return .none - } - } - - let scheduler = TestScheduler(initialClock: 0) - - let store = TestStore( - initialState: 0, - reducer: reducer, - environment: Environment( - fetch: { value in Effect(value: value * value) }, - mainQueue: scheduler - ) - ) - - store.assert( - .send(.incr) { $0 = 1 }, - .do { scheduler.advance(by: .milliseconds(1)) }, - .receive(.response(1)) { $0 = 1 } - ) - - store.assert( - .send(.incr) { $0 = 2 }, - .send(.cancel), - .do { scheduler.run() } - ) - } -} diff --git a/RxComposableArchitectureTests/SingleSelectionSelectableTypeTests.swift b/RxComposableArchitectureTests/SingleSelectionSelectableTypeTests.swift deleted file mode 100644 index 57acb72..0000000 --- a/RxComposableArchitectureTests/SingleSelectionSelectableTypeTests.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// SingleSelectionSelectableTypeTests.swift -// RxComposableArchitectureTests -// -// Created by Wendy Liga on 25/05/21. -// - -import RxComposableArchitecture -import XCTest - -internal final class SingleSelectionSelectableTypeTests: XCTestCase { - internal struct Item: HashDiffable, Equatable, Selectable { - internal let id: Int - internal var isSelected: Bool - } - - internal struct State { - @SingleSelection - internal var items: IdentifiedArrayOf - } - - internal func test_initNoSelection() { - let initialItems = IdentifiedArray([ - Item(id: 1, isSelected: false), - Item(id: 2, isSelected: false), - Item(id: 3, isSelected: false), - Item(id: 4, isSelected: false) - ]) - - let target = State(items: .init(initialItems)) - AssertSelection(target, selectionId: nil) - } - - internal func test_initWithSelection() { - let initialItems = IdentifiedArray([ - Item(id: 0, isSelected: true), - Item(id: 1, isSelected: false), - Item(id: 2, isSelected: false), - Item(id: 3, isSelected: false) - ]) - - let target = State(items: SingleSelection(initialItems)) - AssertSelection(target, selectionId: 0) - } - - internal func test_initWithMultipleSelection_shouldSelectTheFirstOne() { - let initialItems = IdentifiedArray([ - Item(id: 0, isSelected: false), - Item(id: 1, isSelected: true), - Item(id: 2, isSelected: true), - Item(id: 3, isSelected: true) - ]) - - let target = State(items: SingleSelection(initialItems)) - AssertSelection(target, selectionId: 1) - } - - internal func test_initWithSelection_thenChangeSelection() { - let initialItems = IdentifiedArray([ - Item(id: 0, isSelected: true), - Item(id: 1, isSelected: false), - Item(id: 2, isSelected: false), - Item(id: 3, isSelected: false) - ]) - - var target = State(items: SingleSelection(initialItems)) - - target.items[1].isSelected = true - AssertSelection(target, selectionId: 1) - } - - internal func test_initWithMultipleSelection_shouldSelectTheFirstOne_thenChangeSelectionSeveralTime() { - let initialItems = IdentifiedArray([ - Item(id: 0, isSelected: false), - Item(id: 1, isSelected: true), - Item(id: 2, isSelected: true), - Item(id: 3, isSelected: true) - ]) - - var target = State(items: SingleSelection(initialItems)) - target.items[2].isSelected = true - target.items[3].isSelected = true - AssertSelection(target, selectionId: 3) - } - - internal func AssertSelection( - _ state: State, - selectionId id: Int?, - file: StaticString = #file, - line: UInt = #line - ) { - let selected = state.items.filter(\.isSelected) - guard let id = id else { - XCTAssertTrue(selected.count == 0, file: file, line: line) - return - } - - guard let element = selected[id: id] else { - XCTFail("element is not valid", file: file, line: line); return - } - - XCTAssertTrue(selected.count == 1 && element.isSelected, file: file, line: line) - } -} diff --git a/RxComposableArchitectureTests/SingleSelectionTests.swift b/RxComposableArchitectureTests/SingleSelectionTests.swift deleted file mode 100644 index e890212..0000000 --- a/RxComposableArchitectureTests/SingleSelectionTests.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// SingleSelectionTests.swift -// RxComposableArchitectureTests -// -// Created by Wendy Liga on 25/05/21. -// - -import RxComposableArchitecture -import XCTest - -internal final class SingleSelectionTests: XCTestCase { - internal struct Item: HashDiffable, Equatable { - internal let id: Int - internal var isSelected: Bool - } - - internal struct State { - @SingleSelection([], selection: \.isSelected) - internal var items: IdentifiedArrayOf - } - - internal func test_initNoSelection() { - let initialItems = IdentifiedArray([ - Item(id: 1, isSelected: false), - Item(id: 2, isSelected: false), - Item(id: 3, isSelected: false), - Item(id: 4, isSelected: false) - ]) - - let target = State(items: SingleSelection(initialItems, selection: \.isSelected)) - AssertSelection(target, selectionId: nil) - } - - internal func test_initWithSelection() { - let initialItems = IdentifiedArray([ - Item(id: 0, isSelected: true), - Item(id: 1, isSelected: false), - Item(id: 2, isSelected: false), - Item(id: 3, isSelected: false) - ]) - - let target = State(items: SingleSelection(initialItems, selection: \.isSelected)) - AssertSelection(target, selectionId: 0) - } - - internal func test_initWithMultipleSelection_shouldSelectTheFirstOne() { - let initialItems = IdentifiedArray([ - Item(id: 0, isSelected: false), - Item(id: 1, isSelected: true), - Item(id: 2, isSelected: true), - Item(id: 3, isSelected: true) - ]) - - let target = State(items: SingleSelection(initialItems, selection: \.isSelected)) - AssertSelection(target, selectionId: 1) - } - - internal func test_initWithSelection_thenChangeSelection() { - let initialItems = IdentifiedArray([ - Item(id: 0, isSelected: true), - Item(id: 1, isSelected: false), - Item(id: 2, isSelected: false), - Item(id: 3, isSelected: false) - ]) - - var target = State(items: SingleSelection(initialItems, selection: \.isSelected)) - - target.items[1].isSelected = true - AssertSelection(target, selectionId: 1) - } - - internal func test_initWithMultipleSelection_shouldSelectTheFirstOne_thenChangeSelectionSeveralTime() { - let initialItems = IdentifiedArray([ - Item(id: 0, isSelected: false), - Item(id: 1, isSelected: true), - Item(id: 2, isSelected: true), - Item(id: 3, isSelected: true) - ]) - - var target = State(items: SingleSelection(initialItems, selection: \.isSelected)) - target.items[2].isSelected = true - target.items[3].isSelected = true - AssertSelection(target, selectionId: 3) - } - - internal func AssertSelection( - _ state: SingleSelectionTests.State, - selectionId id: Int?, - file: StaticString = #file, - line: UInt = #line - ) { - let selected = state.items.filter(\.isSelected) - guard let id = id else { - XCTAssertTrue(selected.count == 0, file: file, line: line) - return - } - - guard let element = selected[id: id] else { - XCTFail("element is not valid", file: file, line: line); return - } - - XCTAssertTrue(selected.count == 1 && element.isSelected, file: file, line: line) - } -} diff --git a/RxComposableArchitectureTests/StoreTests.swift b/RxComposableArchitectureTests/StoreTests.swift deleted file mode 100644 index ffe61a8..0000000 --- a/RxComposableArchitectureTests/StoreTests.swift +++ /dev/null @@ -1,395 +0,0 @@ -import RxSwift -import TestSupport -import XCTest - -@testable import RxComposableArchitecture - -internal final class StoreTests: XCTestCase { - private let disposeBag = DisposeBag() - - internal func testCancellableIsRemovedOnImmediatelyCompletingEffect() { - let reducer = Reducer { _, _, _ in .none } - let store = Store(initialState: (), reducer: reducer, environment: ()) - - XCTAssertEqual(store.effectDisposables.count, 0) - - store.send(()) - - XCTAssertEqual(store.effectDisposables.count, 0) - } - - internal func testCancellableIsRemovedWhenEffectCompletes() { - let scheduler = TestScheduler(initialClock: 0) - let effect = Effect(value: ()) - .delay(.seconds(1), scheduler: scheduler) - .eraseToEffect() - - enum Action { case start, end } - - let reducer = Reducer { _, action, _ in - switch action { - case .start: - return effect.map { .end } - case .end: - return .none - } - } - let store = Store(initialState: (), reducer: reducer, environment: ()) - - XCTAssertEqual(store.effectDisposables.count, 0) - - store.send(.start) - - XCTAssertEqual(store.effectDisposables.count, 1) - - scheduler.advance(by: .seconds(2)) - - XCTAssertEqual(store.effectDisposables.count, 0) - } - - internal func testScopedStoreReceivesUpdatesFromParent() { - let counterReducer = Reducer { state, _, _ in - state += 1 - return .none - } - - let parentStore = Store(initialState: 0, reducer: counterReducer, environment: ()) - let childStore = parentStore.scope(state: String.init) - - var values: [String] = [] - childStore.subscribe { $0 } - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, ["0"]) - - parentStore.send(()) - - XCTAssertEqual(values, ["0", "1"]) - } - - internal func testParentStoreReceivesUpdatesFromChild() { - let counterReducer = Reducer { state, _, _ in - state += 1 - return .none - } - - let parentStore = Store(initialState: 0, reducer: counterReducer, environment: ()) - let childStore = parentStore.scope(state: String.init) - - var values: [Int] = [] - - parentStore.subscribe { $0 } - .subscribe(onNext: { values.append($0) }) - .disposed(by: disposeBag) - - XCTAssertEqual(values, [0]) - - childStore.send(()) - - XCTAssertEqual(values, [0, 1]) - } - - internal func testScopeWithPublisherTransform() { - let counterReducer = Reducer { state, action, _ in - state = action - return .none - } - let parentStore = Store(initialState: 0, reducer: counterReducer, environment: ()) - - var outputs: [String] = [] - - parentStore - .scope(state: { $0.map { "\($0)" }.distinctUntilChanged() }) - .subscribe(onNext: { childStore in - childStore.observable - .subscribe(onNext: { outputs.append($0) }) - .disposed(by: self.disposeBag) - }) - .disposed(by: disposeBag) - - parentStore.send(0) - XCTAssertEqual(outputs, ["0"]) - parentStore.send(0) - XCTAssertEqual(outputs, ["0"]) - parentStore.send(1) - XCTAssertEqual(outputs, ["0", "1"]) - parentStore.send(1) - XCTAssertEqual(outputs, ["0", "1"]) - parentStore.send(2) - XCTAssertEqual(outputs, ["0", "1", "2"]) - } - - internal func testScopeCallCount() { - let counterReducer = Reducer { state, _, _ in state += 1 - return .none - } - - var numCalls1 = 0 - _ = Store(initialState: 0, reducer: counterReducer, environment: ()) - .scope(state: { (count: Int) -> Int in - numCalls1 += 1 - return count - }) - - XCTAssertEqual(numCalls1, 2) - } - - internal func testScopeCallCount2() { - let counterReducer = Reducer { state, _, _ in - state += 1 - return .none - } - - var numCalls1 = 0 - var numCalls2 = 0 - var numCalls3 = 0 - - let store = Store(initialState: 0, reducer: counterReducer, environment: ()) - .scope(state: { (count: Int) -> Int in - numCalls1 += 1 - return count - }) - .scope(state: { (count: Int) -> Int in - numCalls2 += 1 - return count - }) - .scope(state: { (count: Int) -> Int in - numCalls3 += 1 - return count - }) - - XCTAssertEqual(numCalls1, 2) - XCTAssertEqual(numCalls2, 2) - XCTAssertEqual(numCalls3, 2) - - store.send(()) - - XCTAssertEqual(numCalls1, 4) - XCTAssertEqual(numCalls2, 5) - XCTAssertEqual(numCalls3, 6) - - store.send(()) - - XCTAssertEqual(numCalls1, 6) - XCTAssertEqual(numCalls2, 8) - XCTAssertEqual(numCalls3, 10) - - store.send(()) - - XCTAssertEqual(numCalls1, 8) - XCTAssertEqual(numCalls2, 11) - XCTAssertEqual(numCalls3, 14) - } - - internal func testSynchronousEffectsSentAfterSinking() { - enum Action { - case tap - case next1 - case next2 - case end - } - var values: [Int] = [] - let counterReducer = Reducer { _, action, _ in - switch action { - case .tap: - return .merge( - Effect(value: .next1), - Effect(value: .next2), - .fireAndForget { values.append(1) } - ) - case .next1: - return .merge( - Effect(value: .end), - .fireAndForget { values.append(2) } - ) - case .next2: - return .fireAndForget { values.append(3) } - case .end: - return .fireAndForget { values.append(4) } - } - } - - let store = Store(initialState: (), reducer: counterReducer, environment: ()) - - store.send(.tap) - - XCTAssertEqual(values, [1, 2, 3, 4]) - } - - internal func testLotsOfSynchronousActions() { - enum Action { case incr, noop } - let reducer = Reducer { state, action, _ in - switch action { - case .incr: - state += 1 - return state >= 10000 ? Effect(value: .noop) : Effect(value: .incr) - case .noop: - return .none - } - } - - let store = Store(initialState: 0, reducer: reducer, environment: ()) - store.send(.incr) - XCTAssertEqual(store.state, 10000) - } - - internal func testPublisherScope() { - let appReducer = Reducer { state, action, _ in - state += action ? 1 : 0 - return .none - } - - let parentStore = Store(initialState: 0, reducer: appReducer, environment: ()) - - var outputs: [Int] = [] - - parentStore - .scope(state: { $0.distinctUntilChanged() }) - .subscribe(onNext: { childStore in - childStore.observable - .subscribe(onNext: { outputs.append($0) }) - .disposed(by: self.disposeBag) - }) - .disposed(by: disposeBag) - - XCTAssertEqual(outputs, [0]) - - parentStore.send(true) - XCTAssertEqual(outputs, [0, 1]) - - parentStore.send(false) - XCTAssertEqual(outputs, [0, 1]) - parentStore.send(false) - XCTAssertEqual(outputs, [0, 1]) - parentStore.send(false) - XCTAssertEqual(outputs, [0, 1]) - parentStore.send(false) - XCTAssertEqual(outputs, [0, 1]) - } - - internal func testIfLetAfterScope() { - struct AppState { - var count: Int? - } - - let appReducer = Reducer { state, action, _ in - state.count = action - return .none - } - - let parentStore = Store(initialState: AppState(), reducer: appReducer, environment: ()) - - // NB: This test needs to hold a strong reference to the emitted stores - var outputs: [Int?] = [] - var stores: [Any] = [] - - parentStore - .scope(state: { $0.count }) - .ifLet( - then: { store in - stores.append(store) - outputs.append(store.state) - }, - else: { - outputs.append(nil) - } - ) - .disposed(by: disposeBag) - - XCTAssertEqual(outputs, [nil]) - - parentStore.send(1) - XCTAssertEqual(outputs, [nil, 1]) - - parentStore.send(nil) - XCTAssertEqual(outputs, [nil, 1, nil]) - - parentStore.send(1) - XCTAssertEqual(outputs, [nil, 1, nil, 1]) - - parentStore.send(nil) - XCTAssertEqual(outputs, [nil, 1, nil, 1, nil]) - - parentStore.send(1) - XCTAssertEqual(outputs, [nil, 1, nil, 1, nil, 1]) - - parentStore.send(nil) - XCTAssertEqual(outputs, [nil, 1, nil, 1, nil, 1, nil]) - } - - internal func testIfLetTwo() { - let parentStore = Store( - initialState: 0, - reducer: Reducer { state, action, _ in - if action { - state? += 1 - return .none - } else { - return Observable.just(true) - .observeOn(MainScheduler.instance) - .eraseToEffect() - } - }, - environment: () - ) - - parentStore.ifLet { childStore in - childStore - .observable - .subscribe() - .disposed(by: self.disposeBag) - - childStore.send(false) - _ = XCTWaiter.wait(for: [.init()], timeout: 0.1) - childStore.send(false) - _ = XCTWaiter.wait(for: [.init()], timeout: 0.1) - childStore.send(false) - _ = XCTWaiter.wait(for: [.init()], timeout: 0.1) - XCTAssertEqual(childStore.state, 3) - } - .disposed(by: disposeBag) - } - - internal func testActionQueuing() { - let subject = PublishSubject() - - enum Action: Equatable { - case incrementTapped - case initialize - case doIncrement - } - - let store = TestStore( - initialState: 0, - reducer: Reducer { state, action, _ in - switch action { - case .incrementTapped: - subject.onNext(()) - return .none - - case .initialize: - return subject.map { .doIncrement }.eraseToEffect() - - case .doIncrement: - state += 1 - return .none - } - }, - environment: () - ) - - store.assert( - .send(.initialize), - .send(.incrementTapped), - .receive(.doIncrement) { - $0 = 1 - }, - .send(.incrementTapped), - .receive(.doIncrement) { - $0 = 2 - }, - .do { subject.onCompleted() } - ) - } -} diff --git a/RxComposableArchitectureTests/TimerTests.swift b/RxComposableArchitectureTests/TimerTests.swift deleted file mode 100644 index 89b5311..0000000 --- a/RxComposableArchitectureTests/TimerTests.swift +++ /dev/null @@ -1,140 +0,0 @@ -// -// TimerTests.swift -// RxComposableArchitecture_RxComposableArchitectureTests -// -// Created by Jefferson Setiawan on 02/02/21. -// - -import RxComposableArchitecture -import RxSwift -import TestSupport -import XCTest - -internal final class TimerTests: XCTestCase { - private var disposeBag = DisposeBag() - - internal func testTimer() { - let scheduler = TestScheduler(initialClock: 0) - - var count = 0 - - Effect.timer(id: 1, every: .seconds(1), on: scheduler) - .subscribe(onNext: { _ in count += 1 }) - .disposed(by: disposeBag) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count, 1) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count, 2) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count, 3) - - scheduler.advance(by: .seconds(3)) - XCTAssertEqual(count, 6) - } - - internal func testInterleavingTimer() { - let scheduler = TestScheduler(initialClock: 0) - - var count2 = 0 - var count3 = 0 - - Effect.merge( - Effect.timer(id: 1, every: .seconds(2), on: scheduler) - .do(onNext: { _ in count2 += 1 }) - .eraseToEffect(), - Effect.timer(id: 2, every: .seconds(3), on: scheduler) - .do(onNext: { _ in count3 += 1 }) - .eraseToEffect() - ) - .subscribe(onNext: { _ in }) - .disposed(by: disposeBag) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count2, 0) - XCTAssertEqual(count3, 0) - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count2, 1) - XCTAssertEqual(count3, 0) - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count2, 1) - XCTAssertEqual(count3, 1) - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count2, 2) - XCTAssertEqual(count3, 1) - } - - internal func testTimerCancellation() { - let scheduler = TestScheduler(initialClock: 0) - - var count2 = 0 - var count3 = 0 - - struct CancelToken: Hashable {} - - Effect.merge( - Effect.timer(id: CancelToken(), every: .seconds(2), on: scheduler) - .do(onNext: { _ in count2 += 1 }) - .eraseToEffect(), - Effect.timer(id: CancelToken(), every: .seconds(3), on: scheduler) - .do(onNext: { _ in count3 += 1 }) - .eraseToEffect(), - Observable.just(()) - .delay(.seconds(31), scheduler: scheduler) - .flatMap { Effect.cancel(id: CancelToken()) } - .eraseToEffect() - ) - .subscribe(onNext: { _ in }) - .disposed(by: disposeBag) - - scheduler.advance(by: .seconds(1)) - - XCTAssertEqual(count2, 0) - XCTAssertEqual(count3, 0) - - scheduler.advance(by: .seconds(1)) - - XCTAssertEqual(count2, 1) - XCTAssertEqual(count3, 0) - - scheduler.advance(by: .seconds(1)) - - XCTAssertEqual(count2, 1) - XCTAssertEqual(count3, 1) - - scheduler.advance(by: .seconds(1)) - - XCTAssertEqual(count2, 2) - XCTAssertEqual(count3, 1) - - scheduler.run() - - XCTAssertEqual(count2, 15) - XCTAssertEqual(count3, 10) - } - - internal func testTimerCompletion() { - let scheduler = TestScheduler(initialClock: 0) - - var count = 0 - - Effect.timer(id: 1, every: .seconds(1), on: scheduler) - .take(3) - .subscribe(onNext: { _ in count += 1 }) - .disposed(by: disposeBag) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count, 1) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count, 2) - - scheduler.advance(by: .seconds(1)) - XCTAssertEqual(count, 3) - - scheduler.run() - XCTAssertEqual(count, 3) - } -} diff --git a/TestSupport/Annotating.swift b/TestSupport/Annotating.swift deleted file mode 100644 index a3b34f1..0000000 --- a/TestSupport/Annotating.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// Annotating.swift -// TestSupport -// -// Created by Wendy Liga on 24/09/20. -// - -#if DEBUG - import XCTest - - extension TestStore.Annotating { - public static var activity: Self { - Self { step, groupLevel, callback in - func runActivity(named name: String) { - #if BAZEL - callback { _ in } - /** - unit test on bazel doesn't support add custom activity - - Fatal error: XCTContext.runActivity(named:block:) failed because activities are disallowed in the current configuration.: file /Library/Caches/com.apple.xbs/Sources/XCTest_Sim/XCTest-16091.4/Sources/libXCTestSwiftSupport/XCTContext_SwiftExtensions.swift, line 23 - */ - return - #endif - - let indent = String(repeating: "\t", count: groupLevel) - - XCTContext.runActivity(named: "\(indent)\(name)") { _ in - callback { _ in } - } - } - - switch step.type { - case let .send(action, _): - runActivity(named: "send: \(action)") - case let .receive(action, _): - runActivity(named: "receive: \(action)") - case let .group(name, _): - runActivity(named: name) - default: - callback { _ in } - return - } - } - } - - public static var console: Self { - Self { step, groupLevel, callback in - func console(_ string: String) { - let indent = String(repeating: "\t", count: groupLevel) - print("\(indent)\(string)") - } - - switch step.type { - case let .send(action, _): - console("send: \(action)") - case let .receive(action, _): - console("receive: \(action)") - case let .group(name, _): - console("TestStore assert group: '\(name)' started at \(Date())") - default: - return - } - - callback { stepPassed in - console("\t [\(stepPassed ? "PASS" : "FAIL")]") - } - } - } - } - -#endif diff --git a/TestSupport/Export.swift b/TestSupport/Export.swift deleted file mode 100644 index af096b7..0000000 --- a/TestSupport/Export.swift +++ /dev/null @@ -1,2 +0,0 @@ -@_exported import DiffingTestSupport -@_exported import RxComposableArchitecture diff --git a/TestSupport/FailingEffect.swift b/TestSupport/FailingEffect.swift deleted file mode 100644 index 467fe89..0000000 --- a/TestSupport/FailingEffect.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// FailingEffect.swift -// RxComposableArchitecture_TestSupport -// -// Created by Jefferson Setiawan on 12/04/21. -// - -import RxComposableArchitecture -import XCTest - -extension Effect { - /// An effect that causes a test to fail if it runs. - /// - /// This effect can provide an additional layer of certainty that a tested code path does not - /// execute a particular effect. - /// - /// For example, let's say we have a very simple counter application, where a user can increment - /// and decrement a number. The state and actions are simple enough: - /// - /// struct CounterState: Equatable { - /// var count = 0 - /// } - /// - /// enum CounterAction: Equatable { - /// case decrementButtonTapped - /// case incrementButtonTapped - /// } - /// - /// Let's throw in a side effect. If the user attempts to decrement the counter below zero, the - /// application should refuse and play an alert sound instead. - /// - /// We can model playing a sound in the environment with an effect: - /// - /// struct CounterEnvironment { - /// let playAlertSound: () -> Effect - /// } - /// - /// Now that we've defined the domain, we can describe the logic in a reducer: - /// - /// let counterReducer = Reducer< - /// CounterState, CounterAction, CounterEnvironment - /// > { state, action, environment in - /// switch action { - /// case .decrementButtonTapped: - /// if state > 0 { - /// state.count -= 0 - /// return .none - /// } else { - /// return environment.playAlertSound() - /// .fireAndForget() - /// } - /// - /// case .incrementButtonTapped: - /// state.count += 1 - /// return .non - /// } - /// } - /// - /// Let's say we want to write a test for the increment path. We can see in the reducer that it - /// should never play an alert, so we can configure the environment with an effect that will - /// fail if it ever executes: - /// - /// func testIncrement() { - /// let store = TestStore( - /// initialState: CounterState(count: 0) - /// reducer: counterReducer, - /// environment: CounterEnvironment( - /// playSound: .failing("playSound") - /// ) - /// ) - /// - /// store.send(.increment) { - /// $0.count = 1 - /// } - /// } - /// - /// By using a `.failing` effect in our environment we have strengthened the assertion and made - /// the test easier to understand at the same time. We can see, without consulting the reducer - /// itself, that this particular action should not access this effect. - /// - /// - Parameter prefix: A string that identifies this scheduler and will prefix all failure - /// messages. - /// - Returns: An effect that causes a test to fail if it runs. - public static func failing(_ prefix: String) -> Effect { - .fireAndForget { - XCTFail("\(prefix.isEmpty ? "" : "\(prefix) - ")A failing effect ran.") - } - } -} diff --git a/TestSupport/Internal/PriorityQueue.swift b/TestSupport/Internal/PriorityQueue.swift deleted file mode 100644 index c0a1af0..0000000 --- a/TestSupport/Internal/PriorityQueue.swift +++ /dev/null @@ -1,107 +0,0 @@ -import Foundation - -// this is a copy pasted code, so don't wanna bother much with linting -// swiftlint:disable explicit_acl -struct PriorityQueue { - private let _hasHigherPriority: (Element, Element) -> Bool - private let _isEqual: (Element, Element) -> Bool - - private var _elements = [Element]() - - init(hasHigherPriority: @escaping (Element, Element) -> Bool, isEqual: @escaping (Element, Element) -> Bool) { - _hasHigherPriority = hasHigherPriority - _isEqual = isEqual - } - - mutating func enqueue(_ element: Element) { - _elements.append(element) - bubbleToHigherPriority(_elements.count - 1) - } - - func peek() -> Element? { - return _elements.first - } - - var isEmpty: Bool { - return _elements.count == 0 - } - - mutating func dequeue() -> Element? { - guard let front = peek() else { - return nil - } - - removeAt(0) - - return front - } - - mutating func remove(_ element: Element) { - for i in 0 ..< _elements.count { - if _isEqual(_elements[i], element) { - removeAt(i) - return - } - } - } - - private mutating func removeAt(_ index: Int) { - let removingLast = index == _elements.count - 1 - if !removingLast { - _elements.swapAt(index, _elements.count - 1) - } - - _ = _elements.popLast() - - if !removingLast { - bubbleToHigherPriority(index) - bubbleToLowerPriority(index) - } - } - - private mutating func bubbleToHigherPriority(_ initialUnbalancedIndex: Int) { - precondition(initialUnbalancedIndex >= 0) - precondition(initialUnbalancedIndex < _elements.count) - - var unbalancedIndex = initialUnbalancedIndex - - while unbalancedIndex > 0 { - let parentIndex = (unbalancedIndex - 1) / 2 - guard _hasHigherPriority(_elements[unbalancedIndex], _elements[parentIndex]) else { break } - _elements.swapAt(unbalancedIndex, parentIndex) - unbalancedIndex = parentIndex - } - } - - private mutating func bubbleToLowerPriority(_ initialUnbalancedIndex: Int) { - precondition(initialUnbalancedIndex >= 0) - precondition(initialUnbalancedIndex < _elements.count) - - var unbalancedIndex = initialUnbalancedIndex - while true { - let leftChildIndex = unbalancedIndex * 2 + 1 - let rightChildIndex = unbalancedIndex * 2 + 2 - - var highestPriorityIndex = unbalancedIndex - - if leftChildIndex < _elements.count, _hasHigherPriority(_elements[leftChildIndex], _elements[highestPriorityIndex]) { - highestPriorityIndex = leftChildIndex - } - - if rightChildIndex < _elements.count, _hasHigherPriority(_elements[rightChildIndex], _elements[highestPriorityIndex]) { - highestPriorityIndex = rightChildIndex - } - - guard highestPriorityIndex != unbalancedIndex else { break } - _elements.swapAt(highestPriorityIndex, unbalancedIndex) - - unbalancedIndex = highestPriorityIndex - } - } -} - -extension PriorityQueue: CustomDebugStringConvertible { - var debugDescription: String { - return _elements.debugDescription - } -} diff --git a/TestSupport/Internal/VirtualTimeScheduler.swift b/TestSupport/Internal/VirtualTimeScheduler.swift deleted file mode 100644 index 9ac4bcc..0000000 --- a/TestSupport/Internal/VirtualTimeScheduler.swift +++ /dev/null @@ -1,276 +0,0 @@ -import Foundation -import RxSwift - -// swiftlint:disable explicit_acl -// this is mostly a copy pasted code, so don't wanna bother much with linting -/// A modified version of RxSwift's VirtualTimeScheduler to support advancing by relative time -open class _VirtualTimeScheduler: - SchedulerType { - public typealias VirtualTime = Converter.VirtualTimeUnit - public typealias VirtualTimeInterval = Converter.VirtualTimeIntervalUnit - - private var _running: Bool - - private var _clock: VirtualTime - - fileprivate var _schedulerQueue: PriorityQueue> - private var _converter: Converter - - private var _nextId = 0 - - /// - returns: Current time. - public var now: RxTime { - return _converter.convertFromVirtualTime(clock) - } - - /// - returns: Scheduler's absolute time clock value. - public var clock: VirtualTime { - return _clock - } - - /// Creates a new virtual time scheduler. - /// - /// - parameter initialClock: Initial value for the clock. - public init(initialClock: VirtualTime, converter: Converter) { - _clock = initialClock - _running = false - _converter = converter - _schedulerQueue = PriorityQueue( - hasHigherPriority: { - switch converter.compareVirtualTime($0.time, $1.time) { - case .lessThan: - return true - case .equal: - return $0.id < $1.id - case .greaterThan: - return false - } - }, isEqual: { $0 === $1 } - ) - #if TRACE_RESOURCES - _ = Resources.incrementTotal() - #endif - } - - /** - Schedules an action to be executed immediately. - - parameter state: State passed to the action to be executed. - - parameter action: Action to be executed. - - returns: The disposable object used to cancel the scheduled action (best effort). - */ - public func schedule(_ state: StateType, action: @escaping (StateType) -> Disposable) - -> Disposable { - return scheduleRelative(state, dueTime: .nanoseconds(0)) { a in - action(a) - } - } - - /** - Schedules an action to be executed. - - parameter state: State passed to the action to be executed. - - parameter dueTime: Relative time after which to execute the action. - - parameter action: Action to be executed. - - returns: The disposable object used to cancel the scheduled action (best effort). - */ - public func scheduleRelative( - _ state: StateType, dueTime: RxTimeInterval, action: @escaping (StateType) -> Disposable - ) -> Disposable { - let time = now.addingTimeInterval(dueTime.convertToSecondsInterval) - let absoluteTime = _converter.convertToVirtualTime(time) - let adjustedTime = adjustScheduledTime(absoluteTime) - return scheduleAbsoluteVirtual(state, time: adjustedTime, action: action) - } - - /** - Schedules an action to be executed after relative time has passed. - - parameter state: State passed to the action to be executed. - - parameter time: Absolute time when to execute the action. If this is less or equal then `now`, `now + 1` will be used. - - parameter action: Action to be executed. - - returns: The disposable object used to cancel the scheduled action (best effort). - */ - public func scheduleRelativeVirtual( - _ state: StateType, dueTime: VirtualTimeInterval, action: @escaping (StateType) -> Disposable - ) -> Disposable { - let time = _converter.offsetVirtualTime(clock, offset: dueTime) - return scheduleAbsoluteVirtual(state, time: time, action: action) - } - - /** - Schedules an action to be executed at absolute virtual time. - - parameter state: State passed to the action to be executed. - - parameter time: Absolute time when to execute the action. - - parameter action: Action to be executed. - - returns: The disposable object used to cancel the scheduled action (best effort). - */ - public func scheduleAbsoluteVirtual( - _ state: StateType, time: Converter.VirtualTimeUnit, action: @escaping (StateType) -> Disposable - ) -> Disposable { - MainScheduler.ensureExecutingOnScheduler() - - let compositeDisposable = CompositeDisposable() - - let item = VirtualSchedulerItem( - action: { - let dispose = action(state) - return dispose - }, time: time, id: _nextId - ) - - _nextId += 1 - - _schedulerQueue.enqueue(item) - - _ = compositeDisposable.insert(item) - - return compositeDisposable - } - - /// Adjusts time of scheduling before adding item to schedule queue. - open func adjustScheduledTime(_ time: Converter.VirtualTimeUnit) -> Converter.VirtualTimeUnit { - return time - } - - /// Runs the scheduler until it has no scheduled items left. - public func run() { - MainScheduler.ensureExecutingOnScheduler() - - if _running { - return - } - - _running = true - repeat { - guard let next = findNext() else { - break - } - - if _converter.compareVirtualTime(next.time, clock).greaterThan { - _clock = next.time - } - - next.invoke() - _schedulerQueue.remove(next) - } while _running - - _running = false - } - - func findNext() -> VirtualSchedulerItem? { - while let front = _schedulerQueue.peek() { - if front.isDisposed { - _schedulerQueue.remove(front) - continue - } - - return front - } - - return nil - } - - /// Advances the scheduler's clock to the specified time, running all work till that point. - /// - /// - parameter virtualTime: Absolute time to advance the scheduler's clock to. - public func advance(to virtualTime: VirtualTime) { - MainScheduler.ensureExecutingOnScheduler() - - if _running { - fatalError("Scheduler is already running") - } - - _running = true - repeat { - guard let next = findNext() else { - break - } - - if _converter.compareVirtualTime(next.time, virtualTime).greaterThan { - break - } - - if _converter.compareVirtualTime(next.time, clock).greaterThan { - _clock = next.time - } - - next.invoke() - _schedulerQueue.remove(next) - } while _running - - _clock = virtualTime - _running = false - } - - public func advance(by virtualInterval: VirtualTimeInterval) { - let absoluteTime = _converter.offsetVirtualTime(clock, offset: virtualInterval) - - advance(to: absoluteTime) - } - - #if TRACE_RESOURCES - deinit { - _ = Resources.decrementTotal() - } - #endif -} - -// MARK: description - -extension _VirtualTimeScheduler: CustomDebugStringConvertible { - /// A textual representation of `self`, suitable for debugging. - public var debugDescription: String { - return _schedulerQueue.debugDescription - } -} - -final class VirtualSchedulerItem(_: Never) -> A {} + return map(absurd) + } +} diff --git a/RxComposableArchitecture/Classes/Effects/Timer.swift b/RxComposableArchitecture/Classes/Effects/Timer.swift new file mode 100644 index 0000000..a9edb13 --- /dev/null +++ b/RxComposableArchitecture/Classes/Effects/Timer.swift @@ -0,0 +1,110 @@ +// +// Timer.swift +// RxComposableArchitecture_RxComposableArchitecture +// +// Created by Jefferson Setiawan on 02/02/21. +// + +import RxSwift + +extension Effect where Output: RxAbstractInteger { + /// Returns an effect that repeatedly emits the current time of the given scheduler on the given + /// interval. + /// + /// While it is possible to use Foundation's `Timer.publish(every:tolerance:on:in:options:)` API + /// to create a timer in the Composable Architecture, it is not advisable. This API only allows + /// creating a timer on a run loop, which means when writing tests you will need to explicitly + /// wait for time to pass in order to see how the effect evolves in your feature. + /// + /// In the Composable Architecture we test time-based effects like this by using the + /// `TestScheduler`, which allows us to explicitly and immediately advance time forward so that + /// we can see how effects emit. However, because `Timer.publish` takes a concrete `RunLoop` as + /// its scheduler, we can't substitute in a `TestScheduler` during tests`. + /// + /// That is why we provide the `Effect.timer` effect. It allows you to create a timer that works + /// with any scheduler, not just a run loop, which means you can use a `DispatchQueue` or + /// `RunLoop` when running your live app, but use a `TestScheduler` in tests. + /// + /// To start and stop a timer in your feature you can create the timer effect from an action + /// and then use the `.cancel(id:)` effect to stop the timer: + /// + /// struct AppState { + /// var count = 0 + /// } + /// + /// enum AppAction { + /// case startButtonTapped, stopButtonTapped, timerTicked + /// } + /// + /// struct AppEnvironment { + /// var mainQueue: AnySchedulerOf + /// } + /// + /// let appReducer = Reducer { state, action, env in + /// struct TimerId: Hashable {} + /// + /// switch action { + /// case .startButtonTapped: + /// return Effect.timer(id: TimerId(), every: 1, on: env.mainQueue) + /// .map { _ in .timerTicked } + /// + /// case .stopButtonTapped: + /// return .cancel(id: TimerId()) + /// + /// case let .timerTicked: + /// state.count += 1 + /// return .none + /// } + /// + /// Then to test the timer in this feature you can use a test scheduler to advance time: + /// + /// func testTimer() { + /// let scheduler = DispatchQueue.testScheduler + /// + /// let store = TestStore( + /// initialState: .init(), + /// reducer: appReducer, + /// envirnoment: .init( + /// mainQueue: scheduler.eraseToAnyScheduler() + /// ) + /// ) + /// + /// store.assert( + /// .send(.startButtonTapped), + /// + /// .do { scheduler.advance(by: .seconds(1)) }, + /// .receive(.timerTicked) { $0.count = 1 }, + /// + /// .do { scheduler.advance(by: .seconds(5)) }, + /// .receive(.timerTicked) { $0.count = 2 }, + /// .receive(.timerTicked) { $0.count = 3 }, + /// .receive(.timerTicked) { $0.count = 4 }, + /// .receive(.timerTicked) { $0.count = 5 }, + /// .receive(.timerTicked) { $0.count = 6 }, + /// + /// .send(.stopButtonTapped) + /// ) + /// } + /// + /// - Note: This effect is only meant to be used with features built in the Composable + /// Architecture, and returned from a reducer. If you want a testable alternative to + /// Foundation's `Timer.publish` you can use the publisher `Publishers.Timer` that is included + /// in this library via the + /// [`CombineSchedulers`](https://github.com/pointfreeco/combine-schedulers) module. + /// + /// - Parameters: + /// - id: The effect's identifier. + /// - interval: The time interval on which to publish events. For example, a value of `0.5` + /// publishes an event approximately every half-second. + /// - scheduler: The scheduler on which the timer runs. + public static func timer( + id: AnyHashable, + every interval: RxTimeInterval, + on scheduler: SchedulerType + ) -> Effect { + Observable + .interval(interval, scheduler: scheduler) + .eraseToEffect() + .cancellable(id: id) + } +} diff --git a/RxComposableArchitecture/Classes/Export.swift b/RxComposableArchitecture/Classes/Export.swift new file mode 100644 index 0000000..55ecc58 --- /dev/null +++ b/RxComposableArchitecture/Classes/Export.swift @@ -0,0 +1 @@ +@_exported import CasePaths diff --git a/RxComposableArchitecture/Classes/IdentifiedArray.swift b/RxComposableArchitecture/Classes/IdentifiedArray.swift new file mode 100644 index 0000000..b7ae6cd --- /dev/null +++ b/RxComposableArchitecture/Classes/IdentifiedArray.swift @@ -0,0 +1,347 @@ +// +// IdentifiedArray.swift +// RxComposableArchitecture_RxComposableArchitecture +// +// Created by Jefferson Setiawan on 16/07/20. +// + +import Foundation + +/// An array of elements that can be identified by a given key path. +/// +/// A useful container of state that is intended to interface with `ForEachStore`. For example, +/// your application may model a counter in an identifiable fashion: +/// +/// struct CounterState: Differentiable { +/// let id: UUID +/// var count = 0 +/// } +/// enum CounterAction { case incr, decr } +/// let counterReducer = Reducer { ... } +/// +/// This domain can be pulled back to a larger domain with the `forEach` method: +/// +/// struct AppState { var counters = IdentifiedArray(id: \.self) } +/// enum AppAction { case counter(id: UUID, action: CounterAction) } +/// let appReducer = counterReducer.forEach( +/// state: \AppState.counters, +/// action: /AppAction.counter(id:action:), +/// environment: { $0 } +/// ) +/// +/// And then SwiftUI can work with this array of identified elements in a list view: +/// +/// struct AppView: View { +/// let store: Store +/// +/// var body: some View { +/// List { +/// ForEachStore( +/// self.store.scope(state: \.counters, action: AppAction.counter(id:action)) +/// content: CounterView.init(store:) +/// ) +/// } +/// } +/// } +public struct IdentifiedArray: MutableCollection, RandomAccessCollection + where ID: Hashable { + /// A key path to a value that identifies an element. + public let id: KeyPath + + /// A raw array of each element's identifier. + public private(set) var ids: [ID] + + /// A raw array of the underlying elements. + public var elements: [Element] { Array(self) } + + // TODO: Support multiple elements with the same identifier but different data. + private var dictionary: [ID: Element] + + /// Initializes an identified array with a sequence of elements and a key + /// path to an element's identifier. + /// + /// - Parameters: + /// - elements: A sequence of elements. + /// - id: A key path to a value that identifies an element. + public init(_ elements: S, id: KeyPath) where S: Sequence, S.Element == Element { + self.id = id + + let idsAndElements = elements.map { (id: $0[keyPath: id], element: $0) } + ids = idsAndElements.map { $0.id } + dictionary = Dictionary(idsAndElements, uniquingKeysWith: { $1 }) + } + + /// Initializes an empty identified array with a key path to an element's + /// identifier. + /// + /// - Parameter id: A key path to a value that identifies an element. + public init(id: KeyPath) { + self.init([], id: id) + } + + public var startIndex: Int { ids.startIndex } + public var endIndex: Int { ids.endIndex } + + public func index(after i: Int) -> Int { + ids.index(after: i) + } + + public func index(before i: Int) -> Int { + ids.index(before: i) + } + + public subscript(position: Int) -> Element { + // NB: `_read` crashes Xcode Preview compilation. + get { dictionary[ids[position]]! } + _modify { yield &self.dictionary[self.ids[position]]! } + } + + #if DEBUG + /// Direct access to an element by its identifier. + /// + /// - Parameter id: The identifier of element to access. Must be a valid identifier for an + /// element of the array and will _not_ insert elements that are not already in the array, or + /// remove elements when passed `nil`. Use `append` or `insert(_:at:)` to insert elements. Use + /// `remove(id:)` to remove an element by its identifier. + /// - Returns: The element. + public subscript(id id: ID) -> Element? { + get { dictionary[id] } + set { + if newValue != nil, dictionary[id] == nil { + fatalError( + """ + Can't update element with identifier \(id) because no such element exists in the array. + + If you are trying to insert an element into the array, use the "append" or "insert" \ + methods. + """ + ) + } + if newValue == nil { + fatalError( + """ + Can't update element with identifier \(id) with nil. + + If you are trying to remove an element from the array, use the "remove(id:) method." + """ + ) + } + if newValue![keyPath: self.id] != id { + fatalError( + """ + Can't update element at identifier \(id) with element having mismatched identifier \ + \(newValue![keyPath: self.id]). + + If you would like to replace the element with identifier \(id) with an element with a \ + new identifier, remove the existing element and then insert the new element, instead. + """ + ) + } + dictionary[id] = newValue + } + } + + #else + public subscript(id id: ID) -> Element? { + // NB: `_read` crashes Xcode Preview compilation. + get { dictionary[id] } + _modify { yield &self.dictionary[id] } + } + #endif + + public mutating func insert(_ newElement: Element, at i: Int) { + let id = newElement[keyPath: self.id] + dictionary[id] = newElement + ids.insert(id, at: i) + } + + public mutating func insert( + contentsOf newElements: C, at i: Int + ) where C: Collection, Element == C.Element { + for newElement in newElements.reversed() { + insert(newElement, at: i) + } + } + + /// Removes and returns the element with the specified identifier. + /// + /// - Parameter id: The identifier of the element to remove. + /// - Returns: The removed element. + @discardableResult + public mutating func remove(id: ID) -> Element { + let element = dictionary[id] + assert(element != nil, "Unexpectedly found nil while removing an identified element.") + dictionary[id] = nil + ids.removeAll(where: { $0 == id }) + return element! + } + + @discardableResult + public mutating func remove(at position: Int) -> Element { + remove(id: ids.remove(at: position)) + } + + public mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows { + var ids: [ID] = [] + for (index, id) in zip(self.ids.indices, self.ids).reversed() { + if try shouldBeRemoved(dictionary[id]!) { + self.ids.remove(at: index) + ids.append(id) + } + } + for id in ids where !self.ids.contains(id) { + self.dictionary[id] = nil + } + } + + public mutating func remove(atOffsets offsets: IndexSet) { + for offset in offsets.reversed() { + _ = remove(at: offset) + } + } + + /// Unavailable, if needed, please implement this, The implementation of `move` is in SwitUI.Collection.Array +// public mutating func move(fromOffsets source: IndexSet, toOffset destination: Int) { +// self.ids.move(fromOffsets: source, toOffset: destination) +// } + + public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows { + try ids.sort { + try areInIncreasingOrder(self.dictionary[$0]!, self.dictionary[$1]!) + } + } + + public mutating func shuffle(using generator: inout T) where T: RandomNumberGenerator { + ids.shuffle(using: &generator) + } + + public mutating func shuffle() { + var rng = SystemRandomNumberGenerator() + shuffle(using: &rng) + } + + public mutating func reverse() { + ids.reverse() + } +} + +extension IdentifiedArray: CustomDebugStringConvertible { + public var debugDescription: String { + elements.debugDescription + } +} + +extension IdentifiedArray: CustomReflectable { + public var customMirror: Mirror { + Mirror(reflecting: elements) + } +} + +extension IdentifiedArray: CustomStringConvertible { + public var description: String { + elements.description + } +} + +extension IdentifiedArray: Decodable where Element: Decodable & HashDiffable, ID == Element.IdentifierType { + public init(from decoder: Decoder) throws { + self.init(try [Element](from: decoder)) + } +} + +extension IdentifiedArray: Encodable where Element: Encodable { + public func encode(to encoder: Encoder) throws { + try elements.encode(to: encoder) + } +} + +extension IdentifiedArray: Equatable where Element: Equatable {} + +extension IdentifiedArray: Hashable where Element: Hashable {} + +extension IdentifiedArray where Element: Comparable { + public mutating func sort() { + sort(by: <) + } +} + +extension IdentifiedArray: ExpressibleByArrayLiteral where Element: HashDiffable, ID == Element.IdentifierType { + public init(arrayLiteral elements: Element...) { + self.init(elements) + } +} + +extension IdentifiedArray where Element: HashDiffable, ID == Element.IdentifierType { + public init(_ elements: S) where S: Sequence, S.Element == Element { + self.init(elements, id: \.id) + } +} + +extension IdentifiedArray: RangeReplaceableCollection + where Element: HashDiffable, ID == Element.IdentifierType { + public init() { + self.init([], id: \.id) + } + + public mutating func replaceSubrange(_ subrange: R, with newElements: C) + where C: Collection, R: RangeExpression, Element == C.Element, Index == R.Bound { + let replacingIds = ids[subrange] + let newIds = newElements.map { $0.id } + ids.replaceSubrange(subrange, with: newIds) + + for element in newElements { + dictionary[element.id] = element + } + + for id in replacingIds where !ids.contains(id) { + self.dictionary[id] = nil + } + } +} + +/// A convenience type to specify an `IdentifiedArray` by an identifiable element. +public typealias IdentifiedArrayOf = IdentifiedArray + where Element: HashDiffable + +extension IdentifiedArrayOf where Element: HashDiffable, Element.IdentifierType == ID { + public func removeDuplicates() -> Self { + /// This table will contain `diffIdentifier` as the `key` and object `type` as the value + var tableOfObjectType = [AnyHashable: Any.Type]() + + var uniqueObjects = IdentifiedArrayOf() + + forEach { currentObject in + /// Get current object identifier + let currentId = currentObject.id + + /// Get current object type from Type Erasure base object + let currentObjectType = type(of: currentObject) + + /// Check if `currentId` is already registered on `Table Bank of Identifiers Type` + /// If `yes` > Get object type with current identifier from `Table Bank of Identifiers Type` + /// If `no` > Then return `nil` + let previousObjectType = tableOfObjectType[currentId] + + /// Check whether current object type is the same with previous object type(if exist) fetched from `Table Bank of Identifiers Type` + /// If `currentId` already exist on `Table Bank of Identifiers Type` but the type is different it's not counted as _**duplicates**_ + if currentObjectType != previousObjectType { + tableOfObjectType[currentId] = currentObjectType + uniqueObjects.append(currentObject) + } + } + + return uniqueObjects + } +} + +extension IdentifiedArray { + public var isNotEmpty: Bool { + return !isEmpty + } + + @inlinable + public subscript(safe index: Index) -> Element? { + guard startIndex <= index, index < endIndex else { return nil } + return self[index] + } +} diff --git a/RxComposableArchitecture/Classes/IfLet.swift b/RxComposableArchitecture/Classes/IfLet.swift new file mode 100644 index 0000000..ce41913 --- /dev/null +++ b/RxComposableArchitecture/Classes/IfLet.swift @@ -0,0 +1,78 @@ +import RxSwift + +extension Store { + /// Subscribes to updates when a store containing optional state goes from `nil` to non-`nil` or + /// non-`nil` to `nil`. + /// + /// This is useful for handling navigation in UIKit. The state for a screen that you want to + /// navigate to can be held as an optional value in the parent, and when that value switches + /// from `nil` to non-`nil` you want to trigger a navigation and hand the detail view a `Store` + /// whose domain has been scoped to just that feature: + /// + /// class MasterViewController: UIViewController { + /// let store: Store + /// var cancellables: Set = [] + /// ... + /// func viewDidLoad() { + /// ... + /// self.store + /// .scope(state: \.optionalDetail, action: MasterAction.detail) + /// .ifLet( + /// then: { [weak self] detailStore in + /// self?.navigationController?.pushViewController( + /// DetailViewController(store: detailStore), + /// animated: true + /// ) + /// }, + /// else: { [weak self] in + /// guard let self = self else { return } + /// self.navigationController?.popToViewController(self, animated: true) + /// } + /// ) + /// .store(in: &self.cancellables) + /// } + /// } + /// + /// - Parameters: + /// - unwrap: A function that is called with a store of non-optional state whenever the store's + /// optional state goes from `nil` to non-`nil`. + /// - else: A function that is called whenever the store's optional state goes from non-`nil` to + /// `nil`. + /// - Returns: A cancellable associated with the underlying subscription. + public func ifLet( + then unwrap: @escaping (Store) -> Void, + else: @escaping () -> Void + ) -> Disposable where State == Wrapped? { + let elseDisposable = scope( + state: { state in + state + .distinctUntilChanged { ($0 != nil) == ($1 != nil) } + } + ) + .subscribe(onNext: { if $0.state == nil { `else`() } }) + + let unwrapDisposable = scope( + state: { state in + state + .distinctUntilChanged { ($0 != nil) == ($1 != nil) } + .compactMap { $0 } + }, + action: { $0 } + ) + .subscribe(onNext: unwrap) + + return CompositeDisposable(unwrapDisposable, elseDisposable) + } + + /// An overload of `ifLet(then:else:)` for the times that you do not want to handle the `else` + /// case. + /// + /// - Parameter unwrap: A function that is called with a store of non-optional state whenever the + /// store's optional state goes from `nil` to non-`nil`. + /// - Returns: A cancellable associated with the underlying subscription. + public func ifLet( + then unwrap: @escaping (Store) -> Void + ) -> Disposable where State == Wrapped? { + ifLet(then: unwrap, else: {}) + } +} diff --git a/RxComposableArchitecture/Classes/Internal/AnyDisposable.swift b/RxComposableArchitecture/Classes/Internal/AnyDisposable.swift new file mode 100644 index 0000000..51075fe --- /dev/null +++ b/RxComposableArchitecture/Classes/Internal/AnyDisposable.swift @@ -0,0 +1,29 @@ +// +// AnyDisposable.swift +// RxComposableArchitecture_RxComposableArchitecture +// +// Created by Jefferson Setiawan on 02/02/21. +// + +import Foundation +import RxSwift + +internal final class AnyDisposable: Disposable, Hashable { + internal let _dispose: () -> Void + + internal init(_ disposable: Disposable) { + _dispose = disposable.dispose + } + + internal func dispose() { + _dispose() + } + + internal static func == (lhs: AnyDisposable, rhs: AnyDisposable) -> Bool { + return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) + } + + internal func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } +} diff --git a/RxComposableArchitecture/Classes/Internal/Deprecated.swift b/RxComposableArchitecture/Classes/Internal/Deprecated.swift new file mode 100644 index 0000000..f1516ab --- /dev/null +++ b/RxComposableArchitecture/Classes/Internal/Deprecated.swift @@ -0,0 +1,13 @@ +// +// Deprecated.swift +// RxComposableArchitecture_RxComposableArchitecture +// +// Created by Jefferson Setiawan on 22/03/21. +// + +extension Reducer { + @available(*, deprecated, renamed: "optional()") + public var optional: Reducer { + self.optional() + } +} diff --git a/RxComposableArchitecture/Classes/Internal/Locking.swift b/RxComposableArchitecture/Classes/Internal/Locking.swift new file mode 100644 index 0000000..202e2a1 --- /dev/null +++ b/RxComposableArchitecture/Classes/Internal/Locking.swift @@ -0,0 +1,26 @@ +// +// Locking.swift +// RxComposableArchitecture_RxComposableArchitecture +// +// Created by Jefferson Setiawan on 02/02/21. +// + +import Foundation + +extension UnsafeMutablePointer where Pointee == os_unfair_lock_s { + @inlinable @discardableResult + internal func sync(_ work: () -> R) -> R { + os_unfair_lock_lock(self) + defer { os_unfair_lock_unlock(self) } + return work() + } +} + +extension NSRecursiveLock { + @inlinable @discardableResult + internal func sync(work: () -> R) -> R { + lock() + defer { self.unlock() } + return work() + } +} diff --git a/RxComposableArchitecture/Classes/OptionalPaths.swift b/RxComposableArchitecture/Classes/OptionalPaths.swift new file mode 100644 index 0000000..1343b4e --- /dev/null +++ b/RxComposableArchitecture/Classes/OptionalPaths.swift @@ -0,0 +1,275 @@ +// + +import Foundation + +public protocol WritablePath { + associatedtype Root + associatedtype Value + func extract(from root: Root) -> Value? + func set(into root: inout Root, _ value: Value) +} + +extension WritableKeyPath: WritablePath { + public func extract(from root: Root) -> Value? { + root[keyPath: self] + } + + public func set(into root: inout Root, _ value: Value) { + root[keyPath: self] = value + } +} + +extension CasePath: WritablePath { + public func set(into root: inout Root, _ value: Value) { + root = embed(value) + } +} + +extension OptionalPath: WritablePath {} + +public struct OptionalPath { + private let _extract: (Root) -> Value? + private let _set: (inout Root, Value) -> Void + + public init( + extract: @escaping (Root) -> Value?, + set: @escaping (inout Root, Value) -> Void + ) { + _extract = extract + _set = set + } + + public func extract(from root: Root) -> Value? { + _extract(root) + } + + public func set(into root: inout Root, _ value: Value) { + _set(&root, value) + } + + public init( + _ keyPath: WritableKeyPath + ) { + self.init( + extract: { $0[keyPath: keyPath] }, + set: { $0[keyPath: keyPath] = $1 } + ) + } + + public init( + _ casePath: CasePath + ) { + self.init( + extract: casePath.extract(from:), + set: { $0 = casePath.embed($1) } + ) + } + + public func appending( + path: OptionalPath + ) -> OptionalPath { + .init( + extract: { self.extract(from: $0).flatMap(path.extract(from:)) }, + set: { root, appendedValue in + guard var value = self.extract(from: root) else { return } + path.set(into: &value, appendedValue) + self.set(into: &root, value) + } + ) + } + + public func appending( + path: CasePath + ) -> OptionalPath { + appending(path: .init(path)) + } + + public func appending( + path: WritableKeyPath + ) -> OptionalPath { + .init( + extract: { self.extract(from: $0).map { $0[keyPath: path] } }, + set: { root, appendedValue in + guard var value = self.extract(from: root) else { return } + value[keyPath: path] = appendedValue + self.set(into: &root, value) + } + ) + } + + // TODO: Is it safe to keep this overload? + public func appending( + path: WritableKeyPath + ) -> OptionalPath { + appending(path: .init(path)) + } +} + +extension CasePath { + public func appending( + path: OptionalPath + ) -> OptionalPath { + OptionalPath(self).appending(path: path) + } + + public func appending( + path: WritableKeyPath + ) -> OptionalPath { + OptionalPath(self).appending(path: path) + } + + // TODO: Is it safe to keep this overload? + public func appending( + path: WritableKeyPath + ) -> OptionalPath { + OptionalPath(self).appending(path: path) + } +} + +extension WritableKeyPath { + public func appending( + path: OptionalPath + ) -> OptionalPath { + OptionalPath( + extract: { path.extract(from: $0[keyPath: self]) }, + set: { root, appendedValue in path.set(into: &root[keyPath: self], appendedValue) } + ) + } + + public func appending( + path: CasePath + ) -> OptionalPath { + appending(path: .init(path)) + } +} + +extension OptionalPath where Root == Value { + public static var `self`: OptionalPath { + .init(.self) + } +} + +extension OptionalPath where Root == Value? { + public static var some: OptionalPath { + .init(/Optional.some) + } +} + +// MARK: - Operator + +precedencegroup OptionalPathCompositionPrecedence { + associativity: right +} + +infix operator ..: OptionalPathCompositionPrecedence + +extension OptionalPath { + /// Returns a new Optional path created by appending the given optional path to this one. + /// + /// The operator version of `OptionalPath.appending(path:)`. Use this method to extend this optional path to the value type of another path. + /// + /// - Parameters: + /// - lhs: A optional path from a root to a value. + /// - rhs: A optional path from the first optional path's value to some other appended value. + /// - Returns: A new optional path from the first optional path's root to the second optional path's value. + public static func .. ( + lhs: OptionalPath, + rhs: OptionalPath + ) -> OptionalPath { + return lhs.appending(path: rhs) + } + + /// Returns a new Optional path created by appending the given writeable path to this one. + /// + /// The operator version of `OptionalPath.appending(path:)`. Use this method to extend this optional path to the value type of another path. + /// + /// - Parameters: + /// - lhs: A optional path from a root to a value. + /// - rhs: A writeable path from the first optional path's value to some other appended value. + /// - Returns: A new optional path from the first optional path's root to the second writeable path's value. + public static func .. ( + lhs: OptionalPath, + rhs: WritableKeyPath + ) -> OptionalPath { + return lhs.appending(path: rhs) + } + + /// Returns a new Optional path created by appending the given case path to this one. + /// + /// The operator version of `OptionalPath.appending(path:)`. Use this method to extend this optional path to the value type of another path. + /// + /// - Parameters: + /// - lhs: A optional path from a root to a value. + /// - rhs: A case path from the first optional path's value to some other appended value. + /// - Returns: A new optional path from the first optional path's root to the second case path's value. + public static func .. ( + lhs: OptionalPath, + rhs: CasePath + ) -> OptionalPath { + return lhs.appending(path: rhs) + } +} + +extension WritableKeyPath { + /// Returns a new Optional path created by appending the given optional path to this one. + /// + /// The operator version of `WritableKeyPath.appending(path:)`. Use this method to extend this writeable path to the value type of another optional path. + /// + /// - Parameters: + /// - lhs: A writeable path from a root to a value. + /// - rhs: A optional path from the first optional path's value to some other appended value. + /// - Returns: A new optional path from the first writeable path's root to the second optional path's value. + public static func .. ( + lhs: WritableKeyPath, + rhs: OptionalPath + ) -> OptionalPath { + return lhs.appending(path: rhs) + } + + /// Returns a new Optional path created by appending the given case path to this one. + /// + /// The operator version of `WritableKeyPath.appending(path:)`. Use this method to extend this writeable path to the value type of another case path. + /// + /// - Parameters: + /// - lhs: A writeable path from a root to a value. + /// - rhs: A case path from the first optional path's value to some other appended value. + /// - Returns: A new optional path from the first writeable path's root to the second case path's value. + public static func .. ( + lhs: WritableKeyPath, + rhs: CasePath + ) -> OptionalPath { + return lhs.appending(path: rhs) + } +} + +extension CasePath { + /// Returns a new Optional path created by appending the given optional path to this one. + /// + /// The operator version of `CasePath.appending(path:)`. Use this method to extend this case path to the value type of another optional path. + /// + /// - Parameters: + /// - lhs: A case path from a root to a value. + /// - rhs: A optional path from the first optional path's value to some other appended value. + /// - Returns: A new optional path from the first case path's root to the second optional path's value. + public static func .. ( + lhs: CasePath, + rhs: OptionalPath + ) -> OptionalPath { + return lhs.appending(path: rhs) + } + + /// Returns a new Optional path created by appending the given optional path to this one. + /// + /// The operator version of `CasePath.appending(path:)`. Use this method to extend this case path to the value type of another writeable path. + /// + /// - Parameters: + /// - lhs: A case path from a root to a value. + /// - rhs: A writeable path from the first optional path's value to some other appended value. + /// - Returns: A new optional path from the first case path's root to the second writeable path's value. + public static func .. ( + lhs: CasePath, + rhs: WritableKeyPath + ) -> OptionalPath { + return lhs.appending(path: rhs) + } +} diff --git a/RxComposableArchitecture/Classes/PropertyWrapper/SingleSelection.swift b/RxComposableArchitecture/Classes/PropertyWrapper/SingleSelection.swift new file mode 100644 index 0000000..5d57ae3 --- /dev/null +++ b/RxComposableArchitecture/Classes/PropertyWrapper/SingleSelection.swift @@ -0,0 +1,255 @@ +// +// SingleSelection.swift +// RxComposableArchitecture +// +// Created by Wendy Liga on 25/05/21. +// + +/** + PropertyWrapper to help manage single selection on `IdentifiedArray`. + + if you have state like this + + ```swift + struct Item: HashDiffable, Equatable { + let id: Int + var isSelected: Bool + } + + let itemReducer = Reducer... { + switch action { + case .tap: + state.isSelected = true + } + } + + struct State { + var items: IdentifiedArrayOf + } + + let stateReducer = Reducer... { + switch action { + case .item(id, .tap): + state.items = state.items.map { item in + var newItem = item + newItem.isSelected = newItem.id == id + + return newItem + } + } + } + ``` + + as you can see you need to handle the action on parent too, loop each item to select only on selected `id` + here, `SingleSelection` comes to rescue. + + ```swift + struct Item: HashDiffable, Equatable { + let id: Int + var isSelected: Bool + } + + let itemReducer = Reducer... { + switch action { + case .tap: + state.isSelected = true + } + } + + struct State { + @SingleSelection([], selection: \.isSelected) + var items: IdentifiedArrayOf + } + + let stateReducer = Reducer... { + switch action { + case .item(id, .tap): + # you don't need to handle it manually on parent side # + } + } + ``` + + ## how to use + ``` + @SingleSelection([], selection: \.isSelected) + ``` + by using `SingleSelection`, you don't need to handle the single selection manually, + just set selection from child side, and `SingleSelection` will automatically unselect previous one and select the new one. + + your array item also can conform to `Selectable`, so you don't need to manually give the keypath + + ```swift + struct Item: HashDiffable, Equatable, Selectable { + let id: Int + var isSelected: Bool + } + + struct State { + @SingleSelection + var items: IdentifiedArrayOf + } + ``` + + - Complexity: O(n) + everytime array is mutated, SingleSelection need to check if only one item is selected every time mutation happend. + */ +@propertyWrapper +public struct SingleSelection where Element: HashDiffable { + private let _getSelection: (Element) -> Bool + private let _setSelection: (inout Element, Bool) -> Void + + private var _currentSelectedId: Element.IdentifierType? + private var _wrappedValue: IdentifiedArrayOf + + public var wrappedValue: IdentifiedArrayOf { + get { + _wrappedValue + } + set { + set(newValue) + } + } + + /** + SingleSelection + + - Parameters: + - wrappedValue: initial value + - extract: map given `Element` to `isSelected` Bool + - set: closure to set new Bool value to `Element` `isSelected` + */ + public init( + _ wrappedValue: IdentifiedArrayOf = [], + extract: @escaping (Element) -> Bool, + set: @escaping (inout Element, Bool) -> Void + ) { + _wrappedValue = wrappedValue + _getSelection = extract + _setSelection = set + + // initial setup + self.set(wrappedValue) + } + + /** + SingleSelection + + - Parameters: + - wrappedValue: initial value + - selection: writeable keypath to `isSelected` value. + */ + public init( + _ wrappedValue: IdentifiedArrayOf = [], + selection path: WritableKeyPath + ) { + self.init( + wrappedValue, + extract: { $0[keyPath: path] }, + set: { $0[keyPath: path] = $1 } + ) + } + + /** + logic that will run before setting new value to wrappedValue + + here we will try to + - eleminate multiple selected item (only 1 will last) + - deselect `_currentSelectedId` if neccessary + - will keep track of current selected id on `_currentSelectedId` + */ + private mutating func set(_ values: IdentifiedArrayOf) { + /// filter all element where `isSelected` is true, and map it to its `Element.IdentifierType` or `id` + let selectedIds = values + .compactMap { value -> Element.IdentifierType? in + guard _getSelection(value) else { return nil } + return value.id + } + + /// if current selected id is nil, we will search for candidate + guard let currentSelectedId = _currentSelectedId else { + var _values = values + + // if new given array has more than 1 item selected, will select first index and delesect else. + if selectedIds.count > 1 { + // loop every other id that is selected other than intented + (1 ..< selectedIds.endIndex).forEach { offset in + let id = selectedIds[offset] + // if not nil + _values[id: id].map { value in + var value = value + _setSelection(&value, false) // unselect + _values[id: id] = value + } + } + } + + // save + _currentSelectedId = selectedIds.first + _wrappedValue = _values + + return + } + + /// if current selected id count is 1, then single selection rules is fulfilled, we don't need to continue. + guard selectedIds.count > 1 else { + // save new value + _wrappedValue = values + return + } + + // delesect current selected + var withoutCurrentSelection = values + withoutCurrentSelection[id: currentSelectedId].map { value in + var value = value + _setSelection(&value, false) + withoutCurrentSelection[id: currentSelectedId] = value + } + + // clear current selection + _currentSelectedId = nil + + // recuversivly search selected id and remove duplicate if necessary + set(withoutCurrentSelection) + } +} + +public protocol Selectable { + var isSelected: Bool { get set } +} + +extension Selectable { + public var path: WritableKeyPath { + \.isSelected + } +} + +extension SingleSelection where Element: Selectable { + /** + SingleSelection + + - Parameters: + - wrappedValue: initial value + */ + public init(_ wrappedValue: IdentifiedArrayOf = []) { + self.init( + wrappedValue, + extract: \.isSelected, + set: { $0.isSelected = $1 } + ) + } +} + +extension SingleSelection: Equatable where Element: Equatable { + // manually conform equatable + // because there're closure + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs._currentSelectedId == rhs._currentSelectedId && lhs._wrappedValue == rhs._wrappedValue + } +} + +extension SingleSelection: Hashable where Element: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(_currentSelectedId) + hasher.combine(_wrappedValue) + } +} diff --git a/RxComposableArchitecture/Classes/PropertyWrapper/UniqueElements.swift b/RxComposableArchitecture/Classes/PropertyWrapper/UniqueElements.swift new file mode 100644 index 0000000..92273d2 --- /dev/null +++ b/RxComposableArchitecture/Classes/PropertyWrapper/UniqueElements.swift @@ -0,0 +1,59 @@ +// +// UniqueElements.swift +// RxComposableArchitecture_RxComposableArchitecture +// +// Created by Kensen on 07/01/21. +// + + +/** + Property wrapper to reduce boiler plate code to remove duplicates from a collection of HashDiffable. + + ``` + struct Element: Equatable, HashDiffable {} + + struct ParentState: Equatable { + @UniqueElements var arrayState: [Element] + @UniqueElements var identifiedArrayState: IdentifiedArrayOf + } + ``` + + Everytime we set arrayState or identifiedArrayState, it will automatically remove all duplicates, making them having only unique elements. + Using this property wrapper will help reduce data disrepancy between the source of data and components that use them + */ + +@propertyWrapper +public struct UniqueElements: Equatable where State: Collection & Equatable, State.Element: HashDiffable { + public var wrappedValue: State { + didSet { + wrappedValue = Self.getUniqueState(wrappedValue) + } + } + + public init(wrappedValue: State) { + self.wrappedValue = Self.getUniqueState(wrappedValue) + } + + private static func getUniqueState(_ state: State) -> State { + if let array = state as? [State.Element] { + return (array.removeDuplicates() as? State) ?? state + } else if let identifiedArray = state as? IdentifiedArrayOf { + return (identifiedArray.removeDuplicates() as? State) ?? state + } else { + assertionFailure("\(type(of: state)) is not supported yet") + return state + } + } +} + +extension UniqueElements: Decodable where State: Decodable { + public init(value: State) { + wrappedValue = Self.getUniqueState(value) + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let rawValue = try container.decode(State.self) + self.init(value: rawValue) + } +} diff --git a/RxComposableArchitecture/Classes/Reducer.swift b/RxComposableArchitecture/Classes/Reducer.swift new file mode 100644 index 0000000..ad9a16c --- /dev/null +++ b/RxComposableArchitecture/Classes/Reducer.swift @@ -0,0 +1,624 @@ +import CasePaths +import Darwin +import RxSwift + +public struct Reducer { + private let reducer: (inout State, Action, Environment) -> Effect + + public init(_ reducer: @escaping (inout State, Action, Environment) -> Effect) { + self.reducer = reducer + } + + public static var empty: Reducer { + Self { _, _, _ in .none } + } + + public static func combine(_ reducers: Reducer...) -> Reducer { + .combine(reducers) + } + + public static func combine(_ reducers: [Reducer]) -> Reducer { + Self { value, action, environment in + .merge(reducers.map { $0.reducer(&value, action, environment) }) + } + } + + /// Transforms a reducer that works on local state, action and environment into one that works on + /// global state, action and environment. It accomplishes this by providing 3 transformations to + /// the method: + /// + /// * A writable key path that can get/set a piece of local state from the global state. + /// * A case path that can extract/embed a local action into a global action. + /// * A function that can transform the global environment into a local environment. + /// + /// This operation is important for breaking down large reducers into small ones. When used with + /// the `combine` operator you can define many reducers that work on small pieces of domain, and + /// then _pull them back_ and _combine_ them into one big reducer that works on a large domain. + /// + /// // Global domain that holds a local domain: + /// struct AppState { var settings: SettingsState, /* rest of state */ } + /// struct AppAction { case settings(SettingsAction), /* other actions */ } + /// struct AppEnvironment { var settings: SettingsEnvironment, /* rest of dependencies */ } + /// + /// // A reducer that works on the local domain: + /// let settingsReducer = Reducer { ... } + /// + /// // Pullback the settings reducer so that it works on all of the app domain: + /// let appReducer: Reducer = .combine( + /// settingsReducer.pullback( + /// state: \.settings, + /// action: /AppAction.settings, + /// environment: { $0.settings } + /// ), + /// + /// /* other reducers */ + /// ) + /// + /// - Parameters: + /// - toLocalState: A writable path (`WritableKeyPath`, `CasePath`, or `OptionalPath`) that can + /// get/set `State` inside `GlobalState`. + /// - toLocalAction: A writable path (`WritableKeyPath`, `CasePath`, or `OptionalPath`) that can + /// get/set `Action` inside `GlobalAction`. + /// - toLocalEnvironment: A function that transforms `GlobalEnvironment` into `Environment`. + /// - Returns: A reducer that works on `GlobalState`, `GlobalAction`, `GlobalEnvironment`. + public func pullback( + state toLocalState: WritableKeyPath, + action toLocalAction: CasePath, + environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment + ) -> Reducer { + .init { globalState, globalAction, globalEnvironment in + guard let localAction = toLocalAction.extract(from: globalAction) else { return .none } + return self.reducer( + &globalState[keyPath: toLocalState], + localAction, + toLocalEnvironment(globalEnvironment) + ) + .map(toLocalAction.embed) + } + } + + public func pullback( + state toLocalState: StatePath, + action toLocalAction: ActionPath, + environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment + ) -> Reducer + where + StatePath: WritablePath, StatePath.Root == GlobalState, StatePath.Value == State, + ActionPath: WritablePath, ActionPath.Root == GlobalAction, ActionPath.Value == Action { + return .init { globalState, globalAction, globalEnvironment in + guard + var localState = toLocalState.extract(from: globalState), + let localAction = toLocalAction.extract(from: globalAction) + else { return .none } + let effect = + self + .reducer(&localState, localAction, toLocalEnvironment(globalEnvironment)) + .map { localAction -> GlobalAction in + var globalAction = globalAction + toLocalAction.set(into: &globalAction, localAction) + return globalAction + } + toLocalState.set(into: &globalState, localState) + return effect + } + } + + /// Transforms a reducer that works on non-optional state into one that works on optional state by + /// only running the non-optional reducer when state is non-nil. + /// + /// Often used in tandem with `pullback` to transform a reducer on a non-optional child domain + /// into a reducer that can be combined with a reducer on a parent domain that contains some + /// optional child domain: + /// + /// // Global domain that holds an optional local domain: + /// struct AppState { var modal: ModalState? } + /// enum AppAction { case modal(ModalAction) } + /// struct AppEnvironment { var mainQueue: AnySchedulerOf } + /// + /// // A reducer that works on the non-optional local domain: + /// let modalReducer = Reducer.combine( + /// modalReducer.optional().pullback( + /// state: \.modal, + /// action: /AppAction.modal, + /// environment: { ModalEnvironment(mainQueue: $0.mainQueue) } + /// ), + /// Reducer { state, action, environment in + /// ... + /// } + /// ) + /// + /// Take care when combining optional reducers into parent domains. An optional reducer cannot + /// process actions in its domain when its state is `nil`. If a child action is sent to an + /// optional reducer when child state is `nil`, it is generally considered a logic error. There + /// are a few ways in which these errors can sneak into a code base: + /// + /// * A parent reducer sets child state to `nil` when processing a child action and runs + /// _before_ the child reducer: + /// + /// let parentReducer = Reducer.combine( + /// // When combining reducers, the parent reducer runs first + /// Reducer { state, action, environment in + /// switch action { + /// case .child(.didDisappear): + /// // And `nil`s out child state when processing a child action + /// state.child = nil + /// return .none + /// ... + /// } + /// }, + /// // Before the child reducer runs + /// childReducer.optional().pullback(...) + /// ) + /// + /// let childReducer = Reducer< + /// ChildState, ChildAction, ChildEnvironment + /// > { state, action environment in + /// case .didDisappear: + /// // This action is never received here because child state is `nil` in the parent + /// ... + /// } + /// + /// To ensure that a child reducer can process any action that a parent may use to `nil` out + /// its state, combine it _before_ the parent: + /// + /// let parentReducer = Reducer.combine( + /// // The child runs first + /// childReducer.optional().pullback(...), + /// // The parent runs after + /// Reducer { state, action, environment in + /// ... + /// } + /// ) + /// + /// * A child effect feeds a child action back into the store when child state is `nil`: + /// + /// let childReducer = Reducer< + /// ChildState, ChildAction, ChildEnvironment + /// > { state, action environment in + /// switch action { + /// case .onAppear: + /// // An effect may want to feed its result back to the child domain in an action + /// return environment.apiClient + /// .request() + /// .map(ChildAction.response) + /// + /// case let .response(response): + /// // But the child cannot process this action if its state is `nil` in the parent + /// ... + /// } + /// } + /// + /// It is perfectly reasonable to ignore the result of an effect when child state is `nil`, + /// for example one-off effects that you don't want to cancel. However, many long-living + /// effects _should_ be explicitly canceled when tearing down a child domain: + /// + /// let childReducer = Reducer< + /// ChildState, ChildAction, ChildEnvironment + /// > { state, action environment in + /// struct MotionId: Hashable {} + /// + /// switch action { + /// case .onAppear: + /// // Mark long-living effects that shouldn't outlive their domain cancellable + /// return environment.motionClient + /// .start() + /// .map(ChildAction.motion) + /// .cancellable(id: MotionId()) + /// + /// case .onDisappear: + /// // And explicitly cancel them when the domain is torn down + /// return .cancel(id: MotionId()) + /// ... + /// } + /// } + /// + /// * A view store sends a child action when child state is `nil`: + /// + /// WithViewStore(self.parentStore) { parentViewStore in + /// // If child state is `nil`, it cannot process this action. + /// Button("Child Action") { parentViewStore.send(.child(.action)) } + /// ... + /// } + /// + /// Use `Store.scope` with`IfLetStore` or `Store.ifLet` to ensure that views can only send + /// child actions when the child domain is non-`nil`. + /// + /// IfLetStore( + /// self.parentStore.scope(state: { $0.child }, action: { .child($0) } + /// ) { childStore in + /// // This destination only appears when child state is non-`nil` + /// WithViewStore(childStore) { childViewStore in + /// // So this action can only be sent when child state is non-`nil` + /// Button("Child Action") { childViewStore.send(.action) } + /// } + /// ... + /// } + /// + /// - See also: `IfLetStore`, a SwiftUI helper for transforming a store on optional state into a + /// store on non-optional state. + /// - See also: `Store.ifLet`, a UIKit helper for doing imperative work with a store on optional + /// state. + /// + /// - Parameter breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer + /// but state is `nil`. This is generally considered a logic error, as a child reducer cannot + /// process a child action for unavailable child state. + /// - Returns: A reducer that works on optional state. + public func optional( + breakpointOnNil: Bool = true, + _ file: StaticString = #file, + _ line: UInt = #line + ) -> Reducer< + State?, Action, Environment + > { + .init { state, action, environment in + guard state != nil else { + #if DEBUG + if breakpointOnNil { + fputs( + """ + --- + Warning: Reducer.optional@\(file):\(line) + + "\(debugCaseOutput(action))" was received by an optional reducer when its state was \ + "nil". This is generally considered an application logic error, and can happen for a \ + few reasons: + + * The optional reducer was combined with or run from another reducer that set \ + "\(State.self)" to "nil" before the optional reducer ran. Combine or run optional \ + reducers before reducers that can set their state to "nil". This ensures that \ + optional reducers can handle their actions while their state is still non-"nil". + + * An in-flight effect emitted this action while state was "nil". While it may be \ + perfectly reasonable to ignore this action, you may want to cancel the associated \ + effect before state is set to "nil", especially if it is a long-living effect. + + * This action was sent to the store while state was "nil". Make sure that actions \ + for this reducer can only be sent to a view store when state is non-"nil". In \ + SwiftUI applications, use "IfLetStore". + --- + + """, + stderr + ) + raise(SIGTRAP) + } + #endif + return .none + } + return self.reducer(&state!, action, environment) + } + } + + /// A version of `pullback` that transforms a reducer that works on an element into one that works + /// on a collection of elements. + /// + /// // Global domain that holds a collection of local domains: + /// struct AppState { var todos: [Todo] } + /// enum AppAction { case todo(index: Int, action: TodoAction) } + /// struct AppEnvironment { var mainQueue: AnySchedulerOf } + /// + /// // A reducer that works on a local domain: + /// let todoReducer = Reducer { ... } + /// + /// // Pullback the local todo reducer so that it works on all of the app domain: + /// let appReducer = Reducer.combine( + /// todoReducer.forEach( + /// state: \.todos, + /// action: /AppAction.todo(index:action:), + /// environment: { _ in TodoEnvironment() } + /// ), + /// Reducer { state, action, environment in + /// ... + /// } + /// ) + /// + /// Take care when combining `forEach` reducers into parent domains, as order matters. Always + /// combine `forEach` reducers _before_ parent reducers that can modify the collection. + /// + /// - Parameters: + /// - toLocalState: A key path that can get/set an array of `State` elements inside. + /// `GlobalState`. + /// - toLocalAction: A case path that can extract/embed `(Int, Action)` from `GlobalAction`. + /// - toLocalEnvironment: A function that transforms `GlobalEnvironment` into `Environment`. + /// - breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer but the + /// index is out of bounds. This is generally considered a logic error, as a child reducer + /// cannot process a child action for unavailable child state. + /// - Returns: A reducer that works on `GlobalState`, `GlobalAction`, `GlobalEnvironment`. + public func forEach( + state toLocalState: WritableKeyPath, + action toLocalAction: CasePath, + environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment, + breakpointOnNil: Bool = true, + _ file: StaticString = #file, + _ line: UInt = #line + ) -> Reducer { + .init { globalState, globalAction, globalEnvironment in + guard let (index, localAction) = toLocalAction.extract(from: globalAction) else { + return .none + } + if index >= globalState[keyPath: toLocalState].endIndex { + #if DEBUG + if breakpointOnNil { + fputs( + """ + --- + Warning: Reducer.forEach@\(file):\(line) + + "\(debugCaseOutput(localAction))" was received by a "forEach" reducer at index \ + \(index) when its state contained no element at this index. This is generally \ + considered an application logic error, and can happen for a few reasons: + + * This "forEach" reducer was combined with or run from another reducer that removed \ + the element at this index when it handled this action. To fix this make sure that \ + this "forEach" reducer is run before any other reducers that can move or remove \ + elements from state. This ensures that "forEach" reducers can handle their actions \ + for the element at the intended index. + + * An in-flight effect emitted this action while state contained no element at this \ + index. While it may be perfectly reasonable to ignore this action, you may want to \ + cancel the associated effect when moving or removing an element. If your "forEach" \ + reducer returns any long-living effects, you should use the identifier-based \ + "forEach" instead. + + * This action was sent to the store while its state contained no element at this \ + index. To fix this make sure that actions for this reducer can only be sent to a \ + view store when its state contains an element at this index. In SwiftUI \ + applications, use "ForEachStore". + --- + + """, + stderr + ) + raise(SIGTRAP) + } + #endif + return .none + } + return self.reducer( + &globalState[keyPath: toLocalState][index], + localAction, + toLocalEnvironment(globalEnvironment) + ) + .map { toLocalAction.embed((index, $0)) } + } + } + + public func forEach( + state toLocalState: WritableKeyPath, + action toLocalAction: CasePath, + environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment, + breakpointOnNil: Bool = true, + _ file: StaticString = #file, + _ line: UInt = #line + ) -> Reducer { + .init { globalState, globalAction, globalEnvironment in + guard let (key, localAction) = toLocalAction.extract(from: globalAction) else { return .none } + if globalState[keyPath: toLocalState][key] == nil { + #if DEBUG + if breakpointOnNil { + fputs( + """ + --- + Warning: Reducer.forEach@\(file):\(line) + + "\(debugCaseOutput(localAction))" was received by a "forEach" reducer at key \(key) \ + when its state contained no element at this key. This is generally considered an \ + application logic error, and can happen for a few reasons: + + * This "forEach" reducer was combined with or run from another reducer that removed \ + the element at this key when it handled this action. To fix this make sure that this \ + "forEach" reducer is run before any other reducers that can move or remove elements \ + from state. This ensures that "forEach" reducers can handle their actions for the \ + element at the intended key. + + * An in-flight effect emitted this action while state contained no element at this \ + key. It may be perfectly reasonable to ignore this action, but you also may want to \ + cancel the effect it originated from when removing a value from the dictionary, \ + especially if it is a long-living effect. + + * This action was sent to the store while its state contained no element at this \ + key. To fix this make sure that actions for this reducer can only be sent to a view \ + store when its state contains an element at this key. + --- + + """, + stderr + ) + raise(SIGTRAP) + } + #endif + return .none + } + return self.reducer( + &globalState[keyPath: toLocalState][key]!, + localAction, + toLocalEnvironment(globalEnvironment) + ) + .map { toLocalAction.embed((key, $0)) } + } + } + + /// A version of `pullback` that transforms a reducer that works on an element into one that works + /// on an identified array of elements. + /// + /// // Global domain that holds a collection of local domains: + /// struct AppState { var todos: IdentifiedArrayOf } + /// enum AppAction { case todo(id: Todo.ID, action: TodoAction) } + /// struct AppEnvironment { var mainQueue: AnySchedulerOf } + /// + /// // A reducer that works on a local domain: + /// let todoReducer = Reducer { ... } + /// + /// // Pullback the local todo reducer so that it works on all of the app domain: + /// let appReducer = Reducer.combine( + /// todoReducer.forEach( + /// state: \.todos, + /// action: /AppAction.todo(id:action:), + /// environment: { _ in TodoEnvironment() } + /// ), + /// Reducer { state, action, environment in + /// ... + /// } + /// ) + /// + /// Take care when combining `forEach` reducers into parent domains, as order matters. Always + /// combine `forEach` reducers _before_ parent reducers that can modify the collection. + /// + /// - Parameters: + /// - toLocalState: A key path that can get/set a collection of `State` elements inside + /// `GlobalState`. + /// - toLocalAction: A case path that can extract/embed `(Collection.Index, Action)` from + /// `GlobalAction`. + /// - toLocalEnvironment: A function that transforms `GlobalEnvironment` into `Environment`. + /// - breakpointOnNil: Raises `SIGTRAP` signal when an action is sent to the reducer but the + /// identified array does not contain an element with the action's identifier. This is + /// generally considered a logic error, as a child reducer cannot process a child action + /// for unavailable child state. + /// - Returns: A reducer that works on `GlobalState`, `GlobalAction`, `GlobalEnvironment`. + public func forEach( + state toLocalState: WritableKeyPath>, + action toLocalAction: CasePath, + environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment, + breakpointOnNil: Bool = true, + _ file: StaticString = #file, + _ line: UInt = #line + ) -> Reducer { + .init { globalState, globalAction, globalEnvironment in + guard let (id, localAction) = toLocalAction.extract(from: globalAction) else { return .none } + if globalState[keyPath: toLocalState][id: id] == nil { + #if DEBUG + if breakpointOnNil { + fputs( + """ + --- + Warning: Reducer.forEach@\(file):\(line) + + "\(debugCaseOutput(localAction))" was received by a "forEach" reducer at id \(id) \ + when its state contained no element at this id. This is generally considered an \ + application logic error, and can happen for a few reasons: + + * This "forEach" reducer was combined with or run from another reducer that removed \ + the element at this id when it handled this action. To fix this make sure that this \ + "forEach" reducer is run before any other reducers that can move or remove elements \ + from state. This ensures that "forEach" reducers can handle their actions for the \ + element at the intended id. + + * An in-flight effect emitted this action while state contained no element at this \ + id. It may be perfectly reasonable to ignore this action, but you also may want to \ + cancel the effect it originated from when removing an element from the identified \ + array, especially if it is a long-living effect. + + * This action was sent to the store while its state contained no element at this id. \ + To fix this make sure that actions for this reducer can only be sent to a view store \ + when its state contains an element at this id. In SwiftUI applications, use \ + "ForEachStore". + --- + + """, + stderr + ) + raise(SIGTRAP) + } + #endif + return .none + } + return + self + .reducer( + &globalState[keyPath: toLocalState][id: id]!, + localAction, + toLocalEnvironment(globalEnvironment) + ) + .map { toLocalAction.embed((id, $0)) } + } + } + + public func callAsFunction( + _ state: inout State, + _ action: Action, + _ environment: Environment + ) -> Effect { + func environmentToUse() -> Environment { + #if DEBUG + if let bootstrappedEnvironment = Bootstrap.get(environment: type(of: environment)) { + return bootstrappedEnvironment + } else { + return environment + } + #else + return environment + #endif + } + + return reducer(&state, action, environmentToUse()) + } + + public func combined(with other: Reducer) -> Reducer { + .combine(self, other) + } + + /// Runs the reducer. + /// + /// - Parameters: + /// - state: Mutable state. + /// - action: An action. + /// - environment: An environment. + /// - debug: any additional action when executing reducer + /// - Returns: An effect that can emit zero or more actions. + public func run( + _ state: inout State, + _ action: Action, + _ environment: Environment, + _ debug: (State) -> Void = { _ in } + ) -> Effect { + let reducer = self.reducer(&state, action, environment) + debug(state) + + return reducer + } +} + +extension Reducer where Environment == Void { + public func callAsFunction( + _ state: inout State, + _ action: Action + ) -> Effect { + callAsFunction(&state, action, ()) + } +} + +extension Reducer where State: HashDiffable { + /** + Pullback reducer for reducer which store is part of `Differentiable` array. + */ + public func forEach( + state toLocalState: WritableKeyPath, + action toLocalAction: CasePath, + environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment + ) -> Reducer + where Identifier == State.IdentifierType { + .init { globalState, globalAction, globalEnvironment in + guard let (identifier, localAction) = toLocalAction.extract(from: globalAction) else { + return .none + } + + // search index of identifier + guard let index = globalState[keyPath: toLocalState].firstIndex(where: { $0.id == identifier }) + else { + assertionFailure("\(identifier) is not exist on Global State") + return .none + } + + // return redux + return self.reducer( + &globalState[keyPath: toLocalState][index], + localAction, + toLocalEnvironment(globalEnvironment) + ) + .map { toLocalAction.embed((identifier, $0)) } + } + } +} diff --git a/RxComposableArchitecture/Classes/ReplaceMe.swift b/RxComposableArchitecture/Classes/ReplaceMe.swift deleted file mode 100644 index e69de29..0000000 diff --git a/RxComposableArchitecture/Classes/Stateless.swift b/RxComposableArchitecture/Classes/Stateless.swift new file mode 100644 index 0000000..a7ee78d --- /dev/null +++ b/RxComposableArchitecture/Classes/Stateless.swift @@ -0,0 +1,12 @@ +// +// Stateless.swift +// RxComposableArchitecture_RxComposableArchitecture +// +// Created by Jefferson Setiawan on 21/05/21. +// + +import Foundation + +public struct Stateless: Equatable { + public init() {} +} diff --git a/RxComposableArchitecture/Classes/Store.swift b/RxComposableArchitecture/Classes/Store.swift new file mode 100644 index 0000000..8d1d7cd --- /dev/null +++ b/RxComposableArchitecture/Classes/Store.swift @@ -0,0 +1,283 @@ +import Foundation +import RxRelay +import RxSwift + +public final class Store { + public private(set) var state: State { + get { return relay.value } + set { relay.accept(newValue) } + } + + private var isSending = false + private var synchronousActionsToSend: [Action] = [] + private var bufferedActions: [Action] = [] + + private let reducer: (inout State, Action) -> Effect + + private let disposeBag = DisposeBag() + internal var effectDisposables = CompositeDisposable() + private let relay: BehaviorRelay + + public var observable: Observable { + return relay.asObservable() + } + + private init( + initialState: State, + reducer: @escaping (inout State, Action) -> Effect + ) { + relay = BehaviorRelay(value: initialState) + self.reducer = reducer + state = initialState + } + + public convenience init( + initialState: State, + reducer: Reducer, + environment: Environment + ) { + self.init( + initialState: initialState, + reducer: { reducer.callAsFunction(&$0, $1, environment) } + ) + } + + public func send(_ action: Action) { + if !isSending { + synchronousActionsToSend.append(action) + } else { + bufferedActions.append(action) + return + } + + while !synchronousActionsToSend.isEmpty || !bufferedActions.isEmpty { + let action = !synchronousActionsToSend.isEmpty + ? synchronousActionsToSend.removeFirst() + : bufferedActions.removeFirst() + + isSending = true + let effect = reducer(&state, action) + isSending = false + + var didComplete = false + var isProcessingEffects = true + var disposeKey: CompositeDisposable.DisposeKey? + + let effectDisposable = effect.subscribe( + onNext: { [weak self] action in + if isProcessingEffects { + self?.synchronousActionsToSend.append(action) + } else { + self?.send(action) + } + }, + onError: { err in + assertionFailure("Error during effect handling: \(err.localizedDescription)") + }, + onCompleted: { [weak self] in + didComplete = true + if let disposeKey = disposeKey { + self?.effectDisposables.remove(for: disposeKey) + } + } + ) + + isProcessingEffects = false + + if !didComplete { + disposeKey = effectDisposables.insert(effectDisposable) + } + } + } + + public func scope( + state toLocalState: @escaping (State) -> LocalState, + action fromLocalAction: @escaping (LocalAction) -> Action + ) -> Store { + let localStore = Store( + initialState: toLocalState(state), + reducer: { localState, localAction in + self.send(fromLocalAction(localAction)) + localState = toLocalState(self.state) + return .none + } + ) + + relay + .subscribe(onNext: { [weak localStore] newValue in + localStore?.state = toLocalState(newValue) + }) + .disposed(by: localStore.disposeBag) + + return localStore + } + + public func scope( + state toLocalState: @escaping (State) -> LocalState + ) -> Store { + scope(state: toLocalState, action: { $0 }) + } + + /// Scopes the store to a publisher of stores of more local state and local actions. + /// + /// - Parameters: + /// - toLocalState: A function that transforms a publisher of `State` into a publisher of + /// `LocalState`. + /// - fromLocalAction: A function that transforms `LocalAction` into `Action`. + /// - Returns: A publisher of stores with its domain (state and action) transformed. + public func scope( + state toLocalState: @escaping (Observable) -> Observable, + action fromLocalAction: @escaping (LocalAction) -> Action + ) -> Observable> { + func extractLocalState(_ state: State) -> LocalState? { + var localState: LocalState? + _ = toLocalState(Observable.just(state)).subscribe(onNext: { localState = $0 }) + return localState + } + + return toLocalState(relay.asObservable()) + .map { localState in + let localStore = Store( + initialState: localState, + reducer: { localState, localAction in + self.send(fromLocalAction(localAction)) + localState = extractLocalState(self.state) ?? localState + return .none + } + ) + + self.relay.asObservable() + .subscribe(onNext: { [weak localStore] state in + guard let localStore = localStore else { return } + localStore.state = extractLocalState(state) ?? localStore.state + }) + .disposed(by: self.disposeBag) + + return localStore + } + } + + /// Scopes the store to a publisher of stores of more local state and local actions. + /// + /// - Parameter toLocalState: A function that transforms a publisher of `State` into a publisher + /// of `LocalState`. + /// - Returns: A publisher of stores with its domain (state and action) + /// transformed. + public func scope( + state toLocalState: @escaping (Observable) -> Observable + ) -> Observable> { + scope(state: toLocalState, action: { $0 }) + } + + /// Returns a "stateless" store by erasing state to `Void`. + public var stateless: Store { + scope(state: { _ in () }) + } + + /// Returns an "actionless" store by erasing action to `Never`. + public var actionless: Store { + func absurd(_: Never) -> A {} + return scope(state: { $0 }, action: absurd) + } + + public func subscribe( + _ toLocalState: @escaping (State) -> LocalState, + removeDuplicates isDuplicate: @escaping (LocalState, LocalState) -> Bool + ) -> Effect { + return relay.map(toLocalState).distinctUntilChanged(isDuplicate).eraseToEffect() + } + + public func subscribe( + _ toLocalState: @escaping (State) -> LocalState + ) -> Effect { + return relay.map(toLocalState).distinctUntilChanged().eraseToEffect() + } +} + +extension Store where State: Equatable { + public func subscribe() -> Effect { + return relay.distinctUntilChanged().eraseToEffect() + } +} + +extension Store where State: Collection, State.Element: HashDiffable, State: Equatable, State.Element: Equatable { + /** + A version of scope that scope an collection of sub store. + + This is kinda a version of `ForEachStoreNode`, not composing `WithViewStore` but creates the sub store. + + ## Example + ``` + struct AppState { var todos: [Todo] } + struct AppAction { case todo(index: Int, action: TodoAction } + + store.subscribe(\.todos) + .drive(onNext: { todos in + self.todoNodes = zip(todos.indices, todos).map { (offset, _) in + TodoNode(with: store.scope( + identifier: identifier, + action: Action.todo(index:action:) + ) + } + }) + .disposed(by: disposeBag) + ``` + + But with example above, you created the entire node again and again and it's not the efficient way. + You can do some diffing and only creating spesific index, and rest is handle by diffing. + + - Parameters: + - identifier: the identifier from `IdentifierType` make sure index is in bounds of the collection + - action: A function to transform `LocalAction` to `Action`. `LocalAction` should have `(CollectionIndex, LocalAction)` signature. + + - Returns: A new store with its domain (state and domain) transformed based on the index you set + */ + public func scope( + at identifier: State.Element.IdentifierType, + action fromLocalAction: @escaping (LocalAction) -> Action + ) -> Store? { + let toLocalState: (State.Element.IdentifierType, State) -> State.Element? = { identifier, state in + /** + if current state is IdentifiedArray, use pre exist subscript by identifier, to improve performance + */ + if let identifiedArray = state as? IdentifiedArrayOf { + return identifiedArray[id: identifier] + } else { + return state.first(where: { $0.id == identifier }) + } + } + + // skip if element on parent state wasn't found + guard let element = toLocalState(identifier, state) else { return nil } + + let localStore = Store( + initialState: element, + reducer: { localState, localAction in + self.send(fromLocalAction(localAction)) + guard let finalState = toLocalState(identifier, self.state) else { + return .none + } + + localState = finalState + return .none + } + ) + + // reflect changes on store parent to local store + relay + .distinctUntilChanged() + .flatMapLatest { newValue -> Observable in + guard let newElement = toLocalState(identifier, newValue) else { + return .empty() + } + + return .just(newElement) + } + .subscribe(onNext: { [weak localStore] newValue in + localStore?.state = newValue + }) + .disposed(by: localStore.disposeBag) + + return localStore + } +} diff --git a/development-podspecs/CasePaths.podspec.json b/development-podspecs/CasePaths.podspec.json new file mode 100644 index 0000000..f8f0ea3 --- /dev/null +++ b/development-podspecs/CasePaths.podspec.json @@ -0,0 +1,20 @@ +{ + "name": "CasePaths", + "version": "0.1.0", + "authors": "local pod", + "homepage": "https://github.com/pointfreeco/swift-case-paths", + "summary": "local pod", + "license": { + "type": "MIT", + "file": "LICENSE" + }, + "platforms": { + "ios": "10.0" + }, + "source": { + "git": "https://github.com/pointfreeco/swift-case-paths", + "tag": "0.1.3" + }, + "source_files": "Sources/**/*.swift", + "swift_version": "5.0" +} \ No newline at end of file