如何使用 Rxswift 处理程序收藏按钮

How to use the Rxswift handler the favorite button

提问人:Terriermon 提问时间:7/7/2023 更新时间:7/8/2023 访问量:34

问:

我有一个最喜欢的按钮,该按钮由网络数据更改并点击按钮。当 isSelected 通过点击按钮时,它需要请求收藏或不喜欢的 API,但当它被网络数据更改时,它不需要请求 api。 这是我的代码,当 isSelected 更改时,它总是请求 api。isSelected

// in viewmodel
 isFavorite = selectedVideo
            .map { $0.isFavorite ?? false }
            .flatMapLatest({ favorite in
                onTapFavorite.scan(favorite) { acc, _ in !acc }.startWith(favorite)
            })
            .distinctUntilChanged()

// when subscribe
 Observable.combineLatest(viewModel.isFavorite, viewModel.selectedVideo)
            .flatMapLatest({ (isFavorite, video) in
            if isFavorite {
               return APIService.favoriteVideo(videoId: video.videoId)
            } else {
                return APIService.unfavoriteVideo(videoId: video.videoId)
            }
        })
            .subscribe(onNext: { _ in

        }).disposed(by: disposeBag)
iOS 版Swift RX-SWIFT

评论


答:

1赞 Daniel T. 7/8/2023 #1

你有两个功能,所以你应该有两个可观察链......

服务器更新链如下所示:

func updateServer(selectedVideo: Observable<Video>, onTapFavorite: Observable<Void>) -> Observable<(videoId: Video.ID, isFavorite: Bool)> {
    selectedVideo
        .flatMapLatest { video in
            onTapFavorite
                .scan(video.isFavorite ?? false) { isFavorite, _ in !isFavorite }
                .map { (videoId: video.id, isFavorite: $0) }
        }
}

并且是这样绑定的:

updateServer(selectedVideo: selectedVideo, onTapFavorite: favoriteButton.rx.tap.asObservable())
    .flatMapLatest {
        $0.isFavorite ? APIService.favoriteVideo(videoId: $0.videoId) : APIService.unfavoriteVideo(videoId: $0.videoId)
    }
    .subscribe(onError: { error in
        // handle error
    })
    .disposed(by: disposeBag)

对于 isSelected 属性,请使用不同的 Observable 链:

func isSelected(selectedVideo: Observable<Video>, onTapFavorite: Observable<Void>) -> Observable<Bool> {
    selectedVideo
        .map { $0.isFavorite ?? false }
        .flatMapLatest { isFavorite in
            onTapFavorite
                .scan(isFavorite) { isFavorite, _ in !isFavorite }
                .startWith(isFavorite)
        }
}

绑定如下:

isSelected(selectedVideo: selectedVideo, onTapFavorite: favoriteButton.rx.tap.asObservable())
    .bind(to: favoriteButton.rx.isSelected)
    .disposed(by: disposeBag)
0赞 Daniel T. 7/8/2023 #2

在审查这个问题的要求时。我看到在考虑错误时可能会有很多复杂性。我决定给出一个答案,使用我的因果关系工具来充分充实答案,包括错误条件:

以下代码负责以下所有操作:

  • 选择新视频时,(通过 )它会更新按钮的状态,而无需进行网络调用。selectedVideo
  • 当用户点击按钮时,它会更新按钮的状态,然后进行网络调用。如果调用失败,它会将按钮重置为以前的状态,并且对象会将错误通知错误订阅者(以防您想要发出警报或让用户知道发生错误的内容)。api
  • 如果用户在网络呼叫处于运行状态时点击该按钮,它将取消正在进行的呼叫,并为更新状态进行新的呼叫。
  • (一个有趣的边缘案例)如果视频在网络调用进行时更新,然后网络调用失败,则不会使用旧的视频对象信息更新新的视频对象。
struct Video: Identifiable {
    let id: Int
    let isFavorite: Bool

    func isFavoriteToggled() -> Video {
        Video(id: id, isFavorite: !isFavorite)
    }
}

func bind(favoriteButton: UIButton, selectedVideo: Observable<Video>, disposeBag: DisposeBag, api: API) {
    enum Input {
        case update(Video)
        case networkFailure(Video)
        case tap
    }

    let state = cycle(
        input: Observable.merge(
            selectedVideo.map(Input.update),
            favoriteButton.rx.tap.map(to: Input.tap)
        ),
        initialState: Video?.none,
        reduce: { state, input in
            switch input {
            case .update(let video):
                state = video
            case .networkFailure(let video):
                if state?.id == video.id {
                    state = video
                }
            case .tap:
                state = state.map { $0.isFavoriteToggled() }
            }
        },
        reaction: { action in
            action
                .compactMap { state, input in
                    guard case .tap = input else { return nil }
                    return state
                }
                .flatMapLatest { video in
                    (
                        video.isFavorite
                        ? api.successResponse(.unfavoriteVideo(videoId: video.id))
                        : api.successResponse(.favoriteVideo(videoId: video.id))
                    )
                    .filter { !$0 }
                    .map { _ in Input.networkFailure(video) }
                }
        }
    )

    state
        .compactMap { $0?.isFavorite }
        .bind(to: favoriteButton.rx.isSelected)
        .disposed(by: disposeBag)
}

extension Endpoint where Response == Void {
    static func favoriteVideo(videoId: Video.ID) -> Endpoint { fatalError("create url request here") }
    static func unfavoriteVideo(videoId: Video.ID) -> Endpoint { fatalError("create url request here") }
}

以上是我在我正在处理的项目中编写代码的方式。很有可能,我会将闭包拉出到可以测试的单独函数中。