안녕하세요
오늘은 저번 글에 이어서 곰튀김님의 강의를 따라가며
RxSwift를 사용한 기본적인 코드와 함께 Observable의 개념에 다루겠습니다.
먼저 비슷한 동작을 하는 3개의 코드들부터 보시죠
아래의 세 코드는 모두 url을 파라미터로 받아서 async로 이미지를 가져오는 코드입니다.
1 - Async 비동기 처리 X
guard let url = URL(string: loadingImageUrl) else { return }
guard let data = try? Data(contentsOf: url) else { return }
let image = UIImage(data: data)
imageView.image = image
코드 자체에는 문제가 없지만 비동기 처리가 되어있지 않다면 실제로 어떻게 작동할까요?
왼쪽 사진의 동기 버튼을 눌러서 위 코드를 동작시키면 아래와 같이 동작합니다.
참고로 기본적으로 오른쪽의 숫자가 계속 증가하도록 했습니다.
그런데 동기 버튼을 누르는 순간 숫자가 멈춥니다!
그리고 사진이 나타난 후 다시 숫자가 증가합니다.
일반적으로 서버를 거치는 네트워킹 작업은 무거운 작업입니다.
비동기 처리가 되어있지 않기 때문에 순서대로 코드가 동작했는데요,
무거운 작업인 async 작업이 끝나고 -> 숫자를 증가시키는 함수가 다시 동작했습니다.
(이런 경우 Xcode에서는 친절하게 보라색맛 경고창도 띄워줍니다..)
상용앱에서 뭔가 눌렀는데 전체 화면이 멈추면 사용성은 둘째치고 대형 사고겠죠!
그래서 비동기 처리가 필요합니다.
2 - Async 비동기 처리 O
// async 방식
DispatchQueue.global().async {
guard let url = URL(string: loadingImageUrl) else { return }
guard let data = try? Data(contentsOf: url) else { return }
let image = UIImage(data: data)
DispatchQueue.main.async {
self.imageView.image = image
}
}
아까 코드 4줄에서 DispatchQueue.global().async와
DispatchQueue.main.async가 추가되었습니다.
자세하게 설명하면 많이 길어지니 최대한 간단하게 설명하자면
다음과 같습니다.
global - 백그라운드 큐에서 작업 (대부분의 경우)
main - 메인 큐에서 작업 (UI와 관련된 경우)
sync 동기 - (순서대로 작업 처리)
async - 비동기(순서를 기다리지 않고 작업 처리)
그러면 DispatchQueue.global().async로 백그라운드 큐에서 이미지를 불러온 후
UI와 관련된 작업을 DispatchQueue.main.async로 메인 큐에서 작업한 코드라고 이해가 될까요?
비동기적으로 작업이 진행되었기 때문에 무거운 작업인 async 작업이 끝날때까지 기다리지 않고
숫자가 멈추지 않고 계속 증가하는 모습을 볼 수 있습니다.
3 - RxSwift 방식으로 작동하는 코드와 함수
(동작 화면은 Async 비동기 처리와 같습니다)
imageView.image = nil
disposable = rxSwiftLoadImage(from: loadingImageUrl)
// DispatchQueue.main.async 처럼 메인에서 작업
.observe(on: MainScheduler.instance)
// rxSwiftLoadImage에서 반환된 observable을 구독하여 이미지 로드 프로세스의 결과를 처리.
.subscribe({ result in
switch result {
// 정상적으로 이미지를 반환한 경우
case let .next(image):
self.imageView.image = image
// 에러일 경우 에러처리
case let .error(err):
print(err.localizedDescription)
// 작업 완료 시 break
case .completed:
break
})
func rxSwiftLoadImage(from imageUrl: String) -> Observable<UIImage?> {
// 새로운 Observable<UIImage?>을 생성
return Observable.create { seal in
asyncLoadImage(from: imageUrl) { image in
// Observable이 구독자에게 새로운 데이터를 방출 => 구독자에게 next 이벤트가 전달 => 해당 함수 실행.
seal.onNext(image)
// 데이터 방출 종료를 알림
seal.onCompleted()
}
// 구독이 해제될 때 실행될 정리 작업을 작성하는 곳
return Disposables.create()
}
}
내용을 정리하자면 다음과 같습니다.
1. rxSwiftLoadImage() 함수가 Observable<UIImage?> 타입을 반환 (이벤트 스트림 생성)
2. disposable에서 rxSwiftLoadImage()의 이벤트 .subscribe로 스트림 구독 (이벤트가 발생하는지 지켜봄)
3. 이벤트가 발생했을 때 .subscribe에서 결과값을 받아와 처리 (이미지 변경 작업)
그림을 통해 개념을 조금 더 쉽게 설명하자면 이벤트 스트림은 그림처럼 쭉 이어지는 선이라고 생각하시면 편합니다.
(Observable<UIImage?>는 이후 이벤트가 발생할 때 UIImage? 타입을 반환함을 의미합니다.)
그리고 저 이벤트 스트림이라는 선을 지켜보는 것을 구독(Subscribe)한다고 표현합니다.
구독하면서 이벤트가 발생했을 때 할 일을 미리 정해둡니다. (이미지가 반환되면 화면에 적용할거야!)
이벤트 스트림에서 이벤트가 발생하면 구독자들에게 발생을 전하게 됩니다.
그리고 구독자들은 미리 정해둔 할 일을 하게 됩니다.
RxSwift가 용어와 실제 사용에서 뭔가 많이 헷갈리는거 같습니다. ㅜ