Skip to content

Instantly share code, notes, and snippets.

@audrl1010
Last active April 27, 2022 03:38
Show Gist options
  • Save audrl1010/2c7baaaf1940bb29a9c0a7d2f0ff01b1 to your computer and use it in GitHub Desktop.
Save audrl1010/2c7baaaf1940bb29a9c0a7d2f0ff01b1 to your computer and use it in GitHub Desktop.
RxSwift Observable이란 무엇일까?

Reactive Programming이란 도대체 무엇일까?

흔한 정의... 리액티브 프로그래밍비동기이벤트 기반의 데이터 스트리밍을 Observable Sequence로 변환하여 개발하는 프로그래밍 방식이다.

저게 무슨 말일까? 전혀 이해가 안갑니다.

또한, Reactive Programming를 처음 마주하게 되었을 때 자주 보는 단어들은 Observable, Observer, Schduler, Operator, 모든 것을 stream으로 본다... 등등 이다. 이건 또 뭘까? 하..

차근차근 알아봐요.

첫 시작은 Observable에 대한 개념부터 잡아보죠.

Observable

생각의 출발점을 먼저 기존의 프로그래밍 방식에서 시작해봐요.

유저의 디바이스에 있는 사진들을 가지고와서 사진들을 CollectionView에 보여주는 기능이 있는 사진 앱을 만든다고 생각해보세요.

첫 번째 해야 할일은 사진 리스트를 가져오는 함수를 만드는 것입니다.

class PhotoListViewController: UIViewController {
  var collectionView: UICollectionView = ...
  
  ...
  // 사진 리스트를 가져오는 함수
  func fetchPhotos() -> PHFetchResult<PHAsset>? {
    let collection = PHAssetCollection.fetchAssetCollections(
      with: .smartAlbum,
      subtype: .smartAlbumUserLibrary,
      options: nil
    ).firstObject

    if let collection = collection {
      let fetchResult = PHAsset.fetchAssets(in: collection, options: nil)
        return fetchResult
    } else {
      return nil
    }
  }
}

그리고 fetchPhotos()함수를 호출하여  사진들을 CollectionView에 보여주는 기능을 만들어야 합니다.

class PhotoListViewController: UIViewController {
  ...
  var fetchResult: PHFetchResult<PHAsset>?
  
  var cachingImageManager = PHCachingImageManager()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    ...
    
    fetchResult = fetchPhotos()
    
    collectionView.reloadData()
  }
}

extension PhotoListViewController: UICollectionViewDataSource {
  func collectionView(
    _ collectionView: UICollectionView,
    numberOfItemsInSection section: Int
  ) -> Int {
    if let fetchResult = fetchResult {
      return fetchResult.count
    } else {
      return 0
    }
  }

  func collectionView(
    _ collectionView: UICollectionView,
    cellForItemAt indexPath: IndexPath
  ) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(
      withReuseIdentifier: PhotoCell.cellIdentifier,
      for: indexPath
    ) as! PhotoCell
    
    if let fetchResult = fetchResult {
      let asset = fetchResult[indexPath.item]
      
      let options = PHImageRequestOptions()
      options.deliveryMode = .highQualityFormat
      options.isNetworkAccessAllowed = false
      
      // 해당 asset의 이미지를 읽어와서 cell의 imageView에 image를 넣습니다.
      cachingImageManager.requestImage(
        for: asset,
        targetSize: CGSize(width: 720, height: 1280),
        contentMode: .aspectFill,
        options: options,
        resultHandler: { image, info in
          if let image = image {
            DispatchQueue.main.async {
              cell.imageView.image = image
            }
          }
      })
    }
    return cell
  }
}

여기 까지 기존에 흔히 우리가 사용하는 방식으로 만들었습니다. 아래는 지금 까지의 흐름을 보여줍니다. Alt text

갑자기, 기획자가 '사진들의 총 개수를 네비게이션 바 왼쪽에 보여주는 기능을 만들어주세요.'라는 요구사항이 온다면 다음과 같이 fetchPhotos() 함수에 의해 반환된 fetchResult 값에 또 다시 의존되어 구현되어집니다.

class PhotoListViewController: UIViewController {
  ...
  override func viewDidLoad() {
    super.viewDidLoad()

    ...
    
    fetchResult = fetchPhotos()
    
    if let fetchResult = fetchResult {
      photosCountLabel.text = "\(fetchResult.count)"
    }
    
    collectionView.reloadData()
  }
}

Alt text

사진들을 CollectionView에 보여주는 기능사진의 총 개수를 보여주는 기능fetchPhotos() 함수에 의해 반환된 fetchResult 값에 의존하여 구현되어진다는 것을 알 수 있습니다.

이 처럼 여러분이 지금까지 프로그래밍을 할 때, 하나의 흐름(fetchPhotos 함수)에 의존하여 여러 갈래의 흐름(사진들을 collectionView에 보여주는 기능, 사진의 총 개수를 보여주는 기능)들을 구현하는 방식으로 개발해왔습니다.

지금까지는 요구사항이 많지 않아 손쉽게 구현할 수 있었습니다. 하지만 요구사항이 많아지게 되면 프로그래머들은 요구사항을 구현하기 위해 특정 흐름(로직)을 찾아 특정 흐름에 의존하여 여러 갈래의 흐름(요구사항)들을 개발해야 하지만 힘들다는 점입니다. 왜 힘들까요? 엄청나게 복잡한 소스에서 특정 흐름찾기란 대단히 어렵습니다. 위에 있는 그림을 통해서는 흐름이 어떤식으로 다음 흐름으로 이어지는지 알 수 있지만, 소스코드 상에서는 어디가 어디까지가 흐름인지 알기가 대단히 힘듭니다.

