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

RxSwiftからCombineへの移行準備とSwiftUIへの道標

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

 RxSwiftからCombineへの移行準備とSwiftUIへの道標

Avatar for Yuichi Kobayashi

Yuichi Kobayashi

March 26, 2021
Tweet

More Decks by Yuichi Kobayashi

Other Decks in Programming

Transcript

  1. $PNCJOF࣌୅ͷ7JFX.PEFM 👉7JFX.PEFMͰ͋Ε͹ը໘୯ҐͰҠߦͰ͖ͦ͏ʁ final class CombineProductsViewModel { // Output @Published private(set)

    var isLoading = false @Published private(set) var products: [Product] = [] // Input / Action func load() { // PassthroughSubject#sendΛτϦΨʔͨ͠Γɺ // ௚઀ϩδοΫΛ࣮ߦͯ͠OutputΛಘΔ } }
  2. !1VCMJTIFE͸ 1SPQFSUZ8SBQQFS @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0,

    *) @propertyWrapper public struct Published<Value> { /// Creates the published instance with an initial wrapped value. /// /// Don't use this initializer directly. Instead, create a property with the `@Published` attribute, as shown here: /// /// @Published var lastUpdated: Date = Date() /// /// - Parameter wrappedValue: The publisher's initial value. public init(wrappedValue: Value) … }
  3. !3Y1VCMJTIFEͭͬͯ͘Έͨ @propertyWrapper struct RxPublished<Value> { var projectedValue: BehaviorRelay<Value> { relay

    } var wrappedValue: Value { get { relay.value } set { relay.accept(newValue) } } private let relay: BehaviorRelay<Value> init(wrappedValue: Value) { self.relay = .init(value: wrappedValue) } }
  4. 6TBHF!3Y1VCMJTIFE final class RxProductsViewModel { private let useCase = ProductUseCase()

    private let bag = DisposeBag() @RxPublished private(set) var isLoading = false @RxPublished private(set) var products: [Product] = [] func load() { useCase .products() .do( onSuccess: { [unowned self] _ in self.isLoading = false }, onError: { [unowned self] error in self.isLoading = false // Handle error }, onSubscribe: { [unowned self] in self.isLoading = true } ) // subscribe .assign(to: \.products, on: self) .disposed(by: bag) } } extension PrimitiveSequence where Trait == SingleTrait { func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Element>, on object: Root) -> Disposable { return subscribe(onSuccess: { object[keyPath: keyPath] = $0 }) } } ˞BTTJHO ͸FYUFOTJPO͍ͯ͠·͢
  5. !3Y1VCMJTIFE final class RxProductsViewController: UIViewController { private let viewModel =

    RxProductsViewModel() private let bag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() bind() viewModel.load() } private func bind() { viewModel.$isLoading .observeOn(MainScheduler.instance) .subscribe(onNext: { isLoading in // Toggle loading indicator }) .disposed(by: bag) viewModel.$products .observeOn(MainScheduler.instance) .subscribe(onNext: { products in // Update view }) .disposed(by: bag) } }
  6. !3Y1VCMJTIFEWT!1VCMJTIFE final class RxProductsViewController: UIViewController { private let viewModel =

    RxProductsViewModel() private let bag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() bind() viewModel.load() } private func bind() { viewModel.$isLoading .observeOn(MainScheduler.instance) .subscribe(onNext: { isLoading in // Toggle loading indicator }) .disposed(by: bag) viewModel.$products .observeOn(MainScheduler.instance) .subscribe(onNext: { products in // Update view }) .disposed(by: bag) } } final class CombineProductsViewController: UIViewController { private let viewModel = CombineProductsViewModel() private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() bind() viewModel.load() } private func bind() { viewModel.$isLoading .receive(on: DispatchQueue.main) .sink(receiveValue: { isLoading in // Toggle loading indicator }) .store(in: &cancellables) viewModel.$products .receive(on: DispatchQueue.main) .sink(receiveValue: { products in // Update view }) .store(in: &cancellables) } } PCTFSWF0O ˠSFDFJWF TVCTDSJCF ˠTJOL EJTQPTFE ˠTUPSF
  7. 3Y$PNCJOF final class CombineProductsViewModel { private let useCase = ProductUseCase()

    private var cancellables = Set<AnyCancellable>() @Published private(set) var isLoading = false @Published private(set) var products: [Product] = [] func load() { useCase .products() // Single -> AnyPublisher .asPublisher() // লུ: isLoading ͸ handleEvents() Ͱ੍ޚ .catch { error in // Handle error return Empty() } .assign(to: \.products, on: self) .store(in: &cancellables) } } final class ProductUseCase { func products() -> Single<[Product]> { … } } ˞6TF$BTF͸͜Μͳײ͡
  8. 4XJGU6*ରԠ7.ʹ͢Δ final class CombineProductsHostingController: UIHostingController<ProductsScene> { private let viewModel =

    CombineProductsViewModel() required init?(coder aDecoder: NSCoder) { super.init( coder: aDecoder, rootView: ProductsScene(viewModel: self.viewModel) ) } } struct ProductsScene: View { @ObservedObject private(set) var viewModel: CombineProductsViewModel var body: some View { List { ForEach(viewModel.products) { product in Text(product.name) } } .onAppear(perform: { viewModel.load() }) } } 4XJGU6*ͰϏϡʔΛ࡞੒ final class CombineProductsViewModel: ObservableObject { … } 7JFX.PEFMΛमਖ਼