FlatMap?

프로젝트내에 이미지 업로드 로직을 RxSwift로 교체하는 과정에서 flatMap연산자를 활용 하게 되어
한번 내용을 정리해 보려고 한다

What is flatMap

flatMap

  • 새로운 시퀀스를 반환해야할때 사용하는 연산자이다
  • flatMap은 블록 내에서 Observable을 리턴해야 한다
  • flatMap 안에서 API를 호출하여 응답값을 Observable로 리턴하여 사용한다
  • 스트림 안에서 throw가 하나라도 방출되면, 해당 스트림은 dispose되므로
    사용자가 계속 재시도 할 수 있는 이벤트 처리에는 부적합하다

example Code

1. 이미지를 업로드할 s3 url 호출 함수


private let mediaProvider = APIService.shared.mediaAPI // media관련 network MoyaProvider

private func getPath(_ type: MediaType) -> Observable<MediaResponse> {
    let path =  mediaProvider.rx
        .request(.getMediaPath(type: type)) // getMediaPath를 통해 주소를 받음
        .filterSuccessfulStatusCodes()
        .map(MediaResponse.self) // path, url 로 나뉘어진 model
    return path.asObservable()
}

2. 이미지리스트를 받아 업로드 로직을 실행할 함수 생성

func uploadImages(_ images: [UIImage]) -> Observable<(Int, String?)> {
        
    return Observable.from(images)
        .flatMap { image -> Observable<(UIImage, MediaResponse)> in
            Observable.zip(Observable.just(image), self.getPath(.image))
             // zip을 통해 image와 mediaResponse를 하나의 observable로 리턴시켜줌
        }
        .flatMap { image, mediaResponse -> Observable<(Moya.Response, String)> in
            let uploadURL = URL(string: mediaResponse.url)!
            let uploadResponse = self.mediaProvider.rx.request(.uploadImage(image, url: uploadURL))
            // mediaProvider를 통해 uploadResponse를 single형태로 리턴받는다
            return Observable.zip(
                uploadResponse.asObservable(), // single을 observable로 변환시켜줌
                Observable.just(mediaResponse.path)
            )
        }
        .enumerated() // index를 반환시키기 위한 연산자
        .map { index, response -> (Int, String?) in
            guard response.0.response?.statusCode ?? 500 == 200 else {
                return (index, nil) // 오류처리를 위한 nil 반환
            }
            return (index, response.1)
        }
    }

3. 이미지 Array를 파라미터로 전달할 업로드 함수 생성

private var imagePathList: [String] = [] //생성된 imagePath값을 저장할 Array

func upload(_ images: [UIImage]) {
    MediaUploadManager().uploadImages(images).subscribe(onNext: { [weak self] index, path in
        guard let self = self else { return }
        guard let path = path else { // 네트워크 에러시 path값이 방출되지 않으므로 오류처리 
            print("uploadImage_error: errorIndex = \(index)")
            self.imagePathList.removeAll()
            return
        }
            
        self.imagePathList.append(path)  //시퀀스가 진행됨에 따라 image의 path값을 저장해준다
        if index == images.count - 1 { 
            // index와 업로드할 이미지갯수 - 1 이 같아지면 피드에 이미지 path어레이를 담아 업로드한다
            self.uploadFeed(imagePathList: self.imagePathList) // 피드 업로드 로직 실행
        }
    })
    .disposed(by: disposeBag)
}

4. 업로드 로직 호출하여 사용

private var selectedImages: [UIImage] = [] // 선택한 이미지가 담길 Array

@objc func didTapDoneButton(_ sender: UIButton) {
    upload(selectedImages)
}