따라서 Reactive Programming이란 기존 프로그래밍 방식에서 어려웠던 흐름에서 흐름으로 자연스럽게 흐름 중심 사고로 개발하는 프로그래밍 방식입니다.

그렇타면 Reactive Programming에서는 어떻게 흐름흐름을 연결했을까요?

Reactive에서는 값을 내보내는 흐름Observable이라 표현하고 값을 내보내는 흐름에 구독하는(의존하는) 흐름Observer라고 표현합니다. 이 둘을 가지고 Observable을 Observer가 관찰하여 흐름을 이어가는 식으로 개발되어집니다.

이 개념을 지금까지 우리가 만들어놓은 것에 대입해보면 fetchPhotos()Observable가 되며, 사진들을 collectionView에 보여주는 기능, 사진의 총 개수를 보여주는 기능들은 Observer가 됩어집니다. 아래와 같이 표현할 수 있어요.

Alt text

자 이제 기존 소스를 RxSwift를 사용해서 바꿔 보겠습니다. 우선 fetchPhotos()함수를 Observable로 바꾸도록 하겠습니다.

먼저 Observable로 바꾸기 전에 Event라는 것을 알아야 합니다.

Reactive 세계에서는 Observable(생산자)가 방출하는 값을 Event라고 하며, Event의 종류는 3가지로 분류됩니다. next(정상값), error(에러), completed(흐름이 정상적으로 종료)

즉, fetchPhotos() 함수는 리턴할 수 있는 값은 사진 리스트 결과 객체nil입니다. 사진 리스트 결과 객체이 값은 정상적인 값을 의미하는 .next event로 분류되며, nil와 같은 값은 비정상적인 값을 의미하는 errorevent 로 분류되어집니다. 그리고 함수가 리턴하는 시점은 함수의 종료를 의미하는 completed event로 분류됩니다.

이제 정말로 바꿔보죠.

class PhotoListViewController: UIViewController {
  var collectionView: UICollectionView = ...

  ...
  
  enum PhotoListError: Error {
    case fetchError
  }
  
  func fetchPhotos() -> Observable<PHFetchResult<PHAsset>> {
    // 1
    return Observable.create { observer in
      let collection = PHAssetCollection.fetchAssetCollections(
        with: .smartAlbum,
        subtype: .smartAlbumUserLibrary,
        options: nil
      ).firstObject
  
      // 2
      if let collection = collection {
        let fetchResult = PHAsset.fetchAssets(in: collection, options: nil)
        observer.on(.next(fetchResult))
      } else {
        observer.on(.error(PhotoListError.fetchError))
      }
      observer.on(.completed)

      return Disposables.create()
    }
  }
}

하나씩 분석해 보면

  1. Observable을 생성하고 리턴하고 있습니다.
  2. observer에게 on()함수를 사용하여 event를 보내고 있습니다.

이렇게 바뀐 Observable을 사용해보겠습니다.

class PhotoListViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    
    ...
    let fetchPhotosObservable = fetchPhotos()
    
    // 사진들을 collectionView는 흐름
    fetchPhotosObservable
      .subscribe(onNext: { [weak self] fetchResult in
        guard let `self` = self else { return }
        self.fetchResult = fetchResult
        self.collectionView.reloadData()
      }, onError: { error in
        // ...
      }, onCompleted: {
        // ...
      })
     
    // 사진의 총 개수를 보여주는 기능
    fetchPhotosObservable
      .subscribe(onNext: { [weak self] fetchResult in
        guard let `self` = self else { return }
        self.photosCountLabel.text = "\(fetchResult.count)"
      })
  }
}

위 코드를 보면 fetchPhotos()함수에 의해 생성된 Observable(사진 리스트를 가져오는 흐름)을 통해 구독하여 나머지 흐름( 사진들을 collectionView에 보여주는 기능, 사진의 총 개수를 보여주는 기능)을 구현하고 있습니다.

지금은 저렇게 Reactive 프로그래밍 방식과 기존 프로그래밍 방식으로 하는것과 코드나 줄차이가 없는데 뭐가 좋은거지 라고 생각하실 수 있습니다. 만약에 엄청나게 많은 코드가 있고, 갑자기 요구사항을 마구마구 온다면, Reactive 프로그래밍 방식으로 짜져있는 코드는 흐름이 잘보이기 때문에 단지 특정흐름에 구독을 통해 다른 흐름을 손쉽게 만들 수 있습니다. 하지만 기존 프로그래밍 방식은 어떨까요? 어디가 어디까지 흐름인지 구분이 쉽게 되지 않아 계속해서 코드를 분석하는 시간이 길어지겠죠.

이 글의 목적은 Reactive 프로그래밍을 공부할 때, 첫 출발점은 Observable에 기반하여 나머지 Reactive 프로그래밍 개념들이 나왔기 때문에 그만큼 Observable은 Reactive 프로그래밍에서 제일 중요한 개념입니다. 이러한 개념을 잡는데 쉽제 않았기에 도움이 되는 마음에서 글을 써보았습니다. 부족하지만 읽어 주셔서 감사합니다. ~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment