[RxSwift] Single과 Completable

jane·2022년 4월 30일
0

RxSwift

목록 보기
2/4
post-thumbnail

Traits

Observable을 감싸고 있는 구조체임
.asObservable() 사용시 다시 기본 Observable로 돌아갈 수 있음

  • Single
  • Completable
  • Maybe

Single

단 하나의 요소error만 방출하는 Observable의 변형인 Traits중 하나이다.

  • 언제 사용하냐?
    보통 HTTP 요청의 결과로 response와 error를 딱 리턴하므로 많이 쓰임

  • 특징

    • 딱 하나의 요소나 error만 방출
    • side effects를 공유하지 않음(?)
    • Observable에 .asSingle() 사용시 Single로 변환 가능

1. Single.create

func fetch() -> Single<[Project]> {
    var projects = [Project]()
    return Single.create { single in
        self.realm.objects(ProjectRealm.self).forEach { projectRealm in
            let project = Project(projectRealm: projectRealm)
            projects.append(project)
        }
        single(.success(projects))
        return Disposables.create()
    }
}
func getRepo(_ repo: String) -> Single<[String: Any]> {
    return Single<[String: Any]>.create { single in
        let task = URLSession.shared.dataTask(with: URL(string: "https://api.github.com/repos/\(repo)")!) { data, _, error in
            if let error = error {
                single(.error(error))
                return
            }

            guard let data = data,
                  let json = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves),
                  let result = json as? [String: Any] else {
                single(.error(DataError.cantParseJSON))
                return
            }

            single(.success(result))
        }

        task.resume()

        return Disposables.create { task.cancel() }
    }
}

getRepo("ReactiveX/RxSwift")
    .subscribe { event in
        switch event {
            case .success(let json):
                print("JSON: ", json)
            case .error(let error):
                print("Error: ", error)
        }
    }
    .disposed(by: disposeBag)

Completable

completederror만 방출하는 Observable의 변형인 Traits중 하나이다.

  • 언제 사용하냐?
    특정 동작이 끝났는지 여부만 관심이 있고, 그 동작으로 인해 일어난 결과물에는 관심이 없을 때 사용한다.

ex) Firebase에 데이터를 업데이트하고 업데이트 완료했다고 completable로 알려주면
구독하고 있는 곳에서 데이터 업데이트가 되고 나서 수행해야할 동작을 수행할 수 있음

  • 특징
    • 요소를 방출하지 않음
    • side effect를 공유하지 않음(무슨말이지?)

1. Completable.create

Observable.create하는거랑 같은 방법이다.
똑같이 create의 리턴 타입이 Disposable이라서 return Disposables.create() 해야함

프로젝트 적용 예시

//Completable.create
func delete(_ project: Project) -> Completable {
    return Completable.create { completable in
        self.dataBase
            .collection("users")
            .document(project.id.description)
            .delete()
        completable(.completed)
        return Disposables.create()
    }
}

//Completable 사용할 때는 subscribe해서 사용
Completable.zip(
    remoteDataSource.delete(project),
    localDataSource.delete(project)
).subscribe(onCompleted: { [self] in
    var currentProjects = projects.value
    if let row = currentProjects.firstIndex(where: { $0.id == project.id }) {
        currentProjects.remove(at: row)
    }
    projects.accept(currentProjects)
}).disposed(by: disposeBag)

local, remote datasource의 delete 메서드를 실행하는 곳(repo)에서 Completable.zip으로 둘을 묶어서 구독하여, 두 작업 모두가 끝난 후에 바로 모델 업데이트할 수 있게 구현한 예시이다.

2. Completable.zip

Completable들이 배열에 담긴: [Completable]을 하나의 Completable 형태로 바꿔주는 메서드 zip

프로젝트 적용 예시

1st. [Project] → [Completable]

  • intersectingRemoteProjects: [Project]
  • intersectingLocalProjects: [Project]

배열의 뎁스는 유지하면서 filter의 조건으로 localProject, remoteProject 둘다를 사용하고 싶어서

이렇게 flatMap과 filter을 겹쳐서 사용함

let sameIDCompletable: [Project] = intersectingRemoteProjects.flatMap { remoteProject in
        intersectingLocalProjects.filter { localProject in
            localProject.updatedAt > remoteProject.updatedAt
        }
}

여기서 .map 를 해서 배열 안의 Project 타입을 Completable로 바꾼 것임
self.remoteDataSource.update($0)의 리턴값이 Completable

let sameIDCompletable: [Completable] =
      intersectingRemoteProjects.flatMap { remoteProject in
          intersectingLocalProjects.filter { localProject in
              localProject.updatedAt > remoteProject.updatedAt
          }.map {
              return self.remoteDataSource.update($0)
          }
      }
  • 만약 flatMap이 아니라 map을 썼다면 [[Completable]] 타입이었을 것...
    let sameIDCompletable: [[Completable]] =
        intersectingRemoteProjects.map { remoteProject in
            intersectingLocalProjects.filter { localProject in
                localProject.updatedAt > remoteProject.updatedAt
            }.map {
                return self.remoteDataSource.update($0)
            }
        }

2nd. [Completable] → Completable
Completable.zip을 사용하여 최종적으로 [Completable] 타입에서 Completable 타입으로 변경해줌

let sameIDCompletable = Completable.zip(
    intersectingRemoteProjects.flatMap { remoteProject in
        intersectingLocalProjects.filter { localProject in
            localProject.updatedAt > remoteProject.updatedAt
        }.map {
            return self.remoteDataSource.update($0)
        }
    }
)

Reference

https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md

profile
제가 나중에 다시 보려고 기록합니다 ✏️

0개의 댓글