저희 앱을 NRC등 다른 앱과 차별화시키는 기능이 바로 친구와 같이 달리는 기능인데요, 이 기능을 구현하려면 실시간으로 사용자와 그 친구가 서로 달린 거리를 주고 받을 수 있도록 해야 합니다. 프로젝트 초반에는 저희가 기획한 내용이 얼마나 많은지 몰라서 제가 소켓을 이용해서 서버를 짜겠다고 했는데, 일정을 계획하다보니 5주동안 앱을 구현하면서 서버까지 구현하는 것은 어렵겠다는 판단이 들었습니다.
서버를 따로 구현하지 않으면서 저희가 원하는 기능을 구현해내기 위해 값 이벤트를 수신 대기하여 데이터를 읽을 수 있는 기능을 제공하는 Firebase를 사용하기로 했습니다. 사용하기로 정한 RxSwift와의 조합도 괜찮을 것이라는 판단이 들었습니다.
공식 문서를 읽어보면, 다음과 같이 값 이벤트를 수신 대기하여 데이터를 읽을 수 있다고 되어 있습니다.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
// ...
})
저희는 이를 RxSwift & RxRelay와 함께 사용하여, RealtimeDatabaseNetworkService
에 다음과 같은 메서드를 구현했습니다. self.ref.child("users").child(user.uid)
와 같은 방식으로 childReference에 접근해야 하기 때문에 childReference(of path: [String])
메서드를 구현하여, path의 문자열을 배열로 받아 childReference에 접근할 수 있도록 했습니다. 이렇게 하지 않으면 self.ref.child().child().child().child()...
와 같은 방식으로 childReference에 접근해야 하기 때문에..!
typealias FirebaseDictionary = [String: Any]
private func childReference(of path: [String]) -> DatabaseReference {
var childReference = self.databaseReference
for path in path {
childReference = childReference.child(path)
}
return childReference
}
func listen(path: [String]) -> Observable<FirebaseDictionary> {
let childReference = self.childReference(of: path)
return BehaviorRelay<FirebaseDictionary>.create { observer in
childReference.observe(DataEventType.value, with: { snapshot in
guard let data = snapshot.value as? [String: Any] else {
observer.onError(FirebaseServiceError.nilDataError)
return
}
observer.onNext(data)
})
return Disposables.create()
}
}
위의 메서드는 RunningRepository의 **func**listen(sessionId: String, mate: String) -> Observable<RunningRealTimeData>
에서 호출되고, 이 메서드는 RunningUseCase의 아래 메서드에서 호출됩니다. **func**listen(sessionId: String, mate: String) -> Observable<RunningRealTimeData>
는 Firebase Realtime Database의 특정 데이터에 변경사항이 생길 때마다 그를 감지하여 그 값을 Observable<RunningRealTimeData>
로 반환합니다. RunningUseCase에서는 이 반환값을 구독하여, 값이 반환될 때마다 이 데이터를 가공하여 해야 할 작업을 할 수 있습니다.
func listenRunningSession() {
guard let sessionId = self.runningSetting.sessionId,
let mateNickname = self.runningSetting.mateNickname else { return }
self.listenIsCanceled(of: sessionId)
self.listenMateRunningData(of: mateNickname, in: sessionId)
}
private func listenMateRunningData(of mate: String, in sessionId: String) {
self.runningRepository.listen(sessionId: sessionId, mate: mate)
.subscribe(onNext: { [weak self] mateRunningRealTimeData in
// ...
})
.disposed(by: self.disposeBag)
}
위의 메서드는, 뷰모델에서 viewDidLoadEvent 이벤트가 input으로 들어오면 호출되므로 바로 친구의 상태를 옵저빙하게 됩니다.
input.viewDidLoadEvent
.subscribe(onNext: { [weak self] in
// ...
self?.runningUseCase.listenRunningSession()
self?.runningUseCase.updateRunningStatus()
})
.disposed(by: disposeBag)