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 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/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..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 { @@ -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..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 { @@ -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.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: AnyObserver + #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.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..7c2f308 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.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.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..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 { @@ -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.repositories, + errorMessage: logicStream.output.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/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/README.md b/README.md index 3235cd2..e9bbb6d 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.output.repositories .bind(to: tableView.rx.items(cellIdentifier: "Cell")) { (row, repository, cell) in cell.textLabel?.text = repository.fullName @@ -321,6 +320,12 @@ final class GitHubSearchViewController: UIViewController { } ``` +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 You can use Xcode Templates for Unio. Let's install with `./Tools/install-xcode-template.sh` command! 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 { } 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 diff --git a/Unio.xcodeproj/project.pbxproj b/Unio.xcodeproj/project.pbxproj index b5574b9..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,13 +26,13 @@ 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 */; }; 9DA555D32241FC0400EC8CC3 /* AcceptableRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D22241FC0400EC8CC3 /* AcceptableRelay.swift */; }; 9DA555D52241FC6600EC8CC3 /* ValueAccessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA555D42241FC6600EC8CC3 /* ValueAccessible.swift */; }; - ED74A63D2243C2EE00D4E99C /* ReadOnlyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED74A63C2243C2EE00D4E99C /* ReadOnlyTests.swift */; }; + ED6897C122B184EE00B04DA0 /* PrimitiveProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6897C022B184EE00B04DA0 /* PrimitiveProperty.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 */; }; /* End PBXBuildFile section */ @@ -70,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 = ""; }; @@ -83,11 +83,11 @@ 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 = ""; }; 9DA555D22241FC0400EC8CC3 /* AcceptableRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceptableRelay.swift; sourceTree = ""; }; 9DA555D42241FC6600EC8CC3 /* ValueAccessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueAccessible.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 = ""; }; + ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveProperty.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 = ""; }; /* End PBXFileReference section */ @@ -145,11 +145,11 @@ 9D2E26982240D1CA00C9EDF7 /* InputType.swift */, 9D2E26952240D1CA00C9EDF7 /* LogicType.swift */, 9D2E269B2240D1CA00C9EDF7 /* OutputType.swift */, - 9DA555D02241F95400EC8CC3 /* ReadOnly.swift */, - 9D2E26942240D1CA00C9EDF7 /* Relay.swift */, + ED6897C022B184EE00B04DA0 /* PrimitiveProperty.swift */, 9D2E26962240D1CA00C9EDF7 /* StateType.swift */, 9D2E267C2240D18500C9EDF7 /* Unio.h */, 9D2E269A2240D1CA00C9EDF7 /* UnioStream.swift */, + 9D2E26942240D1CA00C9EDF7 /* Wrappers.swift */, ); path = Unio; sourceTree = ""; @@ -166,8 +166,8 @@ 9D2E26A72240D1F200C9EDF7 /* Frameworks */ = { isa = PBXGroup; children = ( - 9D9EEC0A228168F800DF5D97 /* RxRelay.framework */, 9D2E26A92240D1F200C9EDF7 /* RxCocoa.framework */, + 9D9EEC0A228168F800DF5D97 /* RxRelay.framework */, 9D2E26A82240D1F200C9EDF7 /* RxSwift.framework */, ); name = Frameworks; @@ -189,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 = ""; @@ -316,12 +316,12 @@ 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 */, 9D2E26A02240D1CA00C9EDF7 /* StateType.swift in Sources */, 9D2E26A42240D1CA00C9EDF7 /* UnioStream.swift in Sources */, 9DA555D52241FC6600EC8CC3 /* ValueAccessible.swift in Sources */, + 9D2E269E2240D1CA00C9EDF7 /* Wrappers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -330,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/Dependency.swift b/Unio/Dependency.swift index b246fb3..c5bf54a 100644 --- a/Unio/Dependency.swift +++ b/Unio/Dependency.swift @@ -17,31 +17,20 @@ public final class Dependency internal init(input: Input, state: State, extra: Extra) { - self._input = input self.state = state self.extra = extra + self.inputObservables = ObservableWrapper(input) } /// Makes possible to get Observable from `Input`. - public func inputObservable(for keyPath: KeyPath) -> Observable { - - return _input[keyPath: keyPath].asObservable() - } + public func inputObservable(for keyPath: KeyPath) -> Observable { - /// Returns read-only value accessible object (e.g. BehaviorRelay). - /// - /// - note: Object is reference, not copied one. - 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. - public func readOnlyReference(from output: Relay, for keyPath: KeyPath) -> ReadOnly { - return ReadOnly(output, for: keyPath) + return inputObservables[dynamicMember: keyPath] } } diff --git a/Unio/ReadOnly.swift b/Unio/PrimitiveProperty.swift similarity index 51% rename from Unio/ReadOnly.swift rename to Unio/PrimitiveProperty.swift index c10bfa0..8f763b2 100644 --- a/Unio/ReadOnly.swift +++ b/Unio/PrimitiveProperty.swift @@ -1,8 +1,8 @@ // -// ReadOnly.swift +// PrimitiveProperty.swift // Unio // -// Created by marty-suzuki on 2019/03/20. +// Created by marty-suzuki on 2019/06/13. // Copyright © 2019 tv.abema. All rights reserved. // @@ -10,38 +10,46 @@ import Foundation import RxRelay 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 ReadOnly: ObservableConvertibleType { +public final class PrimitiveProperty { - private let _value: () -> T.Element - private let _throwableValue: () throws -> T.Element - private let _asObservable: () -> Observable + private let _value: () -> Element + private let _throwableValue: () throws -> Element + private let _asObservable: () -> Observable - private init(value: @escaping () -> T.Element, - throwableValue: @escaping () throws -> T.Element, - asObservable: @escaping () -> 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 { - /// Makes possible to get Observable - public func asObservable() -> Observable { + public func asObservable() -> Observable { return _asObservable() } } -extension ReadOnly: ValueAccessible where T: ValueAccessibleObservable { +extension PrimitiveProperty: ValueAccessible where Failure == Never { /// Makes possible to get value - public var value: T.Element { + public var value: Element { return _value() } - internal convenience init(_ output: Relay, for keyPath: KeyPath) { + 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") }, @@ -49,14 +57,14 @@ extension ReadOnly: ValueAccessible where T: ValueAccessibleObservable { } } -extension ReadOnly: ThrowableValueAccessible where T: ThrowableValueAccessibleObservable { +extension PrimitiveProperty: ThrowableValueAccessible where Failure == Error { /// Makes possible to get throwableValue - public func throwableValue() throws -> T.Element { + public func throwableValue() throws -> Element { return try _throwableValue() } - internal convenience init(_ output: Relay, for keyPath: KeyPath) { + 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/Relay.swift b/Unio/Relay.swift deleted file mode 100644 index 5f704c0..0000000 --- a/Unio/Relay.swift +++ /dev/null @@ -1,166 +0,0 @@ -// -// Relay.swift -// Unio -// -// Created by marty-suzuki on 2019/03/15. -// Copyright © 2019 tv.abema. All rights reserved. -// - -import Foundation -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`. -public final class Relay { - - internal let _dependency: T - - private init(dependency: T) { - self._dependency = dependency - } - - 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) -> AnyObserver { - - return AnyObserver { [weak self] event in - switch event { - case let .next(value): - guard let me = self else { - return - } - me._accept(value, for: keyPath) - - case .error, .completed: - break - } - } - } - - 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() - } -} - -// - MARK: Relay - -extension Relay where T: InputType { - - /// Initializes with `Input`. - public convenience init(_ dependency: T) { - self.init(dependency: dependency) - } - - /// Accepts `event` and emits it to subscribers via `Input`. - public func accept(_ value: U.Element, for keyPath: KeyPath) { - - _accept(value, for: keyPath) - } - - /// Send `event` to this observer via `Input`. - public func accept(for keyPath: KeyPath) -> AnyObserver { - - return _accept(for: keyPath) - } - - /// Notify observer about sequence event via `Input`. - public func onEvent(_ event: Event, for keyPath: KeyPath) { - - _onEvent(event, for: keyPath) - } - - /// Send `event` to this observer via `Input`. - public func onEvent(for keyPath: KeyPath) -> AnyObserver { - - return _onEvent(for: keyPath) - } -} - -// - MARK: Relay - -extension Relay where T: OutputType { - - /// Initializes with `Output`. - public convenience init(_ dependency: T) { - self.init(dependency: dependency) - } - - /// Makes possible to get Observable from `Output`. - public func observable(for keyPath: KeyPath) -> Observable { - - return _observable(for: 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) - } - - /// 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) - } -} - -// - MARK: Relay - -extension Relay where T: ValueAccessibleObservable { - - public var value: T.Element { - return _dependency.value - } - - internal convenience init(_ output: Relay, for keyPath: KeyPath) { - let behaviorRelay = output._dependency[keyPath: keyPath] - self.init(dependency: behaviorRelay) - } - - public func asObservable() -> Observable { - return _dependency.asObservable() - } -} - -// - MARK: Relay - -extension Relay where T: ThrowableValueAccessibleObservable { - - internal convenience init(_ output: Relay, for keyPath: KeyPath) { - let behaviorSubject = output._dependency[keyPath: keyPath] - self.init(dependency: behaviorSubject) - } - - public func throwableValue() throws -> T.Element { - return try _dependency.throwableValue() - } - - public func asObservable() -> Observable { - return _dependency.asObservable() - } -} diff --git a/Unio/UnioStream.swift b/Unio/UnioStream.swift index e3e0a9b..6b15299 100644 --- a/Unio/UnioStream.swift +++ b/Unio/UnioStream.swift @@ -9,21 +9,21 @@ /// 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: 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) { let dependency = Dependency(input: input, state: state, extra: extra) let output = logic.bind(from: dependency) - self.input = Relay(input) - self.output = Relay(output) - self.state = state - self.extra = extra - self.logic = logic + self.input = InputWrapper(input) + self.output = OutputWrapper(output) + self._state = state + self._extra = extra + self._logic = logic } } diff --git a/Unio/Wrappers.swift b/Unio/Wrappers.swift new file mode 100644 index 0000000..fe9be20 --- /dev/null +++ b/Unio/Wrappers.swift @@ -0,0 +1,198 @@ +// +// Wrappers.swift +// Unio +// +// Created by marty-suzuki on 2019/03/15. +// Copyright © 2019 tv.abema. All rights reserved. +// + +import Foundation +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). +@dynamicMemberLookup +public final class InputWrapper { + + internal let _dependency: T + + /// Initializes with `Input`. + 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) { + + self[dynamicMember: keyPath](value) + } + + /// Send `event` to this observer via `Input`. + public func accept(for keyPath: KeyPath) -> AnyObserver { + + return self[dynamicMember: keyPath] + } + + /// Notify observer about sequence event via `Input`. + public func onEvent(_ event: Event, for keyPath: KeyPath) { + + onEvent(for: keyPath).on(event) + } + + /// Send `event` to this observer via `Input`. + public func onEvent(for keyPath: KeyPath) -> AnyObserver { + + 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 + 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() + } +} + +/// 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 { + + internal let _dependency: T + + /// Initializes with `Output`. + public init(_ dependency: T) { + self._dependency = dependency + } + + /// Makes possible to get Observable from `Output`. + public func observable(for keyPath: KeyPath) -> Observable { + + return self[dynamicMember: keyPath] + } + + /// Makes possible to get value from Output when generic parameter is `BehaviorRelay`. + public func value(for keyPath: KeyPath) -> U.Element { + + 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 { + + 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 { + + private let object: T + + internal init(_ object: T) { + 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() + } +} + +#if swift(<5.1) +extension InputWrapper { + + @available(*, unavailable) + subscript(dynamicMember member: String) -> Never { + fatalError("must not be accessible") + } +} + +extension OutputWrapper { + + @available(*, unavailable) + subscript(dynamicMember member: String) -> Never { + fatalError("must not be accessible") + } +} + +extension ObservableWrapper { + + @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 94b8b22..9248971 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) @@ -51,31 +61,6 @@ final class DependencyTests: XCTestCase { disposable.dispose() } - - func testRelayOnly_ValueAccessible() { - - let expected = "test-ValueAccessible" - let testTarget = dependency.testTarget - - let readOnly = testTarget.readOnlyReference(from: dependency.output, for: \.relay) - - dependency.outputRelay.accept(expected) - - XCTAssertEqual(readOnly.value, expected) - } - - func testRelayOnly_ThrowableValueAccessible() { - - let expected = "test-ThrowableValueAccessible" - let testTarget = dependency.testTarget - - let readOnly = testTarget.readOnlyReference(from: dependency.output, for: \.subject) - - dependency.outputSubject.onNext(expected) - - XCTAssertEqual(try readOnly.throwableValue(), expected) - } - } extension DependencyTests { @@ -94,7 +79,7 @@ extension DependencyTests { let testTarget: Unio.Dependency - let output: Relay + let output: OutputWrapper let inputSubject = PublishSubject() let inputRelay = PublishRelay() @@ -103,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 77% rename from UnioTests/TestCases/ReadOnlyTests.swift rename to UnioTests/TestCases/PrimitivePropertyTests.swift index d391603..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 { @@ -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) + 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 ac853a3..c56c74f 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.onNext(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.relay.value) + #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) diff --git a/UnioTests/TestCases/RelayTests.swift b/UnioTests/TestCases/WrappersTests.swift similarity index 63% rename from UnioTests/TestCases/RelayTests.swift rename to UnioTests/TestCases/WrappersTests.swift index 01c7366..a7d6cda 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! @@ -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(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,94 +113,98 @@ final class RelayTests: XCTestCase { let testTarget = dependency.testTargetOutput let stack = BehaviorRelay(value: nil) - let disposable = testTarget.observable(for: \.relay) + #if swift(>=5.1) + let disposable = testTarget._observable + .bind(to: stack) + #else + 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) - - XCTAssertEqual(testTarget.value(for: \.relay), expected) - } - - 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 + let stack = BehaviorRelay(value: nil) - dependency.outputSubject.onNext(expected) - - XCTAssertEqual(try testTarget.value(for: \.subject), expected) - } - - func testValueAccessibleObservable_value() { - - let expected = "test-value" - let testTarget = dependency.testTargetRelay + #if swift(>=5.1) + let disposable = testTarget.relay + .bind(to: stack) + #else + let disposable = testTarget.observable(for: \.relay) + .bind(to: stack) + #endif 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 { @@ -192,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() @@ -204,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) } } }