Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unioを開発してプロダクトに導入してから2年が経った

 Unioを開発してプロダクトに導入してから2年が経った

集まれSwift好き!Swift愛好会 vol.59 @ オンライン
https://love-swift.connpass.com/event/209864/

Live coding:
https://youtu.be/0DeC672gFto?t=1831

Taiki Suzuki

April 22, 2021
Tweet

More Decks by Taiki Suzuki

Other Decks in Programming

Transcript

  1. ViewModelͷ࣮૷͕ेਓे৭ͱ͸ final class CountViewController: UIViewController { private let countUpButton =

    UIButton() private let countLabel = UILabel() private let viewModel = CountViewModel() override func viewDidLoad() { super.viewDidLoad() ... } }
  2. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .subscribe(onNext: { [viewModel] in viewModel.countUp() }) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> private let _count = BehaviorRelay<Int>(value: 0) init() { self.count = _count.map(String.init) } func countUp() { _count.accept(_count.value + 1) } } ViewModelͷ࣮૷ͷྫ ͦͷ1
  3. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .subscribe(onNext: { [viewModel] in viewModel.countUp() }) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> private let _count = BehaviorRelay<Int>(value: 0) init() { self.count = _count.map(String.init) } func countUp() { _count.accept(_count.value + 1) } } ViewModelͷ࣮૷ͷྫ ͦͷ1 ֎෦΁ͷग़ྗͱ಺෦ঢ়ଶΛQSPQFSUZͰఆٛ
  4. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .subscribe(onNext: { [viewModel] in viewModel.countUp() }) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> private let _count = BehaviorRelay<Int>(value: 0) init() { self.count = _count.map(String.init) } func countUp() { _count.accept(_count.value + 1) } } ViewModelͷ࣮૷ͷྫ ͦͷ1 ϝιουܦ༝Ͱ಺෦ঢ়ଶͷߋ৽
  5. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .subscribe(onNext: { [viewModel] in viewModel.countUp() }) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> private let _count = BehaviorRelay<Int>(value: 0) init() { self.count = _count.map(String.init) } func countUp() { _count.accept(_count.value + 1) } } ViewModelͷ࣮૷ͷྫ ͦͷ1 ಺෦ঢ়ଶ͕ߋ৽͞ΕΔͱ4USJOHʹม׵ͯ͠ग़ྗ
  6. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count.bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap.subscribe(onNext: { [viewModel] in viewModel.countUp() }) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> private let _countUp = PublishRelay<Void>() private let disposeBag = DisposeBag() init() { let _count = BehaviorRelay<Int>(value: 0) self.count = _count.map(String.init) _countUp.withLatestFrom(_count) { $1 + 1 }.bind(to: _count) .disposed(by: disposeBag) } func countUp() { _countUp.accept(()) } } ViewModelͷ࣮૷ͷྫ ͦͷ2
  7. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count.bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap.subscribe(onNext: { [viewModel] in viewModel.countUp() }) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> private let _countUp = PublishRelay<Void>() private let disposeBag = DisposeBag() init() { let _count = BehaviorRelay<Int>(value: 0) self.count = _count.map(String.init) _countUp.withLatestFrom(_count) { $1 + 1 }.bind(to: _count) .disposed(by: disposeBag) } func countUp() { _countUp.accept(()) } } ViewModelͷ࣮૷ͷྫ ͦͷ2 ಺෦ঢ়ଶΛCJOEͳͲͰΩϟϓνϟ͞ΕΔ͜ͱΛ લఏʹϩʔΧϧʹఆٛ
  8. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count.bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap.subscribe(onNext: { [viewModel] in viewModel.countUp() }) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> private let _countUp = PublishRelay<Void>() private let disposeBag = DisposeBag() init() { let _count = BehaviorRelay<Int>(value: 0) self.count = _count.map(String.init) _countUp.withLatestFrom(_count) { $1 + 1 }.bind(to: _count) .disposed(by: disposeBag) } func countUp() { _countUp.accept(()) } } ViewModelͷ࣮૷ͷྫ ͦͷ2 ϝιουͷݺͼग़͠ΛϋϯυϦϯά͢ΔͨΊͷ 
 1VCMJTI3FMBZΛఆٛ
  9. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count.bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap.subscribe(onNext: { [viewModel] in viewModel.countUp() }) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> private let _countUp = PublishRelay<Void>() private let disposeBag = DisposeBag() init() { let _count = BehaviorRelay<Int>(value: 0) self.count = _count.map(String.init) _countUp.withLatestFrom(_count) { $1 + 1 }.bind(to: _count) .disposed(by: disposeBag) } func countUp() { _countUp.accept(()) } } ViewModelͷ࣮૷ͷྫ ͦͷ2 1VCMJTI3FMBZ͔ΒͷΠϕϯτΛ΋ͱʹ ঢ়ଶΛߋ৽͢Δ
  10. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count.bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap.bind(to: viewModel.countUp) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> let countUp: AnyObserver<Void> private let disposeBag = DisposeBag() init() { let _count = BehaviorRelay<Int>(value: 0) self.count = _count.map(String.init) let _countUp = PublishSubject<Void>() self.countUp = _countUp.asObserver() _countUp.withLatestFrom(_count) { $1 + 1 }.bind(to: _count) .disposed(by: disposeBag) } } ViewModelͷ࣮૷ͷྫ ͦͷ3
  11. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count.bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap.bind(to: viewModel.countUp) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> let countUp: AnyObserver<Void> private let disposeBag = DisposeBag() init() { let _count = BehaviorRelay<Int>(value: 0) self.count = _count.map(String.init) let _countUp = PublishSubject<Void>() self.countUp = _countUp.asObserver() _countUp.withLatestFrom(_count) { $1 + 1 }.bind(to: _count) .disposed(by: disposeBag) } } ViewModelͷ࣮૷ͷྫ ͦͷ3 "OZ0CTFSWFSͱͯ͠ೖྗΛఆٛ
  12. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count.bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap.bind(to: viewModel.countUp) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> let countUp: AnyObserver<Void> init() { let _countUp = PublishSubject<Void>() self.countUp = _countUp.asObserver() self.count = _countUp.scan(0) { count, _ in count + 1 } .startWith(0) .map(String.init) } } ViewModelͷ࣮૷ͷྫ ͦͷ4
  13. final class CountViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad()

    viewModel.count.bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap.bind(to: viewModel.countUp) .disposed(by: rx.disposeBag) } } final class CountViewModel { let count: Observable<String> let countUp: AnyObserver<Void> init() { let _countUp = PublishSubject<Void>() self.countUp = _countUp.asObserver() self.count = _countUp.scan(0) { count, _ in count + 1 } .startWith(0) .map(String.init) } } ViewModelͷ࣮૷ͷྫ ͦͷ4 ໌ࣔతʹ಺෦ঢ়ଶ͸ఆٛͤͣɺTDBOͳͲ Λ࢖ͬͯঢ়ଶΛߋ৽͢Δ
  14. UnioΛར༻࣮ͨ͠૷ final class CountViewModel: UnioStream<CountViewModel> { struct Input: InputType {

    let countUp = PublishRelay<Void>() } struct State: StateType { let count = BehaviorRelay<Int>(value: 0) } struct Output: OutputType { let count: Observable<String> } static func bind(from dependency: Dependency<Input, State, NoExtra>, disposeBag: DisposeBag) -> Output { let state = dependency.state dependency.inputObservables.countUp .withLatestFrom(state.count) { $1 + 1 } .bind(to: state.count) .disposed(by: disposeBag) return Output(count: state.count.map(String.init)) } }
  15. UnioΛར༻࣮ͨ͠૷ final class CountViewModel: UnioStream<CountViewModel> { struct Input: InputType {

    let countUp = PublishRelay<Void>() } struct State: StateType { let count = BehaviorRelay<Int>(value: 0) } struct Output: OutputType { let count: Observable<String> } static func bind(from dependency: Dependency<Input, State, NoExtra>, disposeBag: DisposeBag) -> Output { let state = dependency.state dependency.inputObservables.countUp .withLatestFrom(state.count) { $1 + 1 } .bind(to: state.count) .disposed(by: disposeBag) return Output(count: state.count.map(String.init)) } } ֎෦͔ΒೖྗΛఆٛ͢Δ৔ॴ QSPQFSZΛ1VCMJTI3FMBZͰఆٛͯ͠΋ ֎෦͔ΒΞΫηε͢Δͱ"OZ0CTFSWFS ʹม׵͞Εɺ಺෦͔ΒΞΫηε͢Δͱ 0CTFSWBCMFʹͳΔ
  16. UnioΛར༻࣮ͨ͠૷ final class CountViewModel: UnioStream<CountViewModel> { struct Input: InputType {

    let countUp = PublishRelay<Void>() } struct State: StateType { let count = BehaviorRelay<Int>(value: 0) } struct Output: OutputType { let count: Observable<String> } static func bind(from dependency: Dependency<Input, State, NoExtra>, disposeBag: DisposeBag) -> Output { let state = dependency.state dependency.inputObservables.countUp .withLatestFrom(state.count) { $1 + 1 } .bind(to: state.count) .disposed(by: disposeBag) return Output(count: state.count.map(String.init)) } } ಺෦ͷঢ়ଶΛఆٛ͢Δ৔ॴ ֎෦͔Β͸ࢀর͞Εͳ͍
  17. UnioΛར༻࣮ͨ͠૷ final class CountViewModel: UnioStream<CountViewModel> { struct Input: InputType {

    let countUp = PublishRelay<Void>() } struct State: StateType { let count = BehaviorRelay<Int>(value: 0) } struct Output: OutputType { let count: Observable<String> } static func bind(from dependency: Dependency<Input, State, NoExtra>, disposeBag: DisposeBag) -> Output { let state = dependency.state dependency.inputObservables.countUp .withLatestFrom(state.count) { $1 + 1 } .bind(to: state.count) .disposed(by: disposeBag) return Output(count: state.count.map(String.init)) } } ֎෦΁ͷग़ྗΛఆٛ͢Δ৔ॴ QSPQFSZΛ#FIBWJPS3FMBZͰఆٛͯ͠΋ ֎෦͔ΒΞΫηε͢Δͱ0CTFSWBCMF΁ ม׵·ͨ͸஋ͷΞΫηεͱͳΔ
  18. UnioΛར༻࣮ͨ͠૷ final class CountViewModel: UnioStream<CountViewModel> { struct Input: InputType {

    let countUp = PublishRelay<Void>() } struct State: StateType { let count = BehaviorRelay<Int>(value: 0) } struct Output: OutputType { let count: Observable<String> } static func bind(from dependency: Dependency<Input, State, NoExtra>, disposeBag: DisposeBag) -> Output { let state = dependency.state dependency.inputObservables.countUp .withLatestFrom(state.count) { $1 + 1 } .bind(to: state.count) .disposed(by: disposeBag) return Output(count: state.count.map(String.init)) } } *OQVUɾ಺෦ͷঢ়ଶɾ֎෦ґଘ͔Β0VUQVUΛੜ੒͢Δ৔ॴ EFQFOEFODZJOQVU0CTFSWBCMFT͔Β*OQVUɺEFQFOEFODZTUBUF͔Β ಺෦ঢ়ଶɺEFQFOEFODZFYUSB͔Β֎෦ґଘΛऔಘͰ͖Δ TUBUJDGVODͳͷͰTFMGͷΩϟϓνϟΛؾʹ͢ΔඞཁͳͲ΋ͳ͍
  19. UnioΛར༻࣮ͨ͠૷ final class CountViewController: UIViewController { private let countUpButton =

    UIButton() private let countLabel = UILabel() private let viewModel = CountViewModel(input: .init(), state: .init(), extra: .init()) override func viewDidLoad() { super.viewDidLoad() viewModel.output.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .bind(to: viewModel.input.countUp) .disposed(by: rx.disposeBag) } }
  20. UnioΛར༻࣮ͨ͠૷ final class CountViewController: UIViewController { private let countUpButton =

    UIButton() private let countLabel = UILabel() private let viewModel = CountViewModel(input: .init(), state: .init(), extra: .init()) override func viewDidLoad() { super.viewDidLoad() viewModel.output.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .bind(to: viewModel.input.countUp) .disposed(by: rx.disposeBag) } } #FIBWJPS3FMBZͰఆٛͯ͠΋0CTFSWBCMF ·ͨ͸஋ͷΞΫηεͱͳΔ
  21. UnioΛར༻࣮ͨ͠૷ final class CountViewController: UIViewController { private let countUpButton =

    UIButton() private let countLabel = UILabel() private let viewModel = CountViewModel(input: .init(), state: .init(), extra: .init()) override func viewDidLoad() { super.viewDidLoad() viewModel.output.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .bind(to: viewModel.input.countUp) .disposed(by: rx.disposeBag) } } 1VCMJTI3FMBZͰఆٛͯ͠΋"OZ0CTFSWFS ·ͨ͸&MFNFOUΛҾ਺ʹͱΔDMPTVSFͱͳΔ
  22. v0.1.0 Initial Release final class CountViewModel: UnioStream<CountViewModel.Logic> { struct Input:

    InputType { let countUp = PublishRelay<Void>() } struct State: StateType { let count = BehaviorRelay<Int>(value: 0) } struct Output: OutputType { let count: Observable<String> } struct Logic: LogicType { private let disposeBag = DisposeBag() func bind(from dependency: Dependency<Input, State, NoExtra>) -> Output { let state = dependency.state dependency.inputObservables.countUp .withLatestFrom(state.count) { $1 + 1 } .bind(to: state.count) .disposed(by: disposeBag) return Output(count: state.count.map(String.init)) } } }
  23. v0.1.0 Initial Release final class CountViewModel: UnioStream<CountViewModel.Logic> { struct Input:

    InputType { let countUp = PublishRelay<Void>() } struct State: StateType { let count = BehaviorRelay<Int>(value: 0) } struct Output: OutputType { let count: Observable<String> } struct Logic: LogicType { private let disposeBag = DisposeBag() func bind(from dependency: Dependency<Input, State, NoExtra>) -> Output { let state = dependency.state dependency.inputObservables.countUp .withLatestFrom(state.count) { $1 + 1 } .bind(to: state.count) .disposed(by: disposeBag) return Output(count: state.count.map(String.init)) } } } -PHJDͷఆ͕ٛ6OJP4USFBNࣗ਎Ͱ͸ͳ͘ ผͷܕͱͯ͠ఆٛ͞Ε͍ͯͨ
  24. final class CountViewController: UIViewController { private let countUpButton = UIButton()

    private let countLabel = UILabel() private let viewModel = CountViewModel(input: .init(), state: .init(), extra: .init(), logic: .init()) override func viewDidLoad() { super.viewDidLoad() viewModel.output.observable(for: \.count) .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .bind(to: viewModel.input.accept(for: \.countUp)) .disposed(by: rx.disposeBag) } } v0.1.0 Initial Release
  25. final class CountViewController: UIViewController { private let countUpButton = UIButton()

    private let countLabel = UILabel() private let viewModel = CountViewModel(input: .init(), state: .init(), extra: .init(), logic: .init()) override func viewDidLoad() { super.viewDidLoad() viewModel.output.observable(for: \.count) .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .bind(to: viewModel.input.accept(for: \.countUp)) .disposed(by: rx.disposeBag) } } v0.1.0 Initial Release ,FZ1BUIΛར༻ͯ͠0CTFSWBCMFΛऔಘ͢Δ ϝιου͕༻ҙ͞Ε͍ͯͨ
  26. v0.1.0 Initial Release protocol CountViewStreamType: AnyObject { var input: InputWrapper<CountViewStream.Input>

    { get } var output: OutputWrapper<CountViewStream.Output> { get } } final class CountViewStream: UnioStream<CountViewStream>, CountViewStreamType { convenience init(extra: Extra = .init()) { self.init(input: Input(), state: State(), extra: extra) } } extension CountViewStream { struct Input: InputType { } struct Output: OutputType { } struct State: StateType { } struct Extra: ExtraType { } static func bind(from dependency: Dependency<Input, State, Extra>, disposeBag: DisposeBag) -> Output { let state = dependency.state return Output() } }
  27. v0.5.0 DynamicMemberLookup support final class CountViewController: UIViewController { private let

    countUpButton = UIButton() private let countLabel = UILabel() private let viewModel = CountViewModel(input: .init(), state: .init(), extra: .init(), logic: .init()) override func viewDidLoad() { super.viewDidLoad() viewModel.output.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .bind(to: viewModel.input.countUp) .disposed(by: rx.disposeBag) } }
  28. v0.5.0 DynamicMemberLookup support final class CountViewController: UIViewController { private let

    countUpButton = UIButton() private let countLabel = UILabel() private let viewModel = CountViewModel(input: .init(), state: .init(), extra: .init(), logic: .init()) override func viewDidLoad() { super.viewDidLoad() viewModel.output.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .bind(to: viewModel.input.countUp) .disposed(by: rx.disposeBag) } } %ZOBNJD.FNCFS-PPLVQΛ࠾༻͢Δ͜ͱͰ PCTFSWBCMF GPS ͔ΒΞΫηε͢ΔܗͰ͸ͳ͘ QSPQFSUZʹΞΫηε͢ΔΑ͏ͳܗʹͰ͖ͨ
  29. v0.6.0 Improve LogicType implementation final class CountViewModel: UnioStream<CountViewModel> { struct

    Input: InputType { let countUp = PublishRelay<Void>() } struct State: StateType { let count = BehaviorRelay<Int>(value: 0) } struct Output: OutputType { let count: Observable<String> } static func bind(from dependency: Dependency<Input, State, NoExtra>, disposeBag: DisposeBag) -> Output { let state = dependency.state dependency.inputObservables.countUp .withLatestFrom(state.count) { $1 + 1 } .bind(to: state.count) .disposed(by: disposeBag) return Output(count: state.count.map(String.init)) } }
  30. v0.6.0 Improve LogicType implementation final class CountViewModel: UnioStream<CountViewModel> { struct

    Input: InputType { let countUp = PublishRelay<Void>() } struct State: StateType { let count = BehaviorRelay<Int>(value: 0) } struct Output: OutputType { let count: Observable<String> } static func bind(from dependency: Dependency<Input, State, NoExtra>, disposeBag: DisposeBag) -> Output { let state = dependency.state dependency.inputObservables.countUp .withLatestFrom(state.count) { $1 + 1 } .bind(to: state.count) .disposed(by: disposeBag) return Output(count: state.count.map(String.init)) } } -PHJD5ZQFΛผͷܕͱͯ͠ఆٛ͢ΔͷͰ͸ͳ͘ 
 6OJP4USFBNͷTVCDMBTTʹఆٛͰ͖ΔΑ͏ʹରԠ
  31. v0.6.0 Improve LogicType implementation final class CountViewController: UIViewController { private

    let countUpButton = UIButton() private let countLabel = UILabel() private let viewModel = CountViewModel(input: .init(), state: .init(), extra: .init()) override func viewDidLoad() { super.viewDidLoad() viewModel.output.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .bind(to: viewModel.input.countUp) .disposed(by: rx.disposeBag) } }
  32. v0.6.0 Improve LogicType implementation final class CountViewController: UIViewController { private

    let countUpButton = UIButton() private let countLabel = UILabel() private let viewModel = CountViewModel(input: .init(), state: .init(), extra: .init()) override func viewDidLoad() { super.viewDidLoad() viewModel.output.count .bind(to: countLabel.rx.text) .disposed(by: rx.disposeBag) countUpButton.rx.tap .bind(to: viewModel.input.countUp) .disposed(by: rx.disposeBag) } } -PHJD5ZQFΛ6OJP4USFBNͷTVCDMBTTʹ࣮૷Ͱ͖ΔΑ͏ʹ ͳͬͨ͜ͱͰ-PHJDΛ౉͢ඞཁ͕ͳ͘ͳͬͨ
  33. public typealias UnioStream<Logic: LogicType> = PrimitiveStream<Logic> & LogicType open class

    PrimitiveStream<Logic: LogicType> { public let input: InputWrapper<Logic.Input> public let output: OutputWrapper<Logic.Output> private let _state: Logic.State private let _extra: Logic.Extra private let _disposeBag = DisposeBag() public init(input: Logic.Input, state: Logic.State, extra: Logic.Extra) { let dependency = Dependency(input: input, state: state, extra: extra) let output = Logic.bind(from: dependency, disposeBag: _disposeBag) self.input = InputWrapper(input) self.output = OutputWrapper(output) self._state = state self._extra = extra } } v0.9.0 Workaround for SR-12081
  34. public typealias UnioStream<Logic: LogicType> = PrimitiveStream<Logic> & LogicType open class

    PrimitiveStream<Logic: LogicType> { public let input: InputWrapper<Logic.Input> public let output: OutputWrapper<Logic.Output> private let _state: Logic.State private let _extra: Logic.Extra private let _disposeBag = DisposeBag() public init(input: Logic.Input, state: Logic.State, extra: Logic.Extra) { let dependency = Dependency(input: input, state: state, extra: extra) let output = Logic.bind(from: dependency, disposeBag: _disposeBag) self.input = InputWrapper(input) self.output = OutputWrapper(output) self._state = state self._extra = extra } } v0.9.0 Workaround for SR-12081 UZQFBMJBTͰ-PHJD5ZQFͷ࣮૷Λࣗ਎ʹڧ੍͢Δܗʹͭͭ͠ ࣮ଶͷ(FOFSJDBSHVNFOUʹࢦఆ͢ΔܗʹͳΔͨΊɺ43 ͷঢ়ଶʹͳͬͯ͠·͏
  35. public typealias UnioStream<Logic: LogicType> = AnyLogicBasedStream<Logic.Input, Logic.Output> & LogicType open

    class AnyLogicBasedStream<Input: InputType, Output: OutputType>: AnyLogicBasedStreamType { public let input: InputWrapper<Input> public let output: OutputWrapper<Output> private let _stream: AnyObject required public init<Logic: LogicType>( input: Logic.Input, state: Logic.State, extra: Logic.Extra, logic _: Logic.Type ) where Input == Logic.Input, Output == Logic.Output { let stream = PrimitiveStream<Logic>(input: input, state: state, extra: extra) self.input = stream.input self.output = stream.output self._stream = stream } } v0.9.0 Workaround for SR-12081
  36. public typealias UnioStream<Logic: LogicType> = AnyLogicBasedStream<Logic.Input, Logic.Output> & LogicType open

    class AnyLogicBasedStream<Input: InputType, Output: OutputType>: AnyLogicBasedStreamType { public let input: InputWrapper<Input> public let output: OutputWrapper<Output> private let _stream: AnyObject required public init<Logic: LogicType>( input: Logic.Input, state: Logic.State, extra: Logic.Extra, logic _: Logic.Type ) where Input == Logic.Input, Output == Logic.Output { let stream = PrimitiveStream<Logic>(input: input, state: state, extra: extra) self.input = stream.input self.output = stream.output self._stream = stream } } v0.9.0 Workaround for SR-12081 (FOFSJDBSHVNFOUʹ*OQVUͱ0VUQVU͚ͩࢦఆ͢Δܗʹͭͭ͠ ಺෦Ͱ͸΋ͱ΋ͱͷΫϥεΛॳظԽ͍ͯ͠ΔͷͰɺࣗ਎ͷ (FOFSJDBSHVNFOUʹࣗ਎Λࢦఆ͢Δঢ়ଶΛආ͚͍ͯΔ
  37. public typealias UnioStream<Logic: LogicType> = AnyLogicBasedStream<Logic.Input, Logic.Output> & LogicType open

    class AnyLogicBasedStream<Input: InputType, Output: OutputType>: AnyLogicBasedStreamType { public let input: InputWrapper<Input> public let output: OutputWrapper<Output> private let _stream: AnyObject required public init<Logic: LogicType>( input: Logic.Input, state: Logic.State, extra: Logic.Extra, logic _: Logic.Type ) where Input == Logic.Input, Output == Logic.Output { let stream = PrimitiveStream<Logic>(input: input, state: state, extra: extra) self.input = stream.input self.output = stream.output self._stream = stream } } v0.9.0 Workaround for SR-12081 UZQFBMJBTͰ-PHJD5ZQFΛࢦఆ͢ΔΑ͏ʹ͍ͯ͠ΔͷͰ ֎෦͔Βݟͨ৔߹ʹ͸ҎલͷόʔδϣϯͱಉҰͷ΋ͷʹ ݟ͑Δ͕ɺ࣮ࡍʹ͸*OQVUͱ0VUQVUΛࢦఆ͍ͯ͠Δঢ়ଶ ʹͳ͍ͬͯΔ
  38. public protocol UnioStreamType: AnyObject { associatedtype Input: InputType associatedtype Output:

    OutputType var input: InputWrapper<Input> { get } var output: OutputWrapper<Output> { get } } public protocol AnyLogicBasedStreamType: UnioStreamType { init<Logic: LogicType>( input: Logic.Input, state: Logic.State, extra: Logic.Extra, logic _: Logic.Type ) where Input == Logic.Input, Output == Logic.Output } extension AnyLogicBasedStreamType where Self: LogicType { public init(input: Input, state: State, extra: Extra) { self.init(input: input, state: state, extra: extra, logic: Self.self) } } v0.9.0 Workaround for SR-12081
  39. public protocol UnioStreamType: AnyObject { associatedtype Input: InputType associatedtype Output:

    OutputType var input: InputWrapper<Input> { get } var output: OutputWrapper<Output> { get } } public protocol AnyLogicBasedStreamType: UnioStreamType { init<Logic: LogicType>( input: Logic.Input, state: Logic.State, extra: Logic.Extra, logic _: Logic.Type ) where Input == Logic.Input, Output == Logic.Output } extension AnyLogicBasedStreamType where Self: LogicType { public init(input: Input, state: State, extra: Extra) { self.init(input: input, state: state, extra: extra, logic: Self.self) } } v0.9.0 Workaround for SR-12081 1SPUPDPMΛఆٛ͠ɺࣗ਎͕-PHJD5ZQFΛ࠾༻͍ͯ͠Δ৔߹ʹ -PHJDΛҾ਺ʹ౉͞ͳͯ͘΋ࡁΉ*OJUJBMJ[FSΛఆٛ
  40. v0.10.0 Computed Support final class TimetableViewModel: UnioStream<TimetableViewModel> { ... struct

    State: StateType { let dateList = BehaviorRelay<[Date]>(value: [Date()]) } struct Output: OutputType { let elapsedTimeForIndex: Computed<(Int) -> String> } struct Extra: ExtraType { let now: () -> Date } static func bind(from dependency: Dependency<Input, State, Extra>, disposeBag: DisposeBag) -> Output { let state = dependency.state let extra = dependency.extra let elapsedTimeForIndex = Computed<(Int) -> String> { let date = state.dateList.value[$0] return "\(extra.now().timeIntervalSince1970 - date.timeIntervalSince1970)" } return Output(elapsedTimeForIndex: elapsedTimeForIndex) } }
  41. v0.10.0 Computed Support final class TimetableViewModel: UnioStream<TimetableViewModel> { ... struct

    State: StateType { let dateList = BehaviorRelay<[Date]>(value: [Date()]) } struct Output: OutputType { let elapsedTimeForIndex: Computed<(Int) -> String> } struct Extra: ExtraType { let now: () -> Date } static func bind(from dependency: Dependency<Input, State, Extra>, disposeBag: DisposeBag) -> Output { let state = dependency.state let extra = dependency.extra let elapsedTimeForIndex = Computed<(Int) -> String> { let date = state.dateList.value[$0] return "\(extra.now().timeIntervalSince1970 - date.timeIntervalSince1970)" } return Output(elapsedTimeForIndex: elapsedTimeForIndex) } } ಈతͳॲཧΛͯ͠ɺ֎෦͔ΒΞΫηεͰ͖ΔΑ͏ʹ͢Δ
  42. v0.10.0 Computed Support final class TimetableViewController: UIViewController, UITableViewDataSource { private

    let viewModel = TimetableViewModel( input: .init(), state: .init(), extra: .init(now: Date.init) ) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = viewModel.output.elapsedTimeForIndex(indexPath.row) return cell } }
  43. v0.10.0 Computed Support final class TimetableViewController: UIViewController, UITableViewDataSource { private

    let viewModel = TimetableViewModel( input: .init(), state: .init(), extra: .init(now: Date.init) ) func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.textLabel?.text = viewModel.output.elapsedTimeForIndex(indexPath.row) return cell } }