From 0e1dd144ae75c68aa9ab287d5cf086a764757352 Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Tue, 14 May 2019 03:15:52 +0900 Subject: [PATCH 01/17] support dynamic member lookup with Swift5.1 #8 --- .../contents.xcworkspacedata | 7 ++ .../UnioSample/GitHubSearchAPIStream.swift | 9 +- .../UnioSample/GitHubSearchLogicStream.swift | 27 +++++- .../GitHubSearchViewController.swift | 22 ++++- .../UnioSample/GitHubSearchViewStream.swift | 9 ++ Unio.xcodeproj/project.pbxproj | 14 ++- Unio/AcceptableObserver.swift | 39 +++++++++ Unio/AnyThrowableValue.swift | 20 +++++ Unio/Dependency.swift | 15 +++- Unio/DynamicMemberLookupAdapter.swift | 86 +++++++++++++++++++ Unio/Relay.swift | 60 ++++++++++--- 11 files changed, 285 insertions(+), 23 deletions(-) create mode 100644 Unio/AcceptableObserver.swift create mode 100644 Unio/AnyThrowableValue.swift create mode 100644 Unio/DynamicMemberLookupAdapter.swift diff --git a/Example/UnioSample.xcworkspace/contents.xcworkspacedata b/Example/UnioSample.xcworkspace/contents.xcworkspacedata index 68b7f75..695d675 100644 --- a/Example/UnioSample.xcworkspace/contents.xcworkspacedata +++ b/Example/UnioSample.xcworkspace/contents.xcworkspacedata @@ -7,4 +7,11 @@ + + + + diff --git a/Example/UnioSample/GitHubSearchAPIStream.swift b/Example/UnioSample/GitHubSearchAPIStream.swift index f6f5615..1788f33 100644 --- a/Example/UnioSample/GitHubSearchAPIStream.swift +++ b/Example/UnioSample/GitHubSearchAPIStream.swift @@ -57,7 +57,14 @@ extension GitHubSearchAPIStream.Logic { func bind(from dependency: Dependency) -> Output { let session = dependency.extra.session - let searchResponseEvent = dependency.inputObservable(for: \.searchRepository) + + let searchRepository: Observable + #if swift(>=5.1) + searchRepository = dependency.inputObservables.searchRepository + #else + searchRepository = dependency.inputObservable(for: \.searchRepository) + #endif + let searchResponseEvent = searchRepository .flatMapLatest { query -> Observable>> in guard var components = URLComponents(string: "https://api.github.com/search/repositories") else { return .empty() diff --git a/Example/UnioSample/GitHubSearchLogicStream.swift b/Example/UnioSample/GitHubSearchLogicStream.swift index 5188c49..fd2a97d 100644 --- a/Example/UnioSample/GitHubSearchLogicStream.swift +++ b/Example/UnioSample/GitHubSearchLogicStream.swift @@ -67,13 +67,27 @@ extension GitHubSearchLogicStream.Logic { let extra = dependency.extra let searchAPIStream = extra.searchAPIStream - searchAPIStream.output - .observable(for: \.searchResponse) + let searchResponse: Observable> + #if swift(>=5.1) + searchResponse = searchAPIStream.output.observables.searchResponse + #else + searchResponse = searchAPIStream.output.observable(for: \.searchResponse) + #endif + searchResponse .map { $0.items } .bind(to: state.repositories) .disposed(by: disposeBag) - dependency.inputObservable(for: \.searchText) + let searchText: Observable + let searchRepository: AcceptableObserver + #if swift(>=5.1) + searchText = dependency.inputObservables.searchText + searchRepository = searchAPIStream.input.searchRepository + #else + searchText = dependency.inputObservable(for: \.searchText) + searchRepository = searchAPIStream.input.accept(for: \.searchRepository) + #endif + searchText .debounce(.milliseconds(300), scheduler: extra.scheduler) .flatMap { query -> Observable in guard let query = query, !query.isEmpty else { @@ -81,10 +95,15 @@ extension GitHubSearchLogicStream.Logic { } return .just(query) } - .bind(to: searchAPIStream.input.accept(for: \.searchRepository)) + .bind(to: searchRepository) .disposed(by: disposeBag) + #if swift(>=5.1) + return Output(repositories: state.repositories, + error: searchAPIStream.output.observables.searchError) + #else return Output(repositories: state.repositories, error: searchAPIStream.output.observable(for: \.searchError)) + #endif } } diff --git a/Example/UnioSample/GitHubSearchViewController.swift b/Example/UnioSample/GitHubSearchViewController.swift index a96c127..fbf5320 100644 --- a/Example/UnioSample/GitHubSearchViewController.swift +++ b/Example/UnioSample/GitHubSearchViewController.swift @@ -51,22 +51,40 @@ final class GitHubSearchViewController: UIViewController { do { let input = viewStream.input + #if swift(>=5.1) + searchBar.rx.text + .bind(to: input.searchText) + .disposed(by: disposeBag) + #else searchBar.rx.text .bind(to: input.accept(for: \.searchText)) .disposed(by: disposeBag) + #endif } do { let output = viewStream.output - output.observable(for: \.repositories) + let repositories: Observable<[GitHub.Repository]> + #if swift(>=5.1) + repositories = output.observables.repositories + #else + repositories = output.observable(for: \.repositories) + #endif + repositories .bind(to: tableView.rx.items(cellIdentifier: "Cell")) { row, repository, cell in cell.textLabel?.text = repository.fullName cell.detailTextLabel?.text = repository.htmlUrl.absoluteString } .disposed(by: disposeBag) - output.observable(for: \.errorMessage) + let errorMessage: Observable + #if swift(>=5.1) + errorMessage = output.observables.errorMessage + #else + errorMessage = output.observable(for: \.errorMessage) + #endif + errorMessage .bind(to: Binder(self) { me, message in let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Close", style: .default, handler: nil)) diff --git a/Example/UnioSample/GitHubSearchViewStream.swift b/Example/UnioSample/GitHubSearchViewStream.swift index 9280f87..1377804 100644 --- a/Example/UnioSample/GitHubSearchViewStream.swift +++ b/Example/UnioSample/GitHubSearchViewStream.swift @@ -62,11 +62,20 @@ extension GitHubSearchViewStream.Logic { let logicStream = dependency.extra.logicStream + #if swift(>=5.1) + dependency.inputObservables.searchText + .bind(to: logicStream.input.searchText) + .disposed(by: disposeBag) + + return Output(repositories: logicStream.output.observables.repositories, + errorMessage: logicStream.output.observables.error.map { $0.localizedDescription }) + #else dependency.inputObservable(for: \.searchText) .bind(to: logicStream.input.accept(for: \.searchText)) .disposed(by: disposeBag) return Output(repositories: logicStream.output.observable(for: \.repositories), errorMessage: logicStream.output.observable(for: \.error).map { $0.localizedDescription }) + #endif } } diff --git a/Unio.xcodeproj/project.pbxproj b/Unio.xcodeproj/project.pbxproj index b5574b9..d270079 100644 --- a/Unio.xcodeproj/project.pbxproj +++ b/Unio.xcodeproj/project.pbxproj @@ -35,6 +35,9 @@ ED74A7382243D99D00D4E99C /* RelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A7372243D99D00D4E99C /* RelayTests.swift */; }; ED74A73A2243E0CC00D4E99C /* DependencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A7392243E0CC00D4E99C /* DependencyTests.swift */; }; ED74A73C2243E43D00D4E99C /* UnioStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A73B2243E43D00D4E99C /* UnioStreamTests.swift */; }; + EDD118F62289CFD70055C0AE /* AcceptableObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD118F52289CFD70055C0AE /* AcceptableObserver.swift */; }; + EDD118F82289E1170055C0AE /* DynamicMemberLookupAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD118F72289E1170055C0AE /* DynamicMemberLookupAdapter.swift */; }; + EDD118FA2289E1630055C0AE /* AnyThrowableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD118F92289E1630055C0AE /* AnyThrowableValue.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -90,6 +93,9 @@ ED74A7372243D99D00D4E99C /* RelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayTests.swift; sourceTree = ""; }; ED74A7392243E0CC00D4E99C /* DependencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyTests.swift; sourceTree = ""; }; ED74A73B2243E43D00D4E99C /* UnioStreamTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnioStreamTests.swift; sourceTree = ""; }; + EDD118F52289CFD70055C0AE /* AcceptableObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptableObserver.swift; sourceTree = ""; }; + EDD118F72289E1170055C0AE /* DynamicMemberLookupAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicMemberLookupAdapter.swift; sourceTree = ""; }; + EDD118F92289E1630055C0AE /* AnyThrowableValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyThrowableValue.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -139,7 +145,10 @@ isa = PBXGroup; children = ( 9DA555C92241DE5F00EC8CC3 /* RxTypes */, + EDD118F52289CFD70055C0AE /* AcceptableObserver.swift */, + EDD118F92289E1630055C0AE /* AnyThrowableValue.swift */, 9D2E269C2240D1CA00C9EDF7 /* Dependency.swift */, + EDD118F72289E1170055C0AE /* DynamicMemberLookupAdapter.swift */, 9D2E26972240D1CA00C9EDF7 /* ExtraType.swift */, 9D2E267D2240D18500C9EDF7 /* Info.plist */, 9D2E26982240D1CA00C9EDF7 /* InputType.swift */, @@ -166,8 +175,8 @@ 9D2E26A72240D1F200C9EDF7 /* Frameworks */ = { isa = PBXGroup; children = ( - 9D9EEC0A228168F800DF5D97 /* RxRelay.framework */, 9D2E26A92240D1F200C9EDF7 /* RxCocoa.framework */, + 9D9EEC0A228168F800DF5D97 /* RxRelay.framework */, 9D2E26A82240D1F200C9EDF7 /* RxSwift.framework */, ); name = Frameworks; @@ -308,10 +317,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + EDD118F62289CFD70055C0AE /* AcceptableObserver.swift in Sources */, 9DA555D32241FC0400EC8CC3 /* AcceptableRelay.swift in Sources */, + EDD118FA2289E1630055C0AE /* AnyThrowableValue.swift in Sources */, 9D2E26A32240D1CA00C9EDF7 /* BehaviorRelayType.swift in Sources */, 9DA555CB2241DE8400EC8CC3 /* BehaviorSubjectType.swift in Sources */, 9D2E26A62240D1CA00C9EDF7 /* Dependency.swift in Sources */, + EDD118F82289E1170055C0AE /* DynamicMemberLookupAdapter.swift in Sources */, 9D2E26A12240D1CA00C9EDF7 /* ExtraType.swift in Sources */, 9D2E26A22240D1CA00C9EDF7 /* InputType.swift in Sources */, 9D2E269F2240D1CA00C9EDF7 /* LogicType.swift in Sources */, diff --git a/Unio/AcceptableObserver.swift b/Unio/AcceptableObserver.swift new file mode 100644 index 0000000..eb493d2 --- /dev/null +++ b/Unio/AcceptableObserver.swift @@ -0,0 +1,39 @@ +// +// AcceptableObserver.swift +// Unio +// +// Created by marty-suzuki on 2019/05/14. +// Copyright © 2019 tv.abema. All rights reserved. +// + +import RxSwift + +/// A type-erased `AcceptableRelay`. +public struct AcceptableObserver: AcceptableRelay, ObserverType { + + private let _accept: (Element) -> Void + + /// Construct an instance whose `on(event)` calls `observer.on(event)` + /// + /// - parameter observer: Observer that receives sequence events. + init(_ relay: Relay) where Relay.Element == Element { + self._accept = { relay.accept($0) } + } + + /// Send `event` to this observer. + /// + /// - parameter event: Event instance. + public func on(_ event: Event) { + switch event { + case let .next(element): + _accept(element) + + case .error, .completed: + return + } + } + + public func accept(_ element: Element) { + _accept(element) + } +} diff --git a/Unio/AnyThrowableValue.swift b/Unio/AnyThrowableValue.swift new file mode 100644 index 0000000..32f431d --- /dev/null +++ b/Unio/AnyThrowableValue.swift @@ -0,0 +1,20 @@ +// +// AnyThrowableValue.swift +// Unio +// +// Created by marty-suzuki on 2019/05/14. +// Copyright © 2019 tv.abema. All rights reserved. +// + +public struct AnyThrowableValue: ThrowableValueAccessible { + + private let get: () throws -> Element + + init(_ value: T) where T.Element == Element { + self.get = { try value.throwableValue() } + } + + public func throwableValue() throws -> Element { + return try get() + } +} diff --git a/Unio/Dependency.swift b/Unio/Dependency.swift index b246fb3..e0555d9 100644 --- a/Unio/Dependency.swift +++ b/Unio/Dependency.swift @@ -26,7 +26,7 @@ public final class Dependency(for keyPath: KeyPath) -> Observable { + public func inputObservable(for keyPath: KeyPath) -> Observable { return _input[keyPath: keyPath].asObservable() } @@ -45,3 +45,16 @@ public final class Dependency=5.1) +extension Dependency { + + public var inputObservables: DML.Observables { + return DML.Observables(_input) + } + + public func readOnlyReferences(from output: Relay) -> DML.ReadOnlyReferences { + return DML.ReadOnlyReferences(output) + } +} +#endif diff --git a/Unio/DynamicMemberLookupAdapter.swift b/Unio/DynamicMemberLookupAdapter.swift new file mode 100644 index 0000000..652aa42 --- /dev/null +++ b/Unio/DynamicMemberLookupAdapter.swift @@ -0,0 +1,86 @@ +// +// DynamicMemberLookupAdapter.swift +// Unio +// +// Created by marty-suzuki on 2019/05/14. +// Copyright © 2019 tv.abema. All rights reserved. +// + +#if swift(>=5.1) +import Foundation +import RxSwift + +public enum DML {} + +extension DML { + + @dynamicMemberLookup + public struct Observables { + + private let object: T + + init(_ object: T) { + self.object = object + } + + public subscript(dynamicMember member: KeyPath) -> Observable { + return object[keyPath: member].asObservable() + } + } +} + +extension DML { + + @dynamicMemberLookup + public struct Values { + + private let object: T + + init(_ object: T) { + self.object = object + } + + public subscript(dynamicMember member: KeyPath) -> U.Element { + return object[keyPath: member].value + } + } +} + +extension DML { + + @dynamicMemberLookup + public struct ThrowableValues { + + private let object: T + + init(_ object: T) { + self.object = object + } + + public subscript(dynamicMember member: KeyPath) -> AnyThrowableValue { + return AnyThrowableValue(object[keyPath: member]) + } + } +} + +extension DML { + + @dynamicMemberLookup + public struct ReadOnlyReferences { + + private let output: Relay + + init(_ output: Relay) { + self.output = output + } + + public subscript(dynamicMember member: KeyPath) -> ReadOnly { + return ReadOnly(output, for: member) + } + + public subscript(dynamicMember member: KeyPath) -> ReadOnly { + return ReadOnly(output, for: member) + } + } +} +#endif diff --git a/Unio/Relay.swift b/Unio/Relay.swift index 5f704c0..31fedd5 100644 --- a/Unio/Relay.swift +++ b/Unio/Relay.swift @@ -14,6 +14,8 @@ import RxSwift /// /// - note: When generic parameter is `InputType`, it makes possible to access Observales via `KeyPath`. /// On the other hand, when generic parameter is `OutType`, it makes possible to access Observers via `KeyPath`. +#if swift(>=5.1) +@dynamicMemberLookup public final class Relay { internal let _dependency: T @@ -21,6 +23,19 @@ public final class Relay { private init(dependency: T) { self._dependency = dependency } +} +#else +public final class Relay { + + internal let _dependency: T + + private init(dependency: T) { + self._dependency = dependency + } +} +#endif + +extension Relay { private func _observable(for keyPath: KeyPath) -> Observable { @@ -32,20 +47,9 @@ public final class Relay { _dependency[keyPath: keyPath].accept(value) } - private func _accept(for keyPath: KeyPath) -> AnyObserver { - - return AnyObserver { [weak self] event in - switch event { - case let .next(value): - guard let me = self else { - return - } - me._accept(value, for: keyPath) + private func _accept(for keyPath: KeyPath) -> AcceptableObserver { - case .error, .completed: - break - } - } + return AcceptableObserver(_dependency[keyPath: keyPath]) } private func _onEvent(_ event: Event, for keyPath: KeyPath) { @@ -83,7 +87,7 @@ extension Relay where T: InputType { } /// Send `event` to this observer via `Input`. - public func accept(for keyPath: KeyPath) -> AnyObserver { + public func accept(for keyPath: KeyPath) -> AcceptableObserver { return _accept(for: keyPath) } @@ -164,3 +168,31 @@ extension Relay where T: ThrowableValueAccessibleObservable { return _dependency.asObservable() } } + +#if swift(>=5.1) +extension Relay where T: OutputType { + + public var observables: DML.Observables { + return DML.Observables(_dependency) + } + + public var values: DML.Values { + return DML.Values(_dependency) + } + + public var throwableValues: DML.ThrowableValues { + return DML.ThrowableValues(_dependency) + } +} + +extension Relay where T: InputType { + + public subscript(dynamicMember member: KeyPath) -> AcceptableObserver { + return accept(for: member) + } + + public subscript(dynamicMember member: KeyPath) -> AnyObserver { + return onEvent(for: member) + } +} +#endif From 3879758996cfc1abde2f3c82f08eec03c91a8815 Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Thu, 13 Jun 2019 04:14:11 +0900 Subject: [PATCH 02/17] add property --- Unio.xcodeproj/project.pbxproj | 6 ++- Unio/Dependency.swift | 14 +++++++ Unio/PrimitiveProperty.swift | 73 ++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 Unio/PrimitiveProperty.swift diff --git a/Unio.xcodeproj/project.pbxproj b/Unio.xcodeproj/project.pbxproj index b5574b9..7e867b0 100644 --- a/Unio.xcodeproj/project.pbxproj +++ b/Unio.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 9DA555D12241F95400EC8CC3 /* ReadOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D02241F95400EC8CC3 /* ReadOnly.swift */; }; 9DA555D32241FC0400EC8CC3 /* AcceptableRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D22241FC0400EC8CC3 /* AcceptableRelay.swift */; }; 9DA555D52241FC6600EC8CC3 /* ValueAccessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D42241FC6600EC8CC3 /* ValueAccessible.swift */; }; + ED6897C122B184EE00B04DA0 /* PrimitiveProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */; }; ED74A63D2243C2EE00D4E99C /* ReadOnlyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A63C2243C2EE00D4E99C /* ReadOnlyTests.swift */; }; ED74A6402243C3DB00D4E99C /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D2E26A82240D1F200C9EDF7 /* RxSwift.framework */; }; ED74A6432243C43400D4E99C /* RxSwift.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 9D2E26A82240D1F200C9EDF7 /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -86,6 +87,7 @@ 9DA555D02241F95400EC8CC3 /* ReadOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadOnly.swift; sourceTree = ""; }; 9DA555D22241FC0400EC8CC3 /* AcceptableRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptableRelay.swift; sourceTree = ""; }; 9DA555D42241FC6600EC8CC3 /* ValueAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueAccessible.swift; sourceTree = ""; }; + ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveProperty.swift; sourceTree = ""; }; ED74A63C2243C2EE00D4E99C /* ReadOnlyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadOnlyTests.swift; sourceTree = ""; }; ED74A7372243D99D00D4E99C /* RelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayTests.swift; sourceTree = ""; }; ED74A7392243E0CC00D4E99C /* DependencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyTests.swift; sourceTree = ""; }; @@ -145,6 +147,7 @@ 9D2E26982240D1CA00C9EDF7 /* InputType.swift */, 9D2E26952240D1CA00C9EDF7 /* LogicType.swift */, 9D2E269B2240D1CA00C9EDF7 /* OutputType.swift */, + ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */, 9DA555D02241F95400EC8CC3 /* ReadOnly.swift */, 9D2E26942240D1CA00C9EDF7 /* Relay.swift */, 9D2E26962240D1CA00C9EDF7 /* StateType.swift */, @@ -166,8 +169,8 @@ 9D2E26A72240D1F200C9EDF7 /* Frameworks */ = { isa = PBXGroup; children = ( - 9D9EEC0A228168F800DF5D97 /* RxRelay.framework */, 9D2E26A92240D1F200C9EDF7 /* RxCocoa.framework */, + 9D9EEC0A228168F800DF5D97 /* RxRelay.framework */, 9D2E26A82240D1F200C9EDF7 /* RxSwift.framework */, ); name = Frameworks; @@ -316,6 +319,7 @@ 9D2E26A22240D1CA00C9EDF7 /* InputType.swift in Sources */, 9D2E269F2240D1CA00C9EDF7 /* LogicType.swift in Sources */, 9D2E26A52240D1CA00C9EDF7 /* OutputType.swift in Sources */, + ED6897C122B184EE00B04DA0 /* PrimitiveProperty.swift in Sources */, 9D2E269D2240D1CA00C9EDF7 /* PublishRelayType.swift in Sources */, 9DA555D12241F95400EC8CC3 /* ReadOnly.swift in Sources */, 9D2E269E2240D1CA00C9EDF7 /* Relay.swift in Sources */, diff --git a/Unio/Dependency.swift b/Unio/Dependency.swift index b246fb3..a526a0a 100644 --- a/Unio/Dependency.swift +++ b/Unio/Dependency.swift @@ -44,4 +44,18 @@ public final class Dependency(from output: Relay, for keyPath: KeyPath) -> ReadOnly { return ReadOnly(output, for: keyPath) } + + /// Returns property that accessible to value (e.g. BehaviorRelay). + /// + /// - note: Object is reference, not copied one. + public func property(from output: Relay, for keyPath: KeyPath) -> Property { + return Property(output, for: keyPath) + } + + /// Returns property that accessible to throwable value (e.g. BehaviorSubject). + /// + /// - note: Object is reference, not copied one. + public func property(from output: Relay, for keyPath: KeyPath) -> ThrowableProperty { + return ThrowableProperty(output, for: keyPath) + } } diff --git a/Unio/PrimitiveProperty.swift b/Unio/PrimitiveProperty.swift new file mode 100644 index 0000000..2e69ad6 --- /dev/null +++ b/Unio/PrimitiveProperty.swift @@ -0,0 +1,73 @@ +// +// PrimitiveProperty.swift +// Unio +// +// Created by marty-suzuki on 2019/06/13. +// Copyright © 2019 tv.abema. All rights reserved. +// + +import Foundation +import RxCocoa +import RxSwift + +/// Represents a property that makes possible to access value +public typealias Property = PrimitiveProperty + +/// Represents a property that makes possible to access throwable value +public typealias ThrowableProperty = PrimitiveProperty + +/// Makes possible to access particular method of property that contained by ValueAccessibleObservable (or ThrowableValueAccessibleObservable) even while hides actual properties (BehaviorRelay, BehaviorSubject and so on). +/// +/// - note: When generic parameter is `ValueAccessibleObservable`, it makes possible to access Observale and value via `KeyPath`. +/// On the other hand, when generic parameter is `ThrowableValueAccessibleObservable`, it makes possible to access Observale and throwable via `KeyPath`. +public final class PrimitiveProperty { + + private let _value: () -> Element + private let _throwableValue: () throws -> Element + private let _asObservable: () -> Observable + + private init(value: @escaping () -> Element, + throwableValue: @escaping () throws -> Element, + asObservable: @escaping () -> Observable) { + self._value = value + self._throwableValue = throwableValue + self._asObservable = asObservable + } +} + +extension PrimitiveProperty: ObservableConvertibleType { + + public func asObservable() -> Observable { + return _asObservable() + } +} + +extension PrimitiveProperty: ValueAccessible where Failure == Never { + + /// Makes possible to get value + public var value: Element { + return _value() + } + + internal convenience init(_ output: Relay, for keyPath: KeyPath) where T.Element == Element { + let behaviorRelay = output._dependency[keyPath: keyPath] + self.init(value: { behaviorRelay.value }, + throwableValue: { fatalError("not reached here") }, + asObservable: { behaviorRelay.asObservable() }) + } +} + +extension PrimitiveProperty: ThrowableValueAccessible where Failure == Error { + + /// Makes possible to get throwableValue + public func throwableValue() throws -> Element { + return try _throwableValue() + } + + internal convenience init(_ output: Relay, for keyPath: KeyPath) where T.Element == Element { + let behaviorSubject = output._dependency[keyPath: keyPath] + self.init(value: { fatalError("not reached here") }, + throwableValue: { try behaviorSubject.throwableValue() }, + asObservable: { behaviorSubject.asObservable() }) + } +} From 88152e126a1a5498e6a975c1c1e92998d34766f4 Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 14 Jun 2019 02:07:22 +0900 Subject: [PATCH 03/17] make ReadOnly deprecated --- Unio/Dependency.swift | 2 ++ Unio/ReadOnly.swift | 3 +++ 2 files changed, 5 insertions(+) diff --git a/Unio/Dependency.swift b/Unio/Dependency.swift index a526a0a..c247ee6 100644 --- a/Unio/Dependency.swift +++ b/Unio/Dependency.swift @@ -34,6 +34,7 @@ public final class Dependency(from output: Relay, for keyPath: KeyPath) -> ReadOnly { return ReadOnly(output, for: keyPath) } @@ -41,6 +42,7 @@ public final class Dependency(from output: Relay, for keyPath: KeyPath) -> ReadOnly { return ReadOnly(output, for: keyPath) } diff --git a/Unio/ReadOnly.swift b/Unio/ReadOnly.swift index c10bfa0..adb27af 100644 --- a/Unio/ReadOnly.swift +++ b/Unio/ReadOnly.swift @@ -14,6 +14,7 @@ import RxSwift /// /// - note: When generic parameter is `ValueAccessibleObservable`, it makes possible to access Observale and value via `KeyPath`. /// On the other hand, when generic parameter is `ThrowableValueAccessibleObservable`, it makes possible to access Observale and throwable via `KeyPath`. +@available(*, deprecated, renamed: "Property") public final class ReadOnly: ObservableConvertibleType { private let _value: () -> T.Element @@ -34,6 +35,7 @@ public final class ReadOnly: ObservableConvertible } } +@available(*, deprecated, renamed: "Property") extension ReadOnly: ValueAccessible where T: ValueAccessibleObservable { /// Makes possible to get value @@ -49,6 +51,7 @@ extension ReadOnly: ValueAccessible where T: ValueAccessibleObservable { } } +@available(*, deprecated, renamed: "ThrowableProperty") extension ReadOnly: ThrowableValueAccessible where T: ThrowableValueAccessibleObservable { /// Makes possible to get throwableValue From 09d469e9db34e0dac4a1aa49a734c5189054abd8 Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 14 Jun 2019 02:07:31 +0900 Subject: [PATCH 04/17] fix test --- UnioTests/TestCases/DependencyTests.swift | 8 ++++---- UnioTests/TestCases/ReadOnlyTests.swift | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/UnioTests/TestCases/DependencyTests.swift b/UnioTests/TestCases/DependencyTests.swift index 94b8b22..75d1b88 100644 --- a/UnioTests/TestCases/DependencyTests.swift +++ b/UnioTests/TestCases/DependencyTests.swift @@ -57,11 +57,11 @@ final class DependencyTests: XCTestCase { let expected = "test-ValueAccessible" let testTarget = dependency.testTarget - let readOnly = testTarget.readOnlyReference(from: dependency.output, for: \.relay) + let property = testTarget.property(from: dependency.output, for: \.relay) dependency.outputRelay.accept(expected) - XCTAssertEqual(readOnly.value, expected) + XCTAssertEqual(property.value, expected) } func testRelayOnly_ThrowableValueAccessible() { @@ -69,11 +69,11 @@ final class DependencyTests: XCTestCase { let expected = "test-ThrowableValueAccessible" let testTarget = dependency.testTarget - let readOnly = testTarget.readOnlyReference(from: dependency.output, for: \.subject) + let property = testTarget.property(from: dependency.output, for: \.subject) dependency.outputSubject.onNext(expected) - XCTAssertEqual(try readOnly.throwableValue(), expected) + XCTAssertEqual(try property.throwableValue(), expected) } } diff --git a/UnioTests/TestCases/ReadOnlyTests.swift b/UnioTests/TestCases/ReadOnlyTests.swift index d391603..36ddf9a 100644 --- a/UnioTests/TestCases/ReadOnlyTests.swift +++ b/UnioTests/TestCases/ReadOnlyTests.swift @@ -63,16 +63,16 @@ extension ReadOnlyTests { private struct Dependency { - let testTargetSubject: ReadOnly> - let testTargetRelay: ReadOnly> + let testTargetSubject: ThrowableProperty + let testTargetRelay: Property let subject = BehaviorSubject(value: nil) let relay = BehaviorRelay(value: nil) init() { let output = Relay(Output(subject: subject, relay: relay)) - self.testTargetSubject = ReadOnly(output, for: \.subject) - self.testTargetRelay = ReadOnly(output, for: \.relay) + self.testTargetSubject = ThrowableProperty(output, for: \.subject) + self.testTargetRelay = Property(output, for: \.relay) } } } From 762f209f7ddd22892ed8eca084a8664768c2f59e Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Wed, 15 May 2019 02:03:20 +0900 Subject: [PATCH 05/17] use stored properties instead of computed properties --- Unio/Dependency.swift | 23 +++++----- Unio/DynamicMemberLookupAdapter.swift | 15 ++----- Unio/Relay.swift | 63 ++++++++++++--------------- 3 files changed, 43 insertions(+), 58 deletions(-) diff --git a/Unio/Dependency.swift b/Unio/Dependency.swift index e0555d9..52c7da4 100644 --- a/Unio/Dependency.swift +++ b/Unio/Dependency.swift @@ -17,12 +17,20 @@ public final class Dependency=5.1) + public let inputObservables: DMLA.Observables + #endif + private let _input: Input internal init(input: Input, state: State, extra: Extra) { self._input = input self.state = state self.extra = extra + + #if swift(>=5.1) + self.inputObservables = DMLA.Observables(input) + #endif } /// Makes possible to get Observable from `Input`. @@ -44,17 +52,10 @@ public final class Dependency(from output: Relay, for keyPath: KeyPath) -> ReadOnly { return ReadOnly(output, for: keyPath) } -} - -#if swift(>=5.1) -extension Dependency { - - public var inputObservables: DML.Observables { - return DML.Observables(_input) - } - public func readOnlyReferences(from output: Relay) -> DML.ReadOnlyReferences { - return DML.ReadOnlyReferences(output) + #if swift(>=5.1) + public func readOnlyReferences(from output: Relay) -> DMLA.ReadOnlyReferences { + return DMLA.ReadOnlyReferences(output) } + #endif } -#endif diff --git a/Unio/DynamicMemberLookupAdapter.swift b/Unio/DynamicMemberLookupAdapter.swift index 652aa42..c988dd0 100644 --- a/Unio/DynamicMemberLookupAdapter.swift +++ b/Unio/DynamicMemberLookupAdapter.swift @@ -6,13 +6,13 @@ // Copyright © 2019 tv.abema. All rights reserved. // -#if swift(>=5.1) import Foundation import RxSwift -public enum DML {} +#if swift(>=5.1) +public typealias DMLA = DynamicMemberLookupAdapter -extension DML { +public enum DynamicMemberLookupAdapter { @dynamicMemberLookup public struct Observables { @@ -27,9 +27,6 @@ extension DML { return object[keyPath: member].asObservable() } } -} - -extension DML { @dynamicMemberLookup public struct Values { @@ -44,9 +41,6 @@ extension DML { return object[keyPath: member].value } } -} - -extension DML { @dynamicMemberLookup public struct ThrowableValues { @@ -61,9 +55,6 @@ extension DML { return AnyThrowableValue(object[keyPath: member]) } } -} - -extension DML { @dynamicMemberLookup public struct ReadOnlyReferences { diff --git a/Unio/Relay.swift b/Unio/Relay.swift index 31fedd5..0f327c1 100644 --- a/Unio/Relay.swift +++ b/Unio/Relay.swift @@ -14,26 +14,37 @@ import RxSwift /// /// - note: When generic parameter is `InputType`, it makes possible to access Observales via `KeyPath`. /// On the other hand, when generic parameter is `OutType`, it makes possible to access Observers via `KeyPath`. -#if swift(>=5.1) @dynamicMemberLookup public final class Relay { internal let _dependency: T + #if swift(>=5.1) + public let observables: DMLA.Observables + + public let values: DMLA.Values + + public let throwableValues: DMLA.ThrowableValues + #endif + private init(dependency: T) { self._dependency = dependency + + #if swift(>=5.1) + self.observables = DMLA.Observables(dependency) + self.values = DMLA.Values(dependency) + self.throwableValues = DMLA.ThrowableValues(dependency) + #endif } } -#else -public final class Relay { - internal let _dependency: T +extension Relay { - private init(dependency: T) { - self._dependency = dependency + @available(*, unavailable) + subscript(dynamicMember member: String) -> Never { + fatalError("must not be accessible") } } -#endif extension Relay { @@ -103,6 +114,16 @@ extension Relay where T: InputType { return _onEvent(for: keyPath) } + + #if swift(>=5.1) + public subscript(dynamicMember member: KeyPath) -> AcceptableObserver { + return accept(for: member) + } + + public subscript(dynamicMember member: KeyPath) -> AnyObserver { + return onEvent(for: member) + } + #endif } // - MARK: Relay @@ -168,31 +189,3 @@ extension Relay where T: ThrowableValueAccessibleObservable { return _dependency.asObservable() } } - -#if swift(>=5.1) -extension Relay where T: OutputType { - - public var observables: DML.Observables { - return DML.Observables(_dependency) - } - - public var values: DML.Values { - return DML.Values(_dependency) - } - - public var throwableValues: DML.ThrowableValues { - return DML.ThrowableValues(_dependency) - } -} - -extension Relay where T: InputType { - - public subscript(dynamicMember member: KeyPath) -> AcceptableObserver { - return accept(for: member) - } - - public subscript(dynamicMember member: KeyPath) -> AnyObserver { - return onEvent(for: member) - } -} -#endif From 7f0eb2d47c0b30534dd3b981060de569a2bc1249 Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Sun, 8 Sep 2019 02:07:24 +0900 Subject: [PATCH 06/17] cleanup --- Unio/Dependency.swift | 12 +-- Unio/DynamicMemberLookupAdapter.swift | 62 ++++++++----- Unio/Relay.swift | 101 ++++++++-------------- Unio/UnioStream.swift | 12 +-- UnioTests/TestCases/DependencyTests.swift | 18 ++++ UnioTests/TestCases/RelayTests.swift | 32 +++++++ UnioTests/TestCases/UnioStreamTests.swift | 13 +++ 7 files changed, 142 insertions(+), 108 deletions(-) diff --git a/Unio/Dependency.swift b/Unio/Dependency.swift index 52c7da4..7f44135 100644 --- a/Unio/Dependency.swift +++ b/Unio/Dependency.swift @@ -17,26 +17,18 @@ public final class Dependency=5.1) public let inputObservables: DMLA.Observables - #endif - - private let _input: Input internal init(input: Input, state: State, extra: Extra) { - self._input = input self.state = state self.extra = extra - - #if swift(>=5.1) self.inputObservables = DMLA.Observables(input) - #endif } /// Makes possible to get Observable from `Input`. public func inputObservable(for keyPath: KeyPath) -> Observable { - return _input[keyPath: keyPath].asObservable() + return inputObservables[dynamicMember: keyPath] } /// Returns read-only value accessible object (e.g. BehaviorRelay). @@ -53,9 +45,7 @@ public final class Dependency=5.1) public func readOnlyReferences(from output: Relay) -> DMLA.ReadOnlyReferences { return DMLA.ReadOnlyReferences(output) } - #endif } diff --git a/Unio/DynamicMemberLookupAdapter.swift b/Unio/DynamicMemberLookupAdapter.swift index c988dd0..9efebc5 100644 --- a/Unio/DynamicMemberLookupAdapter.swift +++ b/Unio/DynamicMemberLookupAdapter.swift @@ -9,13 +9,12 @@ import Foundation import RxSwift -#if swift(>=5.1) public typealias DMLA = DynamicMemberLookupAdapter public enum DynamicMemberLookupAdapter { @dynamicMemberLookup - public struct Observables { + public final class Observables { private let object: T @@ -23,13 +22,13 @@ public enum DynamicMemberLookupAdapter { self.object = object } - public subscript(dynamicMember member: KeyPath) -> Observable { - return object[keyPath: member].asObservable() + public subscript(dynamicMember keyPath: KeyPath) -> Observable { + return object[keyPath: keyPath].asObservable() } } @dynamicMemberLookup - public struct Values { + public final class Values { private let object: T @@ -37,27 +36,17 @@ public enum DynamicMemberLookupAdapter { self.object = object } - public subscript(dynamicMember member: KeyPath) -> U.Element { - return object[keyPath: member].value + public subscript(dynamicMember keyPath: KeyPath) -> U.Element { + return object[keyPath: keyPath].value } - } - - @dynamicMemberLookup - public struct ThrowableValues { - private let object: T - - init(_ object: T) { - self.object = object - } - - public subscript(dynamicMember member: KeyPath) -> AnyThrowableValue { - return AnyThrowableValue(object[keyPath: member]) + public subscript(dynamicMember keyPath: KeyPath) -> AnyThrowableValue { + return AnyThrowableValue(object[keyPath: keyPath]) } } @dynamicMemberLookup - public struct ReadOnlyReferences { + public final class ReadOnlyReferences { private let output: Relay @@ -65,13 +54,38 @@ public enum DynamicMemberLookupAdapter { self.output = output } - public subscript(dynamicMember member: KeyPath) -> ReadOnly { - return ReadOnly(output, for: member) + public subscript(dynamicMember keyPath: KeyPath) -> ReadOnly { + return ReadOnly(output, for: keyPath) } - public subscript(dynamicMember member: KeyPath) -> ReadOnly { - return ReadOnly(output, for: member) + public subscript(dynamicMember keyPath: KeyPath) -> ReadOnly { + return ReadOnly(output, for: keyPath) } } } + +#if swift(<5.1) +extension DMLA.Observables { + + @available(*, unavailable) + subscript(dynamicMember member: String) -> Never { + fatalError("must not be accessible") + } +} + +extension DMLA.Values { + + @available(*, unavailable) + subscript(dynamicMember member: String) -> Never { + fatalError("must not be accessible") + } +} + +extension DMLA.ReadOnlyReferences { + + @available(*, unavailable) + subscript(dynamicMember member: String) -> Never { + fatalError("must not be accessible") + } +} #endif diff --git a/Unio/Relay.swift b/Unio/Relay.swift index 0f327c1..d3489df 100644 --- a/Unio/Relay.swift +++ b/Unio/Relay.swift @@ -19,25 +19,19 @@ public final class Relay { internal let _dependency: T - #if swift(>=5.1) - public let observables: DMLA.Observables + private let _observables: DMLA.Observables? + private let _values: DMLA.Values? - public let values: DMLA.Values - - public let throwableValues: DMLA.ThrowableValues - #endif - - private init(dependency: T) { + private init(dependency: T, + observables: DMLA.Observables?, + values: DMLA.Values?) { self._dependency = dependency - - #if swift(>=5.1) - self.observables = DMLA.Observables(dependency) - self.values = DMLA.Values(dependency) - self.throwableValues = DMLA.ThrowableValues(dependency) - #endif + self._observables = observables + self._values = values } } +#if swift(<5.1) extension Relay { @available(*, unavailable) @@ -45,42 +39,7 @@ extension Relay { fatalError("must not be accessible") } } - -extension Relay { - - private func _observable(for keyPath: KeyPath) -> Observable { - - return _dependency[keyPath: keyPath].asObservable() - } - - private func _accept(_ value: U.Element, for keyPath: KeyPath) { - - _dependency[keyPath: keyPath].accept(value) - } - - private func _accept(for keyPath: KeyPath) -> AcceptableObserver { - - return AcceptableObserver(_dependency[keyPath: keyPath]) - } - - private func _onEvent(_ event: Event, for keyPath: KeyPath) { - _dependency[keyPath: keyPath].on(event) - } - - private func _onEvent(for keyPath: KeyPath) -> AnyObserver { - - return _dependency[keyPath: keyPath].asObserver() - } - - private func _value(for keyPath: KeyPath) -> U.Element { - - return _dependency[keyPath: keyPath].value - } - - private func _value(for keyPath: KeyPath) throws -> U.Element { - return try _dependency[keyPath: keyPath].throwableValue() - } -} +#endif // - MARK: Relay @@ -88,69 +47,77 @@ extension Relay where T: InputType { /// Initializes with `Input`. public convenience init(_ dependency: T) { - self.init(dependency: dependency) + self.init(dependency: dependency, observables: nil, values: nil) } /// Accepts `event` and emits it to subscribers via `Input`. public func accept(_ value: U.Element, for keyPath: KeyPath) { - _accept(value, for: keyPath) + accept(for: keyPath).accept(value) } /// Send `event` to this observer via `Input`. public func accept(for keyPath: KeyPath) -> AcceptableObserver { - return _accept(for: keyPath) + return self[dynamicMember: keyPath] } /// Notify observer about sequence event via `Input`. public func onEvent(_ event: Event, for keyPath: KeyPath) { - _onEvent(event, for: keyPath) + onEvent(for: keyPath).on(event) } /// Send `event` to this observer via `Input`. public func onEvent(for keyPath: KeyPath) -> AnyObserver { - return _onEvent(for: keyPath) + return self[dynamicMember: keyPath] } - #if swift(>=5.1) - public subscript(dynamicMember member: KeyPath) -> AcceptableObserver { - return accept(for: member) + public subscript(dynamicMember keyPath: KeyPath) -> AcceptableObserver { + return AcceptableObserver(_dependency[keyPath: keyPath]) } - public subscript(dynamicMember member: KeyPath) -> AnyObserver { - return onEvent(for: member) + public subscript(dynamicMember keyPath: KeyPath) -> AnyObserver { + return _dependency[keyPath: keyPath].asObserver() } - #endif } // - MARK: Relay extension Relay where T: OutputType { + public var observables: DMLA.Observables { + return _observables! + } + + public var values: DMLA.Values { + return _values! + } + /// Initializes with `Output`. public convenience init(_ dependency: T) { - self.init(dependency: dependency) + self.init(dependency: dependency, + observables: .init(dependency), + values: .init(dependency)) } /// Makes possible to get Observable from `Output`. public func observable(for keyPath: KeyPath) -> Observable { - return _observable(for: keyPath) + return observables[dynamicMember: keyPath] } /// Makes possible to get value from Output when generic parameter is `BehaviorRelay`. public func value(for keyPath: KeyPath) -> U.Element { - return _value(for: keyPath) + return values[dynamicMember: keyPath] } /// Makes possible to get value from Output when generic parameter is `BehaviorSubject`. public func value(for keyPath: KeyPath) throws -> U.Element { - return try _value(for: keyPath) + return try values[dynamicMember: keyPath].throwableValue() } } @@ -164,7 +131,7 @@ extension Relay where T: ValueAccessibleObservable { internal convenience init(_ output: Relay, for keyPath: KeyPath) { let behaviorRelay = output._dependency[keyPath: keyPath] - self.init(dependency: behaviorRelay) + self.init(dependency: behaviorRelay, observables: nil, values: nil) } public func asObservable() -> Observable { @@ -178,7 +145,7 @@ extension Relay where T: ThrowableValueAccessibleObservable { internal convenience init(_ output: Relay, for keyPath: KeyPath) { let behaviorSubject = output._dependency[keyPath: keyPath] - self.init(dependency: behaviorSubject) + self.init(dependency: behaviorSubject, observables: nil, values: nil) } public func throwableValue() throws -> T.Element { diff --git a/Unio/UnioStream.swift b/Unio/UnioStream.swift index e3e0a9b..882cd1f 100644 --- a/Unio/UnioStream.swift +++ b/Unio/UnioStream.swift @@ -12,9 +12,9 @@ open class UnioStream { public let input: Relay public let output: Relay - private let state: StateType - private let extra: ExtraType - private let logic: Logic + private let _state: Logic.State + private let _extra: Logic.Extra + private let _logic: Logic /// - note: initialize parameters are retained in UnioStream public init(input: Logic.Input, state: Logic.State, extra: Logic.Extra, logic: Logic) { @@ -22,8 +22,8 @@ open class UnioStream { let output = logic.bind(from: dependency) self.input = Relay(input) self.output = Relay(output) - self.state = state - self.extra = extra - self.logic = logic + self._state = state + self._extra = extra + self._logic = logic } } diff --git a/UnioTests/TestCases/DependencyTests.swift b/UnioTests/TestCases/DependencyTests.swift index 94b8b22..90acd5e 100644 --- a/UnioTests/TestCases/DependencyTests.swift +++ b/UnioTests/TestCases/DependencyTests.swift @@ -26,8 +26,13 @@ final class DependencyTests: XCTestCase { let testTarget = dependency.testTarget let stack = BehaviorRelay(value: nil) + #if swift(>=5.1) + let disposable = testTarget.inputObservables.relay + .bind(to: stack) + #else let disposable = testTarget.inputObservable(for: \.relay) .bind(to: stack) + #endif dependency.inputRelay.accept(expected) @@ -42,8 +47,13 @@ final class DependencyTests: XCTestCase { let testTarget = dependency.testTarget let stack = BehaviorRelay(value: nil) + #if swift(>=5.1) + let disposable = testTarget.inputObservables.subject + .bind(to: stack) + #else let disposable = testTarget.inputObservable(for: \.subject) .bind(to: stack) + #endif dependency.inputSubject.onNext(expected) @@ -57,7 +67,11 @@ final class DependencyTests: XCTestCase { let expected = "test-ValueAccessible" let testTarget = dependency.testTarget + #if swift(>=5.1) + let readOnly = testTarget.readOnlyReferences(from: dependency.output).relay + #else let readOnly = testTarget.readOnlyReference(from: dependency.output, for: \.relay) + #endif dependency.outputRelay.accept(expected) @@ -69,7 +83,11 @@ final class DependencyTests: XCTestCase { let expected = "test-ThrowableValueAccessible" let testTarget = dependency.testTarget + #if swift(>=5.1) + let readOnly = testTarget.readOnlyReferences(from: dependency.output).subject + #else let readOnly = testTarget.readOnlyReference(from: dependency.output, for: \.subject) + #endif dependency.outputSubject.onNext(expected) diff --git a/UnioTests/TestCases/RelayTests.swift b/UnioTests/TestCases/RelayTests.swift index 01c7366..8c2cd99 100644 --- a/UnioTests/TestCases/RelayTests.swift +++ b/UnioTests/TestCases/RelayTests.swift @@ -30,7 +30,11 @@ final class RelayTests: XCTestCase { let disposable = dependency.inputSubject .bind(to: stack) + #if swift(>=5.1) + testTarget.subject.onNext(expected) + #else testTarget.onEvent(.next(expected), for: \.subject) + #endif XCTAssertEqual(stack.value, expected) @@ -46,8 +50,13 @@ final class RelayTests: XCTestCase { let disposable1 = dependency.inputSubject .bind(to: stack) + #if swift(>=5.1) + let disposable2 = Observable.just(expected) + .bind(to: testTarget.subject) + #else let disposable2 = Observable.just(expected) .bind(to: testTarget.onEvent(for: \.subject)) + #endif XCTAssertEqual(stack.value, expected) @@ -64,7 +73,11 @@ final class RelayTests: XCTestCase { let disposable = dependency.inputRelay .bind(to: stack) + #if swift(>=5.1) + testTarget.relay.accept(expected) + #else testTarget.accept(expected, for: \.relay) + #endif XCTAssertEqual(stack.value, expected) @@ -80,8 +93,13 @@ final class RelayTests: XCTestCase { let disposable1 = dependency.inputRelay .bind(to: stack) + #if swift(>=5.1) + let disposable2 = Observable.just(expected) + .bind(to: testTarget.relay) + #else let disposable2 = Observable.just(expected) .bind(to: testTarget.accept(for: \.relay)) + #endif XCTAssertEqual(stack.value, expected) @@ -95,8 +113,13 @@ final class RelayTests: XCTestCase { let testTarget = dependency.testTargetOutput let stack = BehaviorRelay(value: nil) + #if swift(>=5.1) + let disposable = testTarget.observables.relay + .bind(to: stack) + #else let disposable = testTarget.observable(for: \.relay) .bind(to: stack) + #endif dependency.outputRelay.accept(expected) @@ -112,7 +135,11 @@ final class RelayTests: XCTestCase { dependency.outputRelay.accept(expected) + #if swift(>=5.1) + XCTAssertEqual(testTarget.values.relay, expected) + #else XCTAssertEqual(testTarget.value(for: \.relay), expected) + #endif } func testOutput_throwable_value() { @@ -122,7 +149,12 @@ final class RelayTests: XCTestCase { dependency.outputSubject.onNext(expected) + + #if swift(>=5.1) + XCTAssertEqual(try testTarget.values.subject.throwableValue(), expected) + #else XCTAssertEqual(try testTarget.value(for: \.subject), expected) + #endif } func testValueAccessibleObservable_value() { diff --git a/UnioTests/TestCases/UnioStreamTests.swift b/UnioTests/TestCases/UnioStreamTests.swift index ac853a3..ce1ca4f 100644 --- a/UnioTests/TestCases/UnioStreamTests.swift +++ b/UnioTests/TestCases/UnioStreamTests.swift @@ -29,7 +29,11 @@ final class UnioStreamTests: XCTestCase { let disposable = dependency.input.relay .bind(to: stack) + #if swift(>=5.1) + testTarget.input.relay.accept(expected) + #else testTarget.input.accept(expected, for: \.relay) + #endif XCTAssertEqual(expected, stack.value) @@ -43,7 +47,11 @@ final class UnioStreamTests: XCTestCase { dependency.output.relay.accept(expected) + #if swift(>=5.1) + XCTAssertEqual(expected, testTarget.output.values.relay) + #else XCTAssertEqual(expected, testTarget.output.value(for: \.relay)) + #endif } func testDependency() { @@ -69,8 +77,13 @@ final class UnioStreamTests: XCTestCase { let expected = "test-input" let stack = BehaviorRelay(value: nil) + #if swift(>=5.1) + let disposable = value.inputObservables.relay + .bind(to: stack) + #else let disposable = value.inputObservable(for: \.relay) .bind(to: stack) + #endif dependency.input.relay.accept(expected) From 55c4fa1c07cc11fab162e146a9f02a63b3bcd353 Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Thu, 12 Sep 2019 15:05:18 +0900 Subject: [PATCH 07/17] update osx_image to xcode11 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index a1332d3..41625d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c os: osx -osx_image: xcode10.2 +osx_image: xcode11 before_install: - gem install xcpretty - carthage update --no-use-binaries --platform ios From ae2f97709dfad3fd14cb886a72dd13f000ff2a8d Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Thu, 12 Sep 2019 22:51:40 +0900 Subject: [PATCH 08/17] fix conflict --- Unio.xcodeproj/project.pbxproj | 12 ++---- Unio/AnyThrowableValue.swift | 20 --------- Unio/Dependency.swift | 29 +++---------- Unio/{ReadOnly.swift => Deprecated.swift} | 21 ++++++++- Unio/DynamicMemberLookupAdapter.swift | 38 +++-------------- Unio/Relay.swift | 52 +++++++++++------------ UnioTests/TestCases/DependencyTests.swift | 20 +++------ UnioTests/TestCases/RelayTests.swift | 6 +-- UnioTests/TestCases/UnioStreamTests.swift | 2 +- 9 files changed, 71 insertions(+), 129 deletions(-) delete mode 100644 Unio/AnyThrowableValue.swift rename Unio/{ReadOnly.swift => Deprecated.swift} (75%) diff --git a/Unio.xcodeproj/project.pbxproj b/Unio.xcodeproj/project.pbxproj index 7c4e38c..2ae0909 100644 --- a/Unio.xcodeproj/project.pbxproj +++ b/Unio.xcodeproj/project.pbxproj @@ -26,7 +26,7 @@ 9D9EEC13228172F400DF5D97 /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D2E26A92240D1F200C9EDF7 /* RxCocoa.framework */; }; 9D9EEC14228172F900DF5D97 /* RxCocoa.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 9D2E26A92240D1F200C9EDF7 /* RxCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9DA555CB2241DE8400EC8CC3 /* BehaviorSubjectType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555CA2241DE8400EC8CC3 /* BehaviorSubjectType.swift */; }; - 9DA555D12241F95400EC8CC3 /* ReadOnly.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D02241F95400EC8CC3 /* ReadOnly.swift */; }; + 9DA555D12241F95400EC8CC3 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D02241F95400EC8CC3 /* Deprecated.swift */; }; 9DA555D32241FC0400EC8CC3 /* AcceptableRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D22241FC0400EC8CC3 /* AcceptableRelay.swift */; }; 9DA555D52241FC6600EC8CC3 /* ValueAccessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D42241FC6600EC8CC3 /* ValueAccessible.swift */; }; ED6897C122B184EE00B04DA0 /* PrimitiveProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */; }; @@ -38,7 +38,6 @@ ED74A73C2243E43D00D4E99C /* UnioStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A73B2243E43D00D4E99C /* UnioStreamTests.swift */; }; EDD118F62289CFD70055C0AE /* AcceptableObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD118F52289CFD70055C0AE /* AcceptableObserver.swift */; }; EDD118F82289E1170055C0AE /* DynamicMemberLookupAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD118F72289E1170055C0AE /* DynamicMemberLookupAdapter.swift */; }; - EDD118FA2289E1630055C0AE /* AnyThrowableValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD118F92289E1630055C0AE /* AnyThrowableValue.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -87,7 +86,7 @@ 9D2E26A92240D1F200C9EDF7 /* RxCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxCocoa.framework; path = Carthage/Build/iOS/RxCocoa.framework; sourceTree = ""; }; 9D9EEC0A228168F800DF5D97 /* RxRelay.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxRelay.framework; path = Carthage/Build/iOS/RxRelay.framework; sourceTree = ""; }; 9DA555CA2241DE8400EC8CC3 /* BehaviorSubjectType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BehaviorSubjectType.swift; sourceTree = ""; }; - 9DA555D02241F95400EC8CC3 /* ReadOnly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadOnly.swift; sourceTree = ""; }; + 9DA555D02241F95400EC8CC3 /* Deprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deprecated.swift; sourceTree = ""; }; 9DA555D22241FC0400EC8CC3 /* AcceptableRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptableRelay.swift; sourceTree = ""; }; 9DA555D42241FC6600EC8CC3 /* ValueAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueAccessible.swift; sourceTree = ""; }; ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveProperty.swift; sourceTree = ""; }; @@ -97,7 +96,6 @@ ED74A73B2243E43D00D4E99C /* UnioStreamTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnioStreamTests.swift; sourceTree = ""; }; EDD118F52289CFD70055C0AE /* AcceptableObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptableObserver.swift; sourceTree = ""; }; EDD118F72289E1170055C0AE /* DynamicMemberLookupAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicMemberLookupAdapter.swift; sourceTree = ""; }; - EDD118F92289E1630055C0AE /* AnyThrowableValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyThrowableValue.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -148,8 +146,8 @@ children = ( 9DA555C92241DE5F00EC8CC3 /* RxTypes */, EDD118F52289CFD70055C0AE /* AcceptableObserver.swift */, - EDD118F92289E1630055C0AE /* AnyThrowableValue.swift */, 9D2E269C2240D1CA00C9EDF7 /* Dependency.swift */, + 9DA555D02241F95400EC8CC3 /* Deprecated.swift */, EDD118F72289E1170055C0AE /* DynamicMemberLookupAdapter.swift */, 9D2E26972240D1CA00C9EDF7 /* ExtraType.swift */, 9D2E267D2240D18500C9EDF7 /* Info.plist */, @@ -157,7 +155,6 @@ 9D2E26952240D1CA00C9EDF7 /* LogicType.swift */, 9D2E269B2240D1CA00C9EDF7 /* OutputType.swift */, ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */, - 9DA555D02241F95400EC8CC3 /* ReadOnly.swift */, 9D2E26942240D1CA00C9EDF7 /* Relay.swift */, 9D2E26962240D1CA00C9EDF7 /* StateType.swift */, 9D2E267C2240D18500C9EDF7 /* Unio.h */, @@ -322,10 +319,10 @@ files = ( EDD118F62289CFD70055C0AE /* AcceptableObserver.swift in Sources */, 9DA555D32241FC0400EC8CC3 /* AcceptableRelay.swift in Sources */, - EDD118FA2289E1630055C0AE /* AnyThrowableValue.swift in Sources */, 9D2E26A32240D1CA00C9EDF7 /* BehaviorRelayType.swift in Sources */, 9DA555CB2241DE8400EC8CC3 /* BehaviorSubjectType.swift in Sources */, 9D2E26A62240D1CA00C9EDF7 /* Dependency.swift in Sources */, + 9DA555D12241F95400EC8CC3 /* Deprecated.swift in Sources */, EDD118F82289E1170055C0AE /* DynamicMemberLookupAdapter.swift in Sources */, 9D2E26A12240D1CA00C9EDF7 /* ExtraType.swift in Sources */, 9D2E26A22240D1CA00C9EDF7 /* InputType.swift in Sources */, @@ -333,7 +330,6 @@ 9D2E26A52240D1CA00C9EDF7 /* OutputType.swift in Sources */, ED6897C122B184EE00B04DA0 /* PrimitiveProperty.swift in Sources */, 9D2E269D2240D1CA00C9EDF7 /* PublishRelayType.swift in Sources */, - 9DA555D12241F95400EC8CC3 /* ReadOnly.swift in Sources */, 9D2E269E2240D1CA00C9EDF7 /* Relay.swift in Sources */, 9D2E26A02240D1CA00C9EDF7 /* StateType.swift in Sources */, 9D2E26A42240D1CA00C9EDF7 /* UnioStream.swift in Sources */, diff --git a/Unio/AnyThrowableValue.swift b/Unio/AnyThrowableValue.swift deleted file mode 100644 index 32f431d..0000000 --- a/Unio/AnyThrowableValue.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// AnyThrowableValue.swift -// Unio -// -// Created by marty-suzuki on 2019/05/14. -// Copyright © 2019 tv.abema. All rights reserved. -// - -public struct AnyThrowableValue: ThrowableValueAccessible { - - private let get: () throws -> Element - - init(_ value: T) where T.Element == Element { - self.get = { try value.throwableValue() } - } - - public func throwableValue() throws -> Element { - return try get() - } -} diff --git a/Unio/Dependency.swift b/Unio/Dependency.swift index 11ee3cc..dbf248c 100644 --- a/Unio/Dependency.swift +++ b/Unio/Dependency.swift @@ -31,38 +31,21 @@ public final class Dependency(from output: Relay, for keyPath: KeyPath) -> ReadOnly { - return ReadOnly(output, for: keyPath) - } - - /// Returns read-only value accessible object (e.g. BehaviorSubject). - /// - /// - note: Object is reference, not copied one. - @available(*, deprecated, renamed: "property(from:for:)") - public func readOnlyReference(from output: Relay, for keyPath: KeyPath) -> ReadOnly { - return ReadOnly(output, for: keyPath) - } - -<<<<<<< HEAD - public func readOnlyReferences(from output: Relay) -> DMLA.ReadOnlyReferences { - return DMLA.ReadOnlyReferences(output) -======= /// Returns property that accessible to value (e.g. BehaviorRelay). /// /// - note: Object is reference, not copied one. public func property(from output: Relay, for keyPath: KeyPath) -> Property { - return Property(output, for: keyPath) + return properties(from: output)[dynamicMember: keyPath] } /// Returns property that accessible to throwable value (e.g. BehaviorSubject). /// /// - note: Object is reference, not copied one. public func property(from output: Relay, for keyPath: KeyPath) -> ThrowableProperty { - return ThrowableProperty(output, for: keyPath) ->>>>>>> origin/add-property + return properties(from: output)[dynamicMember: keyPath] + } + + public func properties(from output: Relay) -> DMLA.Properties { + return DMLA.Properties(output) } } diff --git a/Unio/ReadOnly.swift b/Unio/Deprecated.swift similarity index 75% rename from Unio/ReadOnly.swift rename to Unio/Deprecated.swift index adb27af..66a22f4 100644 --- a/Unio/ReadOnly.swift +++ b/Unio/Deprecated.swift @@ -1,5 +1,5 @@ // -// ReadOnly.swift +// Deprecated.swift // Unio // // Created by marty-suzuki on 2019/03/20. @@ -66,3 +66,22 @@ extension ReadOnly: ThrowableValueAccessible where T: ThrowableValueAccessibleOb asObservable: { behaviorSubject.asObservable() }) } } + +extension Dependency { + + /// Returns read-only value accessible object (e.g. BehaviorRelay). + /// + /// - note: Object is reference, not copied one. + @available(*, deprecated, renamed: "property(from:for:)") + public func readOnlyReference(from output: Relay, for keyPath: KeyPath) -> ReadOnly { + return ReadOnly(output, for: keyPath) + } + + /// Returns read-only value accessible object (e.g. BehaviorSubject). + /// + /// - note: Object is reference, not copied one. + @available(*, deprecated, renamed: "property(from:for:)") + public func readOnlyReference(from output: Relay, for keyPath: KeyPath) -> ReadOnly { + return ReadOnly(output, for: keyPath) + } +} diff --git a/Unio/DynamicMemberLookupAdapter.swift b/Unio/DynamicMemberLookupAdapter.swift index 9efebc5..e2077fa 100644 --- a/Unio/DynamicMemberLookupAdapter.swift +++ b/Unio/DynamicMemberLookupAdapter.swift @@ -28,25 +28,7 @@ public enum DynamicMemberLookupAdapter { } @dynamicMemberLookup - public final class Values { - - private let object: T - - init(_ object: T) { - self.object = object - } - - public subscript(dynamicMember keyPath: KeyPath) -> U.Element { - return object[keyPath: keyPath].value - } - - public subscript(dynamicMember keyPath: KeyPath) -> AnyThrowableValue { - return AnyThrowableValue(object[keyPath: keyPath]) - } - } - - @dynamicMemberLookup - public final class ReadOnlyReferences { + public final class Properties { private let output: Relay @@ -54,12 +36,12 @@ public enum DynamicMemberLookupAdapter { self.output = output } - public subscript(dynamicMember keyPath: KeyPath) -> ReadOnly { - return ReadOnly(output, for: keyPath) + public subscript(dynamicMember keyPath: KeyPath) -> Property { + return Property(output, for: keyPath) } - public subscript(dynamicMember keyPath: KeyPath) -> ReadOnly { - return ReadOnly(output, for: keyPath) + public subscript(dynamicMember keyPath: KeyPath) -> ThrowableProperty { + return ThrowableProperty(output, for: keyPath) } } } @@ -73,15 +55,7 @@ extension DMLA.Observables { } } -extension DMLA.Values { - - @available(*, unavailable) - subscript(dynamicMember member: String) -> Never { - fatalError("must not be accessible") - } -} - -extension DMLA.ReadOnlyReferences { +extension DMLA.Properties { @available(*, unavailable) subscript(dynamicMember member: String) -> Never { diff --git a/Unio/Relay.swift b/Unio/Relay.swift index d3489df..8f3ca2a 100644 --- a/Unio/Relay.swift +++ b/Unio/Relay.swift @@ -19,15 +19,8 @@ public final class Relay { internal let _dependency: T - private let _observables: DMLA.Observables? - private let _values: DMLA.Values? - - private init(dependency: T, - observables: DMLA.Observables?, - values: DMLA.Values?) { + private init(dependency: T) { self._dependency = dependency - self._observables = observables - self._values = values } } @@ -47,7 +40,7 @@ extension Relay where T: InputType { /// Initializes with `Input`. public convenience init(_ dependency: T) { - self.init(dependency: dependency, observables: nil, values: nil) + self.init(dependency: dependency) } /// Accepts `event` and emits it to subscribers via `Input`. @@ -87,37 +80,42 @@ extension Relay where T: InputType { extension Relay where T: OutputType { - public var observables: DMLA.Observables { - return _observables! - } - - public var values: DMLA.Values { - return _values! - } - /// Initializes with `Output`. public convenience init(_ dependency: T) { - self.init(dependency: dependency, - observables: .init(dependency), - values: .init(dependency)) + self.init(dependency: dependency) } /// Makes possible to get Observable from `Output`. public func observable(for keyPath: KeyPath) -> Observable { - return observables[dynamicMember: keyPath] + return self[dynamicMember: keyPath] } /// Makes possible to get value from Output when generic parameter is `BehaviorRelay`. - public func value(for keyPath: KeyPath) -> U.Element { + public func value(for keyPath: KeyPath) -> U.Element { - return values[dynamicMember: keyPath] + return self[dynamicMember: keyPath].value } /// Makes possible to get value from Output when generic parameter is `BehaviorSubject`. - public func value(for keyPath: KeyPath) throws -> U.Element { + public func value(for keyPath: KeyPath) throws -> U.Element { + + return try self[dynamicMember: keyPath].throwableValue() + } + + public subscript(dynamicMember keyPath: KeyPath) -> Observable { + + return _dependency[keyPath: keyPath].asObservable() + } + + public subscript(dynamicMember keyPath: KeyPath) -> Property { + + return Property(self, for: keyPath) + } + + public subscript(dynamicMember keyPath: KeyPath) -> ThrowableProperty { - return try values[dynamicMember: keyPath].throwableValue() + return ThrowableProperty(self, for: keyPath) } } @@ -131,7 +129,7 @@ extension Relay where T: ValueAccessibleObservable { internal convenience init(_ output: Relay, for keyPath: KeyPath) { let behaviorRelay = output._dependency[keyPath: keyPath] - self.init(dependency: behaviorRelay, observables: nil, values: nil) + self.init(dependency: behaviorRelay) } public func asObservable() -> Observable { @@ -145,7 +143,7 @@ extension Relay where T: ThrowableValueAccessibleObservable { internal convenience init(_ output: Relay, for keyPath: KeyPath) { let behaviorSubject = output._dependency[keyPath: keyPath] - self.init(dependency: behaviorSubject, observables: nil, values: nil) + self.init(dependency: behaviorSubject) } public func throwableValue() throws -> T.Element { diff --git a/UnioTests/TestCases/DependencyTests.swift b/UnioTests/TestCases/DependencyTests.swift index 9d2aad3..2a0b987 100644 --- a/UnioTests/TestCases/DependencyTests.swift +++ b/UnioTests/TestCases/DependencyTests.swift @@ -62,40 +62,32 @@ final class DependencyTests: XCTestCase { disposable.dispose() } - func testRelayOnly_ValueAccessible() { + func testProperty_ValueAccessible() { let expected = "test-ValueAccessible" let testTarget = dependency.testTarget -<<<<<<< HEAD #if swift(>=5.1) - let readOnly = testTarget.readOnlyReferences(from: dependency.output).relay + let property = testTarget.properties(from: dependency.output).relay #else - let readOnly = testTarget.readOnlyReference(from: dependency.output, for: \.relay) - #endif -======= let property = testTarget.property(from: dependency.output, for: \.relay) ->>>>>>> origin/add-property + #endif dependency.outputRelay.accept(expected) XCTAssertEqual(property.value, expected) } - func testRelayOnly_ThrowableValueAccessible() { + func testProperty_ThrowableValueAccessible() { let expected = "test-ThrowableValueAccessible" let testTarget = dependency.testTarget -<<<<<<< HEAD #if swift(>=5.1) - let readOnly = testTarget.readOnlyReferences(from: dependency.output).subject + let property = testTarget.properties(from: dependency.output).subject #else - let readOnly = testTarget.readOnlyReference(from: dependency.output, for: \.subject) - #endif -======= let property = testTarget.property(from: dependency.output, for: \.subject) ->>>>>>> origin/add-property + #endif dependency.outputSubject.onNext(expected) diff --git a/UnioTests/TestCases/RelayTests.swift b/UnioTests/TestCases/RelayTests.swift index 8c2cd99..abb6027 100644 --- a/UnioTests/TestCases/RelayTests.swift +++ b/UnioTests/TestCases/RelayTests.swift @@ -114,7 +114,7 @@ final class RelayTests: XCTestCase { let stack = BehaviorRelay(value: nil) #if swift(>=5.1) - let disposable = testTarget.observables.relay + let disposable = testTarget.relay .bind(to: stack) #else let disposable = testTarget.observable(for: \.relay) @@ -136,7 +136,7 @@ final class RelayTests: XCTestCase { dependency.outputRelay.accept(expected) #if swift(>=5.1) - XCTAssertEqual(testTarget.values.relay, expected) + XCTAssertEqual(testTarget.relay.value, expected) #else XCTAssertEqual(testTarget.value(for: \.relay), expected) #endif @@ -151,7 +151,7 @@ final class RelayTests: XCTestCase { #if swift(>=5.1) - XCTAssertEqual(try testTarget.values.subject.throwableValue(), expected) + XCTAssertEqual(try testTarget.subject.throwableValue(), expected) #else XCTAssertEqual(try testTarget.value(for: \.subject), expected) #endif diff --git a/UnioTests/TestCases/UnioStreamTests.swift b/UnioTests/TestCases/UnioStreamTests.swift index ce1ca4f..15836e2 100644 --- a/UnioTests/TestCases/UnioStreamTests.swift +++ b/UnioTests/TestCases/UnioStreamTests.swift @@ -48,7 +48,7 @@ final class UnioStreamTests: XCTestCase { dependency.output.relay.accept(expected) #if swift(>=5.1) - XCTAssertEqual(expected, testTarget.output.values.relay) + XCTAssertEqual(expected, testTarget.output.relay.value) #else XCTAssertEqual(expected, testTarget.output.value(for: \.relay)) #endif From d833791c9f00ea0888efd9ac01fb945bf9b367ed Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 13 Sep 2019 02:06:03 +0900 Subject: [PATCH 09/17] add breaking changes - rename Relay to InputWrapper - rename Relay to OutputWrapper these changes make better type inference - remove ReadOnly --- .../UnioSample/GitHubSearchAPIStream.swift | 4 +- .../UnioSample/GitHubSearchLogicStream.swift | 10 +- .../GitHubSearchViewController.swift | 4 +- .../UnioSample/GitHubSearchViewStream.swift | 10 +- .../MockGitHubSearchAPIStream.swift | 8 +- Unio.xcodeproj/project.pbxproj | 36 ++---- Unio/AcceptableObserver.swift | 39 ------ Unio/Dependency.swift | 22 +--- Unio/Deprecated.swift | 87 ------------- Unio/DynamicMemberLookupAdapter.swift | 65 ---------- Unio/PrimitiveProperty.swift | 4 +- Unio/UnioStream.swift | 8 +- Unio/{Relay.swift => Wrappers.swift} | 115 +++++++++-------- UnioTests/TestCases/DependencyTests.swift | 37 +----- ...sts.swift => PrimitivePropertyTests.swift} | 8 +- UnioTests/TestCases/UnioStreamTests.swift | 2 +- .../{RelayTests.swift => WrappersTests.swift} | 121 ++++++++---------- 17 files changed, 162 insertions(+), 418 deletions(-) delete mode 100644 Unio/AcceptableObserver.swift delete mode 100644 Unio/Deprecated.swift delete mode 100644 Unio/DynamicMemberLookupAdapter.swift rename Unio/{Relay.swift => Wrappers.swift} (60%) rename UnioTests/TestCases/{ReadOnlyTests.swift => PrimitivePropertyTests.swift} (90%) rename UnioTests/TestCases/{RelayTests.swift => WrappersTests.swift} (74%) diff --git a/Example/UnioSample/GitHubSearchAPIStream.swift b/Example/UnioSample/GitHubSearchAPIStream.swift index 1788f33..2fe8d88 100644 --- a/Example/UnioSample/GitHubSearchAPIStream.swift +++ b/Example/UnioSample/GitHubSearchAPIStream.swift @@ -11,8 +11,8 @@ import RxSwift import RxRelay protocol GitHubSearchAPIStreamType: AnyObject { - var input: Relay { get } - var output: Relay { get } + var input: InputWrapper { get } + var output: OutputWrapper { get } } final class GitHubSearchAPIStream: UnioStream, GitHubSearchAPIStreamType { diff --git a/Example/UnioSample/GitHubSearchLogicStream.swift b/Example/UnioSample/GitHubSearchLogicStream.swift index fd2a97d..c0b1c35 100644 --- a/Example/UnioSample/GitHubSearchLogicStream.swift +++ b/Example/UnioSample/GitHubSearchLogicStream.swift @@ -11,8 +11,8 @@ import RxSwift import RxRelay protocol GitHubSearchLogicStreamType: AnyObject { - var input: Relay { get } - var output: Relay { get } + var input: InputWrapper { get } + var output: OutputWrapper { get } } final class GitHubSearchLogicStream: UnioStream, GitHubSearchLogicStreamType { @@ -69,7 +69,7 @@ extension GitHubSearchLogicStream.Logic { let searchResponse: Observable> #if swift(>=5.1) - searchResponse = searchAPIStream.output.observables.searchResponse + searchResponse = searchAPIStream.output.searchResponse #else searchResponse = searchAPIStream.output.observable(for: \.searchResponse) #endif @@ -79,7 +79,7 @@ extension GitHubSearchLogicStream.Logic { .disposed(by: disposeBag) let searchText: Observable - let searchRepository: AcceptableObserver + let searchRepository: AnyObserver #if swift(>=5.1) searchText = dependency.inputObservables.searchText searchRepository = searchAPIStream.input.searchRepository @@ -100,7 +100,7 @@ extension GitHubSearchLogicStream.Logic { #if swift(>=5.1) return Output(repositories: state.repositories, - error: searchAPIStream.output.observables.searchError) + error: searchAPIStream.output.searchError) #else return Output(repositories: state.repositories, error: searchAPIStream.output.observable(for: \.searchError)) diff --git a/Example/UnioSample/GitHubSearchViewController.swift b/Example/UnioSample/GitHubSearchViewController.swift index fbf5320..7c2f308 100644 --- a/Example/UnioSample/GitHubSearchViewController.swift +++ b/Example/UnioSample/GitHubSearchViewController.swift @@ -67,7 +67,7 @@ final class GitHubSearchViewController: UIViewController { let repositories: Observable<[GitHub.Repository]> #if swift(>=5.1) - repositories = output.observables.repositories + repositories = output.repositories #else repositories = output.observable(for: \.repositories) #endif @@ -80,7 +80,7 @@ final class GitHubSearchViewController: UIViewController { let errorMessage: Observable #if swift(>=5.1) - errorMessage = output.observables.errorMessage + errorMessage = output.errorMessage #else errorMessage = output.observable(for: \.errorMessage) #endif diff --git a/Example/UnioSample/GitHubSearchViewStream.swift b/Example/UnioSample/GitHubSearchViewStream.swift index 1377804..0e12891 100644 --- a/Example/UnioSample/GitHubSearchViewStream.swift +++ b/Example/UnioSample/GitHubSearchViewStream.swift @@ -11,8 +11,8 @@ import RxSwift import RxRelay protocol GitHubSearchViewStreamType: AnyObject { - var input: Relay { get } - var output: Relay { get } + var input: InputWrapper { get } + var output: OutputWrapper { get } } final class GitHubSearchViewStream: UnioStream, GitHubSearchViewStreamType { @@ -66,9 +66,9 @@ extension GitHubSearchViewStream.Logic { dependency.inputObservables.searchText .bind(to: logicStream.input.searchText) .disposed(by: disposeBag) - - return Output(repositories: logicStream.output.observables.repositories, - errorMessage: logicStream.output.observables.error.map { $0.localizedDescription }) + + return Output(repositories: logicStream.output.repositories, + errorMessage: logicStream.output.error.map { $0.localizedDescription }) #else dependency.inputObservable(for: \.searchText) .bind(to: logicStream.input.accept(for: \.searchText)) diff --git a/Example/UnioSampleTests/MockGitHubSearchAPIStream.swift b/Example/UnioSampleTests/MockGitHubSearchAPIStream.swift index 86b6ebd..8cc84ea 100644 --- a/Example/UnioSampleTests/MockGitHubSearchAPIStream.swift +++ b/Example/UnioSampleTests/MockGitHubSearchAPIStream.swift @@ -13,8 +13,8 @@ import Unio final class MockGitHubSearchAPIStream: GitHubSearchAPIStreamType { - let input: Relay - let output: Relay + let input: InputWrapper + let output: OutputWrapper let _input = GitHubSearchAPIStream.Input() @@ -22,9 +22,9 @@ final class MockGitHubSearchAPIStream: GitHubSearchAPIStreamType { let searchError = BehaviorRelay(value: nil) init() { - self.input = Relay(_input) + self.input = InputWrapper(_input) let _output = GitHubSearchAPIStream.Output(searchResponse: searchResponse.flatMap { $0.map(Observable.just) ?? .empty() }, searchError: searchError.flatMap { $0.map(Observable.just) ?? .empty() }) - self.output = Relay(_output) + self.output = OutputWrapper(_output) } } diff --git a/Unio.xcodeproj/project.pbxproj b/Unio.xcodeproj/project.pbxproj index 2ae0909..52dfa15 100644 --- a/Unio.xcodeproj/project.pbxproj +++ b/Unio.xcodeproj/project.pbxproj @@ -10,7 +10,7 @@ 9D2E26832240D18500C9EDF7 /* Unio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D2E26792240D18500C9EDF7 /* Unio.framework */; }; 9D2E268A2240D18500C9EDF7 /* Unio.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D2E267C2240D18500C9EDF7 /* Unio.h */; settings = {ATTRIBUTES = (Public, ); }; }; 9D2E269D2240D1CA00C9EDF7 /* PublishRelayType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2E26932240D1CA00C9EDF7 /* PublishRelayType.swift */; }; - 9D2E269E2240D1CA00C9EDF7 /* Relay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2E26942240D1CA00C9EDF7 /* Relay.swift */; }; + 9D2E269E2240D1CA00C9EDF7 /* Wrappers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2E26942240D1CA00C9EDF7 /* Wrappers.swift */; }; 9D2E269F2240D1CA00C9EDF7 /* LogicType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2E26952240D1CA00C9EDF7 /* LogicType.swift */; }; 9D2E26A02240D1CA00C9EDF7 /* StateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2E26962240D1CA00C9EDF7 /* StateType.swift */; }; 9D2E26A12240D1CA00C9EDF7 /* ExtraType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2E26972240D1CA00C9EDF7 /* ExtraType.swift */; }; @@ -26,18 +26,15 @@ 9D9EEC13228172F400DF5D97 /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D2E26A92240D1F200C9EDF7 /* RxCocoa.framework */; }; 9D9EEC14228172F900DF5D97 /* RxCocoa.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 9D2E26A92240D1F200C9EDF7 /* RxCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9DA555CB2241DE8400EC8CC3 /* BehaviorSubjectType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555CA2241DE8400EC8CC3 /* BehaviorSubjectType.swift */; }; - 9DA555D12241F95400EC8CC3 /* Deprecated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D02241F95400EC8CC3 /* Deprecated.swift */; }; 9DA555D32241FC0400EC8CC3 /* AcceptableRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D22241FC0400EC8CC3 /* AcceptableRelay.swift */; }; 9DA555D52241FC6600EC8CC3 /* ValueAccessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D42241FC6600EC8CC3 /* ValueAccessible.swift */; }; ED6897C122B184EE00B04DA0 /* PrimitiveProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */; }; - ED74A63D2243C2EE00D4E99C /* ReadOnlyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A63C2243C2EE00D4E99C /* ReadOnlyTests.swift */; }; + ED74A63D2243C2EE00D4E99C /* PrimitivePropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A63C2243C2EE00D4E99C /* PrimitivePropertyTests.swift */; }; ED74A6402243C3DB00D4E99C /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D2E26A82240D1F200C9EDF7 /* RxSwift.framework */; }; ED74A6432243C43400D4E99C /* RxSwift.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 9D2E26A82240D1F200C9EDF7 /* RxSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - ED74A7382243D99D00D4E99C /* RelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A7372243D99D00D4E99C /* RelayTests.swift */; }; + ED74A7382243D99D00D4E99C /* WrappersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A7372243D99D00D4E99C /* WrappersTests.swift */; }; ED74A73A2243E0CC00D4E99C /* DependencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A7392243E0CC00D4E99C /* DependencyTests.swift */; }; ED74A73C2243E43D00D4E99C /* UnioStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A73B2243E43D00D4E99C /* UnioStreamTests.swift */; }; - EDD118F62289CFD70055C0AE /* AcceptableObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD118F52289CFD70055C0AE /* AcceptableObserver.swift */; }; - EDD118F82289E1170055C0AE /* DynamicMemberLookupAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD118F72289E1170055C0AE /* DynamicMemberLookupAdapter.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -73,7 +70,7 @@ 9D2E26822240D18500C9EDF7 /* UnioTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UnioTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9D2E26892240D18500C9EDF7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 9D2E26932240D1CA00C9EDF7 /* PublishRelayType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublishRelayType.swift; sourceTree = ""; }; - 9D2E26942240D1CA00C9EDF7 /* Relay.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Relay.swift; sourceTree = ""; }; + 9D2E26942240D1CA00C9EDF7 /* Wrappers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wrappers.swift; sourceTree = ""; }; 9D2E26952240D1CA00C9EDF7 /* LogicType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogicType.swift; sourceTree = ""; }; 9D2E26962240D1CA00C9EDF7 /* StateType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateType.swift; sourceTree = ""; }; 9D2E26972240D1CA00C9EDF7 /* ExtraType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtraType.swift; sourceTree = ""; }; @@ -86,16 +83,13 @@ 9D2E26A92240D1F200C9EDF7 /* RxCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxCocoa.framework; path = Carthage/Build/iOS/RxCocoa.framework; sourceTree = ""; }; 9D9EEC0A228168F800DF5D97 /* RxRelay.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxRelay.framework; path = Carthage/Build/iOS/RxRelay.framework; sourceTree = ""; }; 9DA555CA2241DE8400EC8CC3 /* BehaviorSubjectType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BehaviorSubjectType.swift; sourceTree = ""; }; - 9DA555D02241F95400EC8CC3 /* Deprecated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Deprecated.swift; sourceTree = ""; }; 9DA555D22241FC0400EC8CC3 /* AcceptableRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptableRelay.swift; sourceTree = ""; }; 9DA555D42241FC6600EC8CC3 /* ValueAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueAccessible.swift; sourceTree = ""; }; ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveProperty.swift; sourceTree = ""; }; - ED74A63C2243C2EE00D4E99C /* ReadOnlyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadOnlyTests.swift; sourceTree = ""; }; - ED74A7372243D99D00D4E99C /* RelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayTests.swift; sourceTree = ""; }; + ED74A63C2243C2EE00D4E99C /* PrimitivePropertyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimitivePropertyTests.swift; sourceTree = ""; }; + ED74A7372243D99D00D4E99C /* WrappersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappersTests.swift; sourceTree = ""; }; ED74A7392243E0CC00D4E99C /* DependencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyTests.swift; sourceTree = ""; }; ED74A73B2243E43D00D4E99C /* UnioStreamTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnioStreamTests.swift; sourceTree = ""; }; - EDD118F52289CFD70055C0AE /* AcceptableObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptableObserver.swift; sourceTree = ""; }; - EDD118F72289E1170055C0AE /* DynamicMemberLookupAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicMemberLookupAdapter.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -145,20 +139,17 @@ isa = PBXGroup; children = ( 9DA555C92241DE5F00EC8CC3 /* RxTypes */, - EDD118F52289CFD70055C0AE /* AcceptableObserver.swift */, 9D2E269C2240D1CA00C9EDF7 /* Dependency.swift */, - 9DA555D02241F95400EC8CC3 /* Deprecated.swift */, - EDD118F72289E1170055C0AE /* DynamicMemberLookupAdapter.swift */, 9D2E26972240D1CA00C9EDF7 /* ExtraType.swift */, 9D2E267D2240D18500C9EDF7 /* Info.plist */, 9D2E26982240D1CA00C9EDF7 /* InputType.swift */, 9D2E26952240D1CA00C9EDF7 /* LogicType.swift */, 9D2E269B2240D1CA00C9EDF7 /* OutputType.swift */, ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */, - 9D2E26942240D1CA00C9EDF7 /* Relay.swift */, 9D2E26962240D1CA00C9EDF7 /* StateType.swift */, 9D2E267C2240D18500C9EDF7 /* Unio.h */, 9D2E269A2240D1CA00C9EDF7 /* UnioStream.swift */, + 9D2E26942240D1CA00C9EDF7 /* Wrappers.swift */, ); path = Unio; sourceTree = ""; @@ -198,9 +189,9 @@ isa = PBXGroup; children = ( ED74A7392243E0CC00D4E99C /* DependencyTests.swift */, - ED74A63C2243C2EE00D4E99C /* ReadOnlyTests.swift */, - ED74A7372243D99D00D4E99C /* RelayTests.swift */, + ED74A63C2243C2EE00D4E99C /* PrimitivePropertyTests.swift */, ED74A73B2243E43D00D4E99C /* UnioStreamTests.swift */, + ED74A7372243D99D00D4E99C /* WrappersTests.swift */, ); path = TestCases; sourceTree = ""; @@ -317,23 +308,20 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - EDD118F62289CFD70055C0AE /* AcceptableObserver.swift in Sources */, 9DA555D32241FC0400EC8CC3 /* AcceptableRelay.swift in Sources */, 9D2E26A32240D1CA00C9EDF7 /* BehaviorRelayType.swift in Sources */, 9DA555CB2241DE8400EC8CC3 /* BehaviorSubjectType.swift in Sources */, 9D2E26A62240D1CA00C9EDF7 /* Dependency.swift in Sources */, - 9DA555D12241F95400EC8CC3 /* Deprecated.swift in Sources */, - EDD118F82289E1170055C0AE /* DynamicMemberLookupAdapter.swift in Sources */, 9D2E26A12240D1CA00C9EDF7 /* ExtraType.swift in Sources */, 9D2E26A22240D1CA00C9EDF7 /* InputType.swift in Sources */, 9D2E269F2240D1CA00C9EDF7 /* LogicType.swift in Sources */, 9D2E26A52240D1CA00C9EDF7 /* OutputType.swift in Sources */, ED6897C122B184EE00B04DA0 /* PrimitiveProperty.swift in Sources */, 9D2E269D2240D1CA00C9EDF7 /* PublishRelayType.swift in Sources */, - 9D2E269E2240D1CA00C9EDF7 /* Relay.swift in Sources */, 9D2E26A02240D1CA00C9EDF7 /* StateType.swift in Sources */, 9D2E26A42240D1CA00C9EDF7 /* UnioStream.swift in Sources */, 9DA555D52241FC6600EC8CC3 /* ValueAccessible.swift in Sources */, + 9D2E269E2240D1CA00C9EDF7 /* Wrappers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -342,9 +330,9 @@ buildActionMask = 2147483647; files = ( ED74A73A2243E0CC00D4E99C /* DependencyTests.swift in Sources */, - ED74A63D2243C2EE00D4E99C /* ReadOnlyTests.swift in Sources */, - ED74A7382243D99D00D4E99C /* RelayTests.swift in Sources */, + ED74A63D2243C2EE00D4E99C /* PrimitivePropertyTests.swift in Sources */, ED74A73C2243E43D00D4E99C /* UnioStreamTests.swift in Sources */, + ED74A7382243D99D00D4E99C /* WrappersTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Unio/AcceptableObserver.swift b/Unio/AcceptableObserver.swift deleted file mode 100644 index eb493d2..0000000 --- a/Unio/AcceptableObserver.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// AcceptableObserver.swift -// Unio -// -// Created by marty-suzuki on 2019/05/14. -// Copyright © 2019 tv.abema. All rights reserved. -// - -import RxSwift - -/// A type-erased `AcceptableRelay`. -public struct AcceptableObserver: AcceptableRelay, ObserverType { - - private let _accept: (Element) -> Void - - /// Construct an instance whose `on(event)` calls `observer.on(event)` - /// - /// - parameter observer: Observer that receives sequence events. - init(_ relay: Relay) where Relay.Element == Element { - self._accept = { relay.accept($0) } - } - - /// Send `event` to this observer. - /// - /// - parameter event: Event instance. - public func on(_ event: Event) { - switch event { - case let .next(element): - _accept(element) - - case .error, .completed: - return - } - } - - public func accept(_ element: Element) { - _accept(element) - } -} diff --git a/Unio/Dependency.swift b/Unio/Dependency.swift index dbf248c..1f09504 100644 --- a/Unio/Dependency.swift +++ b/Unio/Dependency.swift @@ -17,12 +17,12 @@ public final class Dependency + public let inputObservables: ObservableWrapper internal init(input: Input, state: State, extra: Extra) { self.state = state self.extra = extra - self.inputObservables = DMLA.Observables(input) + self.inputObservables = ObservableWrapper(input) } /// Makes possible to get Observable from `Input`. @@ -30,22 +30,4 @@ public final class Dependency(from output: Relay, for keyPath: KeyPath) -> Property { - return properties(from: output)[dynamicMember: keyPath] - } - - /// Returns property that accessible to throwable value (e.g. BehaviorSubject). - /// - /// - note: Object is reference, not copied one. - public func property(from output: Relay, for keyPath: KeyPath) -> ThrowableProperty { - return properties(from: output)[dynamicMember: keyPath] - } - - public func properties(from output: Relay) -> DMLA.Properties { - return DMLA.Properties(output) - } } diff --git a/Unio/Deprecated.swift b/Unio/Deprecated.swift deleted file mode 100644 index 66a22f4..0000000 --- a/Unio/Deprecated.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// Deprecated.swift -// Unio -// -// Created by marty-suzuki on 2019/03/20. -// Copyright © 2019 tv.abema. All rights reserved. -// - -import Foundation -import RxRelay -import RxSwift - -/// Makes possible to access particular method of property that contained by ValueAccessibleObservable (or ThrowableValueAccessibleObservable) even while hides actual properties (BehaviorRelay, BehaviorSubject and so on). -/// -/// - note: When generic parameter is `ValueAccessibleObservable`, it makes possible to access Observale and value via `KeyPath`. -/// On the other hand, when generic parameter is `ThrowableValueAccessibleObservable`, it makes possible to access Observale and throwable via `KeyPath`. -@available(*, deprecated, renamed: "Property") -public final class ReadOnly: ObservableConvertibleType { - - private let _value: () -> T.Element - private let _throwableValue: () throws -> T.Element - private let _asObservable: () -> Observable - - private init(value: @escaping () -> T.Element, - throwableValue: @escaping () throws -> T.Element, - asObservable: @escaping () -> Observable) { - self._value = value - self._throwableValue = throwableValue - self._asObservable = asObservable - } - - /// Makes possible to get Observable - public func asObservable() -> Observable { - return _asObservable() - } -} - -@available(*, deprecated, renamed: "Property") -extension ReadOnly: ValueAccessible where T: ValueAccessibleObservable { - - /// Makes possible to get value - public var value: T.Element { - return _value() - } - - internal convenience init(_ output: Relay, for keyPath: KeyPath) { - let behaviorRelay = output._dependency[keyPath: keyPath] - self.init(value: { behaviorRelay.value }, - throwableValue: { fatalError("not reached here") }, - asObservable: { behaviorRelay.asObservable() }) - } -} - -@available(*, deprecated, renamed: "ThrowableProperty") -extension ReadOnly: ThrowableValueAccessible where T: ThrowableValueAccessibleObservable { - - /// Makes possible to get throwableValue - public func throwableValue() throws -> T.Element { - return try _throwableValue() - } - - internal convenience init(_ output: Relay, for keyPath: KeyPath) { - let behaviorSubject = output._dependency[keyPath: keyPath] - self.init(value: { fatalError("not reached here") }, - throwableValue: { try behaviorSubject.throwableValue() }, - asObservable: { behaviorSubject.asObservable() }) - } -} - -extension Dependency { - - /// Returns read-only value accessible object (e.g. BehaviorRelay). - /// - /// - note: Object is reference, not copied one. - @available(*, deprecated, renamed: "property(from:for:)") - public func readOnlyReference(from output: Relay, for keyPath: KeyPath) -> ReadOnly { - return ReadOnly(output, for: keyPath) - } - - /// Returns read-only value accessible object (e.g. BehaviorSubject). - /// - /// - note: Object is reference, not copied one. - @available(*, deprecated, renamed: "property(from:for:)") - public func readOnlyReference(from output: Relay, for keyPath: KeyPath) -> ReadOnly { - return ReadOnly(output, for: keyPath) - } -} diff --git a/Unio/DynamicMemberLookupAdapter.swift b/Unio/DynamicMemberLookupAdapter.swift deleted file mode 100644 index e2077fa..0000000 --- a/Unio/DynamicMemberLookupAdapter.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// DynamicMemberLookupAdapter.swift -// Unio -// -// Created by marty-suzuki on 2019/05/14. -// Copyright © 2019 tv.abema. All rights reserved. -// - -import Foundation -import RxSwift - -public typealias DMLA = DynamicMemberLookupAdapter - -public enum DynamicMemberLookupAdapter { - - @dynamicMemberLookup - public final class Observables { - - private let object: T - - init(_ object: T) { - self.object = object - } - - public subscript(dynamicMember keyPath: KeyPath) -> Observable { - return object[keyPath: keyPath].asObservable() - } - } - - @dynamicMemberLookup - public final class Properties { - - private let output: Relay - - init(_ output: Relay) { - self.output = output - } - - public subscript(dynamicMember keyPath: KeyPath) -> Property { - return Property(output, for: keyPath) - } - - public subscript(dynamicMember keyPath: KeyPath) -> ThrowableProperty { - return ThrowableProperty(output, for: keyPath) - } - } -} - -#if swift(<5.1) -extension DMLA.Observables { - - @available(*, unavailable) - subscript(dynamicMember member: String) -> Never { - fatalError("must not be accessible") - } -} - -extension DMLA.Properties { - - @available(*, unavailable) - subscript(dynamicMember member: String) -> Never { - fatalError("must not be accessible") - } -} -#endif diff --git a/Unio/PrimitiveProperty.swift b/Unio/PrimitiveProperty.swift index 2e69ad6..4ab020f 100644 --- a/Unio/PrimitiveProperty.swift +++ b/Unio/PrimitiveProperty.swift @@ -49,7 +49,7 @@ extension PrimitiveProperty: ValueAccessible where Failure == Never { return _value() } - internal convenience init(_ output: Relay, for keyPath: KeyPath) where T.Element == Element { + internal convenience init(_ output: OutputWrapper, for keyPath: KeyPath) where T.Element == Element { let behaviorRelay = output._dependency[keyPath: keyPath] self.init(value: { behaviorRelay.value }, throwableValue: { fatalError("not reached here") }, @@ -64,7 +64,7 @@ extension PrimitiveProperty: ThrowableValueAccessible where Failure == Error { return try _throwableValue() } - internal convenience init(_ output: Relay, for keyPath: KeyPath) where T.Element == Element { + internal convenience init(_ output: OutputWrapper, for keyPath: KeyPath) where T.Element == Element { let behaviorSubject = output._dependency[keyPath: keyPath] self.init(value: { fatalError("not reached here") }, throwableValue: { try behaviorSubject.throwableValue() }, diff --git a/Unio/UnioStream.swift b/Unio/UnioStream.swift index 882cd1f..6b15299 100644 --- a/Unio/UnioStream.swift +++ b/Unio/UnioStream.swift @@ -9,8 +9,8 @@ /// Makes possible to implement Unidirectional input / output stream. open class UnioStream { - public let input: Relay - public let output: Relay + public let input: InputWrapper + public let output: OutputWrapper private let _state: Logic.State private let _extra: Logic.Extra @@ -20,8 +20,8 @@ open class UnioStream { public init(input: Logic.Input, state: Logic.State, extra: Logic.Extra, logic: Logic) { let dependency = Dependency(input: input, state: state, extra: extra) let output = logic.bind(from: dependency) - self.input = Relay(input) - self.output = Relay(output) + self.input = InputWrapper(input) + self.output = OutputWrapper(output) self._state = state self._extra = extra self._logic = logic diff --git a/Unio/Relay.swift b/Unio/Wrappers.swift similarity index 60% rename from Unio/Relay.swift rename to Unio/Wrappers.swift index 8f3ca2a..a7d05ae 100644 --- a/Unio/Relay.swift +++ b/Unio/Wrappers.swift @@ -1,5 +1,5 @@ // -// Relay.swift +// Wrappers.swift // Unio // // Created by marty-suzuki on 2019/03/15. @@ -11,46 +11,24 @@ import RxRelay import RxSwift /// Makes possible to access particular method of property that contained by Input (or Output) even while hides actual properties (PublishRelay, PublishSubject and so on). -/// -/// - note: When generic parameter is `InputType`, it makes possible to access Observales via `KeyPath`. -/// On the other hand, when generic parameter is `OutType`, it makes possible to access Observers via `KeyPath`. @dynamicMemberLookup -public final class Relay { +public final class InputWrapper { internal let _dependency: T - private init(dependency: T) { - self._dependency = dependency - } -} - -#if swift(<5.1) -extension Relay { - - @available(*, unavailable) - subscript(dynamicMember member: String) -> Never { - fatalError("must not be accessible") - } -} -#endif - -// - MARK: Relay - -extension Relay where T: InputType { - /// Initializes with `Input`. - public convenience init(_ dependency: T) { - self.init(dependency: dependency) + public init(_ dependency: T) { + self._dependency = dependency } /// Accepts `event` and emits it to subscribers via `Input`. public func accept(_ value: U.Element, for keyPath: KeyPath) { - accept(for: keyPath).accept(value) + accept(for: keyPath).onNext(value) } /// Send `event` to this observer via `Input`. - public func accept(for keyPath: KeyPath) -> AcceptableObserver { + public func accept(for keyPath: KeyPath) -> AnyObserver { return self[dynamicMember: keyPath] } @@ -67,22 +45,27 @@ extension Relay where T: InputType { return self[dynamicMember: keyPath] } - public subscript(dynamicMember keyPath: KeyPath) -> AcceptableObserver { - return AcceptableObserver(_dependency[keyPath: keyPath]) + public subscript(dynamicMember keyPath: KeyPath) -> AnyObserver { + + let relay = _dependency[keyPath: keyPath] + return AnyObserver { $0.element.map(relay.accept) } } public subscript(dynamicMember keyPath: KeyPath) -> AnyObserver { + return _dependency[keyPath: keyPath].asObserver() } } -// - MARK: Relay +/// Makes possible to access particular method of property that contained by Output even while hides actual properties (BehaviorRelay, BehaviorSubject and so on). +@dynamicMemberLookup +public final class OutputWrapper { -extension Relay where T: OutputType { + internal let _dependency: T /// Initializes with `Output`. - public convenience init(_ dependency: T) { - self.init(dependency: dependency) + public init(_ dependency: T) { + self._dependency = dependency } /// Makes possible to get Observable from `Output`. @@ -103,6 +86,16 @@ extension Relay where T: OutputType { return try self[dynamicMember: keyPath].throwableValue() } + public func property(for keyPath: KeyPath) -> Property { + + return self[dynamicMember: keyPath] + } + + public func property(for keyPath: KeyPath) -> ThrowableProperty { + + return self[dynamicMember: keyPath] + } + public subscript(dynamicMember keyPath: KeyPath) -> Observable { return _dependency[keyPath: keyPath].asObservable() @@ -117,40 +110,54 @@ extension Relay where T: OutputType { return ThrowableProperty(self, for: keyPath) } -} -// - MARK: Relay + public subscript(dynamicMember keyPath: KeyPath>) -> Property { -extension Relay where T: ValueAccessibleObservable { + return _dependency[keyPath: keyPath] + } - public var value: T.Element { - return _dependency.value + public subscript(dynamicMember keyPath: KeyPath>) -> ThrowableProperty { + + return _dependency[keyPath: keyPath] } +} - internal convenience init(_ output: Relay, for keyPath: KeyPath) { - let behaviorRelay = output._dependency[keyPath: keyPath] - self.init(dependency: behaviorRelay) +@dynamicMemberLookup +public final class ObservableWrapper { + + private let object: T + + internal init(_ object: T) { + self.object = object } - public func asObservable() -> Observable { - return _dependency.asObservable() + public subscript(dynamicMember keyPath: KeyPath) -> Observable { + return object[keyPath: keyPath].asObservable() } } -// - MARK: Relay - -extension Relay where T: ThrowableValueAccessibleObservable { +#if swift(<5.1) +extension InputWrapper { - internal convenience init(_ output: Relay, for keyPath: KeyPath) { - let behaviorSubject = output._dependency[keyPath: keyPath] - self.init(dependency: behaviorSubject) + @available(*, unavailable) + subscript(dynamicMember member: String) -> Never { + fatalError("must not be accessible") } +} + +extension OutputWrapper { - public func throwableValue() throws -> T.Element { - return try _dependency.throwableValue() + @available(*, unavailable) + subscript(dynamicMember member: String) -> Never { + fatalError("must not be accessible") } +} + +extension ObservableWrapper { - public func asObservable() -> Observable { - return _dependency.asObservable() + @available(*, unavailable) + subscript(dynamicMember member: String) -> Never { + fatalError("must not be accessible") } } +#endif diff --git a/UnioTests/TestCases/DependencyTests.swift b/UnioTests/TestCases/DependencyTests.swift index 2a0b987..9248971 100644 --- a/UnioTests/TestCases/DependencyTests.swift +++ b/UnioTests/TestCases/DependencyTests.swift @@ -61,39 +61,6 @@ final class DependencyTests: XCTestCase { disposable.dispose() } - - func testProperty_ValueAccessible() { - - let expected = "test-ValueAccessible" - let testTarget = dependency.testTarget - - #if swift(>=5.1) - let property = testTarget.properties(from: dependency.output).relay - #else - let property = testTarget.property(from: dependency.output, for: \.relay) - #endif - - dependency.outputRelay.accept(expected) - - XCTAssertEqual(property.value, expected) - } - - func testProperty_ThrowableValueAccessible() { - - let expected = "test-ThrowableValueAccessible" - let testTarget = dependency.testTarget - - #if swift(>=5.1) - let property = testTarget.properties(from: dependency.output).subject - #else - let property = testTarget.property(from: dependency.output, for: \.subject) - #endif - - dependency.outputSubject.onNext(expected) - - XCTAssertEqual(try property.throwableValue(), expected) - } - } extension DependencyTests { @@ -112,7 +79,7 @@ extension DependencyTests { let testTarget: Unio.Dependency - let output: Relay + let output: OutputWrapper let inputSubject = PublishSubject() let inputRelay = PublishRelay() @@ -121,7 +88,7 @@ extension DependencyTests { init() { let input = Input(relay: inputRelay, subject: inputSubject) - self.output = Relay(Output(subject: outputSubject, relay: outputRelay)) + self.output = OutputWrapper(Output(subject: outputSubject, relay: outputRelay)) self.testTarget = Unio.Dependency(input: input, state: .init(), extra: .init()) } } diff --git a/UnioTests/TestCases/ReadOnlyTests.swift b/UnioTests/TestCases/PrimitivePropertyTests.swift similarity index 90% rename from UnioTests/TestCases/ReadOnlyTests.swift rename to UnioTests/TestCases/PrimitivePropertyTests.swift index 36ddf9a..330827d 100644 --- a/UnioTests/TestCases/ReadOnlyTests.swift +++ b/UnioTests/TestCases/PrimitivePropertyTests.swift @@ -1,5 +1,5 @@ // -// ReadOnlyTests.swift +// PrimitivePropertyTests.swift // UnioTests // // Created by marty-suzuki on 2019/03/21. @@ -11,7 +11,7 @@ import RxSwift import XCTest @testable import Unio -final class ReadOnlyTests: XCTestCase { +final class PrimitivePropertyTests: XCTestCase { private var dependency: Dependency! @@ -53,7 +53,7 @@ final class ReadOnlyTests: XCTestCase { } } -extension ReadOnlyTests { +extension PrimitivePropertyTests { private struct Output: OutputType { @@ -70,7 +70,7 @@ extension ReadOnlyTests { let relay = BehaviorRelay(value: nil) init() { - let output = Relay(Output(subject: subject, relay: relay)) + let output = OutputWrapper(Output(subject: subject, relay: relay)) self.testTargetSubject = ThrowableProperty(output, for: \.subject) self.testTargetRelay = Property(output, for: \.relay) } diff --git a/UnioTests/TestCases/UnioStreamTests.swift b/UnioTests/TestCases/UnioStreamTests.swift index 15836e2..c56c74f 100644 --- a/UnioTests/TestCases/UnioStreamTests.swift +++ b/UnioTests/TestCases/UnioStreamTests.swift @@ -30,7 +30,7 @@ final class UnioStreamTests: XCTestCase { .bind(to: stack) #if swift(>=5.1) - testTarget.input.relay.accept(expected) + testTarget.input.relay.onNext(expected) #else testTarget.input.accept(expected, for: \.relay) #endif diff --git a/UnioTests/TestCases/RelayTests.swift b/UnioTests/TestCases/WrappersTests.swift similarity index 74% rename from UnioTests/TestCases/RelayTests.swift rename to UnioTests/TestCases/WrappersTests.swift index abb6027..96486f3 100644 --- a/UnioTests/TestCases/RelayTests.swift +++ b/UnioTests/TestCases/WrappersTests.swift @@ -1,5 +1,5 @@ // -// RelayTests.swift +// WrappersTests.swift // UnioTests // // Created by marty-suzuki on 2019/03/21. @@ -12,7 +12,7 @@ import RxSwift import XCTest @testable import Unio -final class RelayTests: XCTestCase { +final class WrappersTests: XCTestCase { private var dependency: Dependency! @@ -74,7 +74,7 @@ final class RelayTests: XCTestCase { .bind(to: stack) #if swift(>=5.1) - testTarget.relay.accept(expected) + testTarget.relay.onNext(expected) #else testTarget.accept(expected, for: \.relay) #endif @@ -114,107 +114,97 @@ final class RelayTests: XCTestCase { let stack = BehaviorRelay(value: nil) #if swift(>=5.1) - let disposable = testTarget.relay + let disposable = testTarget._observable .bind(to: stack) #else - let disposable = testTarget.observable(for: \.relay) + let disposable = testTarget.observable(for: \._observable) .bind(to: stack) #endif - dependency.outputRelay.accept(expected) + dependency.acceptForObservable(expected) XCTAssertEqual(stack.value, expected) disposable.dispose() } - func testOutput_value() { - - let expected = "test-value" - let testTarget = dependency.testTargetOutput - - dependency.outputRelay.accept(expected) - - #if swift(>=5.1) - XCTAssertEqual(testTarget.relay.value, expected) - #else - XCTAssertEqual(testTarget.value(for: \.relay), expected) - #endif - } - - func testOutput_throwable_value() { + func testOutput_observable_as_behavior_relay() { - let expected = "test-value" + let expected = "test-observable-as-behavior-relay" let testTarget = dependency.testTargetOutput - - dependency.outputSubject.onNext(expected) - + let stack = BehaviorRelay(value: nil) #if swift(>=5.1) - XCTAssertEqual(try testTarget.subject.throwableValue(), expected) + let disposable = testTarget.relay + .bind(to: stack) #else - XCTAssertEqual(try testTarget.value(for: \.subject), expected) + let disposable = testTarget.observable(for: \.relay) + .bind(to: stack) #endif - } - - func testValueAccessibleObservable_value() { - - let expected = "test-value" - let testTarget = dependency.testTargetRelay dependency.outputRelay.accept(expected) - XCTAssertEqual(testTarget.value, expected) + XCTAssertEqual(stack.value, expected) + + disposable.dispose() } - func testValueAccessibleObservable_observable() { + func testOutput_observable_as_behavior_subject() { - let expected = "test-observable" - let testTarget = dependency.testTargetRelay + let expected = "test-observable-as-behavior-subject" + let testTarget = dependency.testTargetOutput let stack = BehaviorRelay(value: nil) - let disposable = testTarget.asObservable() + #if swift(>=5.1) + let disposable = testTarget.subject + .bind(to: stack) + #else + let disposable = testTarget.observable(for: \.subject) .bind(to: stack) + #endif - dependency.outputRelay.accept(expected) + dependency.outputSubject.onNext(expected) XCTAssertEqual(stack.value, expected) disposable.dispose() } - func testThrowableValueAccessibleObservable_value() { + func testOutput_value() { let expected = "test-value" - let testTarget = dependency.testTargetSubject + let testTarget = dependency.testTargetOutput - dependency.outputSubject.onNext(expected) + dependency.outputRelay.accept(expected) - XCTAssertEqual(try testTarget.throwableValue(), expected) + #if swift(>=5.1) + XCTAssertEqual(testTarget.relay.value, expected) + #else + XCTAssertEqual(testTarget.value(for: \.relay), expected) + #endif } - func testThrowableValueAccessibleObservable_observable() { - - let expected = "test-observable" - let testTarget = dependency.testTargetSubject - let stack = BehaviorRelay(value: nil) + func testOutput_throwable_value() { - let disposable = testTarget.asObservable() - .bind(to: stack) + let expected = "test-value" + let testTarget = dependency.testTargetOutput dependency.outputSubject.onNext(expected) - XCTAssertEqual(stack.value, expected) - - disposable.dispose() + #if swift(>=5.1) + XCTAssertEqual(try testTarget.subject.throwableValue(), expected) + #else + XCTAssertEqual(try testTarget.value(for: \.subject), expected) + #endif } } -extension RelayTests { +extension WrappersTests { private struct Output: OutputType { let subject: BehaviorSubject let relay: BehaviorRelay + let _observable: Observable } private struct Input: InputType { @@ -224,11 +214,8 @@ extension RelayTests { private struct Dependency { - let testTargetInput: Relay - let testTargetOutput: Relay - - let testTargetSubject: Relay> - let testTargetRelay: Relay> + let testTargetInput: InputWrapper + let testTargetOutput: OutputWrapper let inputSubject = PublishSubject() let inputRelay = PublishRelay() @@ -236,15 +223,19 @@ extension RelayTests { let outputSubject = BehaviorSubject(value: nil) let outputRelay = BehaviorRelay(value: nil) + let acceptForObservable: (String) -> Void + init() { - let input = Input(relay: inputRelay, subject: inputSubject) - self.testTargetInput = Relay(input) + let relayForObservable = PublishRelay() + self.acceptForObservable = { relayForObservable.accept($0) } - let output = Output(subject: outputSubject, relay: outputRelay) - self.testTargetOutput = Relay(output) + let input = Input(relay: inputRelay, subject: inputSubject) + self.testTargetInput = InputWrapper(input) - self.testTargetSubject = Relay(testTargetOutput, for: \.subject) - self.testTargetRelay = Relay(testTargetOutput, for: \.relay) + let output = Output(subject: outputSubject, + relay: outputRelay, + _observable: relayForObservable.asObservable()) + self.testTargetOutput = OutputWrapper(output) } } } From 7bc528fa35efb18e63777e82799dfe90d745917b Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 13 Sep 2019 02:22:33 +0900 Subject: [PATCH 10/17] fix xcode template --- .../Mock___FILEBASENAME___Stream.swift | 8 ++++---- .../Default/___FILEBASENAME___ViewStream.swift | 5 ++--- .../UnioStream.xctemplate/___FILEBASENAME___Stream.swift | 5 ++--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Tools/Mock UnioStream.xctemplate/Mock___FILEBASENAME___Stream.swift b/Tools/Mock UnioStream.xctemplate/Mock___FILEBASENAME___Stream.swift index ed0619b..3dac9e3 100644 --- a/Tools/Mock UnioStream.xctemplate/Mock___FILEBASENAME___Stream.swift +++ b/Tools/Mock UnioStream.xctemplate/Mock___FILEBASENAME___Stream.swift @@ -6,8 +6,8 @@ import Unio final class Mock___VARIABLE_productName___Stream: ___VARIABLE_productName___StreamType { - let input: Relay<___VARIABLE_productName___Stream.Input> - let output: Relay<___VARIABLE_productName___Stream.Output> + let input: InputWrapper<___VARIABLE_productName___Stream.Input> + let output: OutputWrapper<___VARIABLE_productName___Stream.Output> let _input = ___VARIABLE_productName___Stream.Input() @@ -16,8 +16,8 @@ final class Mock___VARIABLE_productName___Stream: ___VARIABLE_productName___Stre */ init() { - self.input = Relay(_input) + self.input = InputWrapper(_input) let _output = ___VARIABLE_productName___Stream.Output(/* inject output dependencies */) - self.output = Relay(_output) + self.output = OutputWrapper(_output) } } diff --git a/Tools/Unio Components.xctemplate/Default/___FILEBASENAME___ViewStream.swift b/Tools/Unio Components.xctemplate/Default/___FILEBASENAME___ViewStream.swift index 607f5e1..d17a679 100644 --- a/Tools/Unio Components.xctemplate/Default/___FILEBASENAME___ViewStream.swift +++ b/Tools/Unio Components.xctemplate/Default/___FILEBASENAME___ViewStream.swift @@ -5,8 +5,8 @@ import RxSwift import Unio protocol ___VARIABLE_productName___ViewStreamType: AnyObject { - var input: Relay<___VARIABLE_productName___ViewStream.Input> { get } - var output: Relay<___VARIABLE_productName___ViewStream.Output> { get } + var input: InputWrapper<___VARIABLE_productName___ViewStream.Input> { get } + var output: OutputWrapper<___VARIABLE_productName___ViewStream.Output> { get } } final class ___VARIABLE_productName___ViewStream: UnioStream<___VARIABLE_productName___ViewStream.Logic>, ___VARIABLE_productName___ViewStreamType { @@ -48,7 +48,6 @@ extension ___VARIABLE_productName___ViewStream { */ } - struct Extra: ExtraType { } diff --git a/Tools/UnioStream.xctemplate/___FILEBASENAME___Stream.swift b/Tools/UnioStream.xctemplate/___FILEBASENAME___Stream.swift index 31052ea..fff3fb3 100644 --- a/Tools/UnioStream.xctemplate/___FILEBASENAME___Stream.swift +++ b/Tools/UnioStream.xctemplate/___FILEBASENAME___Stream.swift @@ -5,8 +5,8 @@ import RxSwift import Unio protocol ___VARIABLE_productName___StreamType: AnyObject { - var input: Relay<___VARIABLE_productName___Stream.Input> { get } - var output: Relay<___VARIABLE_productName___Stream.Output> { get } + var input: InputWrapper<___VARIABLE_productName___Stream.Input> { get } + var output: OutputWrapper<___VARIABLE_productName___Stream.Output> { get } } final class ___VARIABLE_productName___Stream: UnioStream<___VARIABLE_productName___Stream.Logic>, ___VARIABLE_productName___StreamType { @@ -48,7 +48,6 @@ extension ___VARIABLE_productName___Stream { */ } - struct Extra: ExtraType { } From 3c89b784f9f2e0c0aa8c624037cd255141fe855c Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 13 Sep 2019 19:50:05 +0900 Subject: [PATCH 11/17] add comments --- Unio/Dependency.swift | 3 +++ Unio/Wrappers.swift | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Unio/Dependency.swift b/Unio/Dependency.swift index 1f09504..c5bf54a 100644 --- a/Unio/Dependency.swift +++ b/Unio/Dependency.swift @@ -17,6 +17,9 @@ public final class Dependency internal init(input: Input, state: State, extra: Extra) { diff --git a/Unio/Wrappers.swift b/Unio/Wrappers.swift index a7d05ae..2c969d5 100644 --- a/Unio/Wrappers.swift +++ b/Unio/Wrappers.swift @@ -45,12 +45,18 @@ public final class InputWrapper { return self[dynamicMember: keyPath] } + /// Send `event` to this observer via `Input`. + /// + /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 public subscript(dynamicMember keyPath: KeyPath) -> AnyObserver { let relay = _dependency[keyPath: keyPath] return AnyObserver { $0.element.map(relay.accept) } } + /// Send `event` to this observer via `Input`. + /// + /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 public subscript(dynamicMember keyPath: KeyPath) -> AnyObserver { return _dependency[keyPath: keyPath].asObserver() @@ -86,42 +92,60 @@ public final class OutputWrapper { return try self[dynamicMember: keyPath].throwableValue() } + /// Makes possible to get `Property` from Output when generic parameter is `BehaviorRelay`. public func property(for keyPath: KeyPath) -> Property { return self[dynamicMember: keyPath] } + /// Makes possible to get `ThrowableProperty` from Output when generic parameter is `BehaviorSubject`. public func property(for keyPath: KeyPath) -> ThrowableProperty { return self[dynamicMember: keyPath] } + /// Makes possible to get Observable from `Output`. + /// + /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 public subscript(dynamicMember keyPath: KeyPath) -> Observable { return _dependency[keyPath: keyPath].asObservable() } + /// Makes possible to get `Property` from Output when generic parameter is `BehaviorRelay`. + /// + /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 public subscript(dynamicMember keyPath: KeyPath) -> Property { return Property(self, for: keyPath) } + /// Makes possible to get `ThrowableProperty` from Output when generic parameter is `BehaviorSubject`. + /// + /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 public subscript(dynamicMember keyPath: KeyPath) -> ThrowableProperty { return ThrowableProperty(self, for: keyPath) } + /// Makes possible to get `Property` from Output. + /// + /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 public subscript(dynamicMember keyPath: KeyPath>) -> Property { return _dependency[keyPath: keyPath] } + /// Makes possible to get `ThrowableProperty` from Output. + /// + /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 public subscript(dynamicMember keyPath: KeyPath>) -> ThrowableProperty { return _dependency[keyPath: keyPath] } } +/// Makes possible to access Observable that contained by T even while hides actual properties (BehaviorRelay, BehaviorSubject and so on). @dynamicMemberLookup public final class ObservableWrapper { @@ -131,6 +155,9 @@ public final class ObservableWrapper { self.object = object } + /// Makes possible to get Observable from `T`. + /// + /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 public subscript(dynamicMember keyPath: KeyPath) -> Observable { return object[keyPath: keyPath].asObservable() } From 459baec23fd32ad439036124df9aba6f5c87fe00 Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 13 Sep 2019 20:15:37 +0900 Subject: [PATCH 12/17] be able to access accept method as closure --- Unio/Wrappers.swift | 10 +++++++++- UnioTests/TestCases/WrappersTests.swift | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Unio/Wrappers.swift b/Unio/Wrappers.swift index 2c969d5..fe9be20 100644 --- a/Unio/Wrappers.swift +++ b/Unio/Wrappers.swift @@ -24,7 +24,7 @@ public final class InputWrapper { /// Accepts `event` and emits it to subscribers via `Input`. public func accept(_ value: U.Element, for keyPath: KeyPath) { - accept(for: keyPath).onNext(value) + self[dynamicMember: keyPath](value) } /// Send `event` to this observer via `Input`. @@ -45,6 +45,14 @@ public final class InputWrapper { return self[dynamicMember: keyPath] } + /// Accepts `event` and emits it to subscribers via `Input`. + /// + /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 + public subscript(dynamicMember keyPath: KeyPath) -> (U.Element) -> Void { + + return _dependency[keyPath: keyPath].accept + } + /// Send `event` to this observer via `Input`. /// /// - note: KeyPath Dynamic Member Lookup is avairable greater than Swift5.1 diff --git a/UnioTests/TestCases/WrappersTests.swift b/UnioTests/TestCases/WrappersTests.swift index 96486f3..a7d6cda 100644 --- a/UnioTests/TestCases/WrappersTests.swift +++ b/UnioTests/TestCases/WrappersTests.swift @@ -74,7 +74,7 @@ final class WrappersTests: XCTestCase { .bind(to: stack) #if swift(>=5.1) - testTarget.relay.onNext(expected) + testTarget.relay(expected) #else testTarget.accept(expected, for: \.relay) #endif From 87965511901f1d1e0482dceb7af7745b0804b8df Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 13 Sep 2019 20:37:33 +0900 Subject: [PATCH 13/17] update README --- README.md | 63 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 3235cd2..a036762 100644 --- a/README.md +++ b/README.md @@ -86,16 +86,16 @@ struct Input: InputType { ``` Properties of Input are defined internal scope. -But these can only access `func accept(_:)` (or `func on(_:)`) via KeyPath if Input is wrapped with `Relay`. +But these can only access `func accept(_:)` (or `AnyObserver`) via KeyPath if Input is wrapped with `InputWrapper`. ```swift -let input: Relay +let input: InputWrapper -input.accept("query", for: \.searchText) -input.onEvent(.next(()), for: \.buttonTap) +input.searchText("query") // accesses `func accept(_:)` +input.buttonTap.onNext(()) // accesses `AnyObserver` ``` -![](https://user-images.githubusercontent.com/2082134/54809413-8fb8cf80-4cc6-11e9-9dc2-07cd14f5c2d8.jpg) +![](https://user-images.githubusercontent.com/2082134/64858916-afbcc080-d663-11e9-8a70-92a9293f7c83.png) ### Output @@ -110,30 +110,32 @@ struct Output: OutputType { ``` Properties of Output are defined internal scope. -But these can only access `func asObservable()` via KeyPath if Output is wrapped with `Relay`. +But these can only access `func asObservable()` via KeyPath if Output is wrapped with `OutputWrapper`. ```swift -let output: Relay +let output: OutputWrapper -output.observable(for: \.repositories) +output.repositories .subscribe(onNext: { print($0) }) -output.observable(for: \.isEnabled) +output.isEnabled .subscribe(onNext: { print($0) }) -output.observable(for: \.error) +output.error .subscribe(onNext: { print($0) }) ``` If a property is BehaviorRelay (or BehaviorSubject), be able to access value via KeyPath. ```swift -output.value(for: \.repositories) +let p: Property<[GitHub.Repository]> = output.repositories +p.value -try? output.value(for: \.isEnabled) +let t: ThrowableProperty = output.isEnabled +try? t.throwableValue() ``` -![](https://user-images.githubusercontent.com/2082134/54809443-a2cb9f80-4cc6-11e9-8d10-dfe2403f798b.jpg) +![](https://user-images.githubusercontent.com/2082134/64858314-f7dae380-d661-11e9-9a79-3ca5c53fd90a.png) ### State @@ -179,8 +181,7 @@ Connect sequences and generate [Output](#output) in `func bind(from:)` to use be - `dependency.state` - `dependency.extra` -- `dependency.inputObservable(for:)` ... returns a Observable that is property of [Input](#input). -- `dependency.readOnlyReference(from:for:)` ... returns a read only BehaviorRelay (or BehaviorSubject) (that is wrapped by `ReadOnly`) from property of [Output](#output). +- `dependency.inputObservables` ... returns a Observable that is property of [Input](#input). Here is a exmaple of implementation. @@ -190,12 +191,11 @@ extension GitHubSearchViewStream.Logic { func bind(from dependency: Dependency) -> Output { let apiStream = dependency.extra.apiStream - dependency.inputObservable(for: \.searchText) - .bind(to: apiStream.input.accept(for: \.searchText)) + dependency.inputObservables.searchText + .bind(to: apiStream.searchText) .disposed(by: disposeBag) - let repositories = apiStream.output - .observable(for: \.searchResponse) + let repositories = apiStream.output.searchResponse .map { $0.items } return Output(repositories: repositories) @@ -206,14 +206,14 @@ extension GitHubSearchViewStream.Logic { ### UnioStream UnioStream represents ViewModels of MVVM (it can also be used as Models). -It has `input: Relay` and `output: Relay`. -It automatically generates `input: Relay` and `output: Relay` from instances of [Input](#input), [State](#state), [Extra](#extra) and [Logic](#logic). +It has `input: InputWrapper` and `output: OutputWrapper`. +It automatically generates `input: InputWrapper` and `output: OutputWrapper` from instances of [Input](#input), [State](#state), [Extra](#extra) and [Logic](#logic). ```swift class UnioStream { - let input: Relay - let output: Relay + let input: InputWrapper + let output: OutputWrapper init(input: Logic.Input, state: Logic.State, extra: Logic.Extra, logic: Logic) } @@ -240,8 +240,8 @@ Define GitHubSearchViewStream for searching GitHub repositories. ```swift protocol GitHubSearchViewStreamType: AnyObject { - var input: Relay { get } - var output: Relay { get } + var input: InputWrapper { get } + var output: OutputWrapper { get } } final class GitHubSearchViewStream: UnioStream, GitHubSearchViewStreamType { @@ -279,12 +279,11 @@ extension GitHubSearchViewStream.Logic { func bind(from dependency: Dependency) -> Output { let apiStream = dependency.extra.apiStream - dependency.inputObservable(for: \.searchText) - .bind(to: apiStream.input.accept(for: \.searchText)) + dependency.inputObservables.searchText + .bind(to: apiStream.input.searchText) .disposed(by: disposeBag) - let repositories = apiStream.output - .observable(for: \.searchResponse) + let repositories = apiStream.output.searchResponse .map { $0.items } return Output(repositories: repositories) @@ -307,10 +306,10 @@ final class GitHubSearchViewController: UIViewController { super.viewDidLoad() searchBar.rx.text - .bind(to: viewStream.input.accept(for: \.searchText)) + .bind(to: viewStream.input.searchText) .disposed(by: disposeBag) - viewStream.output.observable(for: \.repositories) + viewStream.outpu.repositories .bind(to: tableView.rx.items(cellIdentifier: "Cell")) { (row, repository, cell) in cell.textLabel?.text = repository.fullName @@ -321,6 +320,8 @@ final class GitHubSearchViewController: UIViewController { } ``` +The document which does not use `KeyPath Dynamic Member Lookup` is [here](https://github.com/cats-oss/Unio/tree/0.4.1#about-unio). + ### Xcode Template You can use Xcode Templates for Unio. Let's install with `./Tools/install-xcode-template.sh` command! From f1181a62ee66fe6bcf6101528df9c9f2f5cc183b Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 13 Sep 2019 21:39:34 +0900 Subject: [PATCH 14/17] add Migration Guide --- Documentation/Unio0_5_0MigrationGuide.md | 14 ++++++++++++++ README.md | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 Documentation/Unio0_5_0MigrationGuide.md diff --git a/Documentation/Unio0_5_0MigrationGuide.md b/Documentation/Unio0_5_0MigrationGuide.md new file mode 100644 index 0000000..a99dd9b --- /dev/null +++ b/Documentation/Unio0_5_0MigrationGuide.md @@ -0,0 +1,14 @@ +# Unio 0.5.0 Migration Guide + +Unio 0.5.0 introduces some breaking changes. + +## Classes + +- [RENAME] `Relay` -> `InputWrapper` +- [RENAME] `Relay` -> `OutputWrapper` +- [RENAME] `ReadOnly` -> `PrimitiveProperty` + +## Methods + +- [DELETE] `Dependency.readOnlyReference(from:for:)` + - Use `OutputWrapper.property(for:)` instead. diff --git a/README.md b/README.md index a036762..f59737b 100644 --- a/README.md +++ b/README.md @@ -320,7 +320,11 @@ final class GitHubSearchViewController: UIViewController { } ``` -The document which does not use `KeyPath Dynamic Member Lookup` is [here](https://github.com/cats-oss/Unio/tree/0.4.1#about-unio). +The documentation which does not use `KeyPath Dynamic Member Lookup` is [here](https://github.com/cats-oss/Unio/tree/0.4.1#about-unio). + +#### Migration Guides + +- [Unio 0.5.0 Migration Guide](./Documentation/Unio0_5_0MigrationGuide.md) ### Xcode Template From aa672eaa50203e4d4d099a419e392b570e95f3e5 Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 13 Sep 2019 21:45:09 +0900 Subject: [PATCH 15/17] fix import --- Unio/PrimitiveProperty.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Unio/PrimitiveProperty.swift b/Unio/PrimitiveProperty.swift index 4ab020f..8f763b2 100644 --- a/Unio/PrimitiveProperty.swift +++ b/Unio/PrimitiveProperty.swift @@ -7,7 +7,7 @@ // import Foundation -import RxCocoa +import RxRelay import RxSwift /// Represents a property that makes possible to access value From b80a6fa4408fe7b42d1a203c673a38b83562027a Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Fri, 13 Sep 2019 21:47:34 +0900 Subject: [PATCH 16/17] update Unio.podspec --- Unio.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Unio.podspec b/Unio.podspec index 3300614..4209baf 100644 --- a/Unio.podspec +++ b/Unio.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = "Unio" - s.version = "0.4.1" + s.version = "0.5.0" s.summary = "KeyPath based Unidirectionarl Input / Output framework with RxSwift." s.homepage = "https://github.com/cats-oss/Unio" s.license = { :type => "MIT", :file => "LICENSE" } @@ -17,6 +17,6 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/cats-oss/Unio.git", :tag => "#{s.version}" } s.source_files = "Unio/**/*.{swift}" s.dependency 'RxSwift', '~> 5.0' - s.dependency 'RxCocoa', '~> 5.0' + s.dependency 'RxRelay', '~> 5.0' s.swift_version = '5.0' end From 650c5025b78f877e57143c0ea4262a068e56bc53 Mon Sep 17 00:00:00 2001 From: marty-suzuki Date: Tue, 17 Sep 2019 15:20:30 +0900 Subject: [PATCH 17/17] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f59737b..e9bbb6d 100644 --- a/README.md +++ b/README.md @@ -309,7 +309,7 @@ final class GitHubSearchViewController: UIViewController { .bind(to: viewStream.input.searchText) .disposed(by: disposeBag) - viewStream.outpu.repositories + viewStream.output.repositories .bind(to: tableView.rx.items(cellIdentifier: "Cell")) { (row, repository, cell) in cell.textLabel?.text = repository.fullName