관리 메뉴

김종권의 iOS 앱 개발 알아가기

[RxSwift] 6.Filtering Operators 실습 본문

RxSwift/RxSwift 기본

[RxSwift] 6.Filtering Operators 실습

jake-kim 2020. 5. 29. 16:27

filter 연산자를 연속적으로 사용

1. 구독을 공유해서 사용하는 방법 (Sharing subscriptions)

구독을 공유하는 주체 : Observable

 

1) 공유해서 사용하지 않은 경우의 문제점

여러 곳에서 subscribe()를 사용한다면 동기화에 문제 발생

 

<공유해서 사용할 함수와 변수 정의>

var start = 0
func getStartNumber() -> Int {
  start += 1
  return start
}

 

<Observable객체 생성>

let numbers = Observable<Int>.create { observer in
  let start = self.getStartNumber()
    observer.onNext(start)
    observer.onNext(start+1)
    observer.onNext(start+2)
    observer.onCompleted()
    return Disposables.create()
}

 

<subscribe>

numbers라는 Oservable객체가 heap영역에 할당된 변수(start)에 변형을 주는 함수에 두 번 접근하므로 값이 다르게 출력

numbers
  .subscribe(
    onNext: { el in
      print(el)
    },
    onCompleted: {
      print("Completed")
    }
  )
// print : 1, 2, 3, Completed

numbers
  .subscribe(
    onNext: { el in
      print(el)
    },
    onCompleted: {
      print("Completed")
    }
  )
// print : 2, 3, 4, Completed

※ 함수와 heap에 할당된 변수 그대로 유지하면서, subscribe시에 결과값을 변하지 않게 subscribe를 공유 하는 방법은?

  share() 연산자 사용하는 것이 가장 좋은 방법

 

2) share() 연산자 없이 filter를 통해서 값을 걸러내는 방법

- Observable이 각각 생성됨

 

3) share() 연산자 사용

- share()를 사용하면, Observable하나로 처리 가능

(8. Transforming Operators in Practice에서 구체적으로 다룸)

 

2. Ignoring certain elements

 

 

 

(좌측 네비게이션 아이콘에 추가하는 함수)

//MainViewController.swift

private func updateNavigationIcon() {
  let icon = imagePreview.image?
    .scaled(CGSize(width: 22, height: 22))
    .withRenderingMode(.alwaysOriginal)
    
  navigationItem.leftBarButtonItem = UIBarButtonItem(image: icon,
    style: .done, target: nil, action: nil)
}
// MainViewController.swift, viewDidLoad()
newPhotos
  .ignoreElements()
  .subscribe(onCompleted: { [weak self] in
    self?.updateNavigationIcon()
  })
  .disposed(by: bag)

2) 세로보다 너비가 큰 이미지만 표시하도록 하기

filtert사용

newPhotos
  .filter { newImage in
    return newImage.size.width > newImage.size.height
  }
  [existing code .subscribe(...)]

 

3) 사진이 6개 이상 표시될 경우 "+"버튼 비활성화

newPhotos
  .takeWhile { [weak self] image in
    let count = self?.images.value.count ?? 0
    return count < 6
  }
  [existing code: filter {...}]

3. 권한 설정 후 동작

권한설정을 하는 이벤트가 발생하면, 그 이후의 동작이 안되는 경우 존재

권한설정

<해결방법>

권한 설정이 안되어 있다면, requestAccess(...)를 호출하는 코드 넣기

DispatchQueue.main.async {
  if authorizationStatus() == .authorized {
    observer.onNext(true)
    observer.onCompleted()
  } else {
    observer.onNext(false)
    requestAuthorization { newStatus in
      observer.onNext(newStatus == .authorized)
      observer.onCompleted()
    }
  }
}

 

<true일 경우만 이미지 업데이트 하기>

- skipWhile로 true만 오게끔 설정

- 한번만 업데이트 하면 되므로 take(1)로 설정

authorized
  .skipWhile { $0 == false }
  .take(1)
  .subscribe(onNext: { [weak self] _ in
    self?.photos = PhotosViewController.loadPhotos()
    DispatchQueue.main.async {
      self?.collectionView?.reloadData()
    }
  })
  .disposed(by: bag)

cf) RxSwift에서는 GCD를 고려하지 않아도 됨 : Scheduling을 통해 해결(15장에서 계속)

 

4. 유저가 앨범 접근에 비동의 한 경우 처리

- 비동의 한 곳에 접근할 경우 error 메세지 출력

<이벤트 발생>

false가 두 번 발생하므로 하나 skip할 것

 

1) 무조건 이벤트가 2개이므로, skip(1), takeLast(1), distinctUntilChanged() 중 하나만 사용해도 됨

authorized
  .skip(1) // 한 개의 이벤트를 skip
  .takeLast(1) // 완료가 되면 1개 이전의 이벤트 발생
  .distinctUntilChanged() // 중복값 방지
  .filter { $0 == false } // false일 경우만 emit
  .subscribe(onNext: { [weak self] _ in
    guard let errorMessage = self?.errorMessage else { return }
    DispatchQueue.main.async(execute: errorMessage)
  })
  .disposed(by: bag)

 

2) UIAlertController을 Rx스타일로 만들기

<alert 함수정의>

- 핵심은 present와 dismiss를 Rx적으로 모두 한 함수에 작성

func alert(title: String, text: String?) -> Completable {
  return Completable.create { [weak self] completable in
    let alertVC = UIAlertController(title: title, message: text, preferredStyle: .alert)
    alertVC.addAction(UIAlertAction(title: "Close", style: .default, handler: {_ in
      completable(.completed)
    }))
    self?.present(alertVC, animated: true, completion: nil)
    return Disposables.create {
      self?.dismiss(animated: true, completion: nil)
    }
  }
}

// cf) Completable : 이벤트 중, error와 completed만 emit하는 것

<alert함수 이용>

- 버튼을 클릭했을 때, 접근제한에 동의하지 않았으므로 메세지를 띄우고 -> "OK"버튼 -> alert dismiss -> popViewController

// PhotosViewController.swift

private func errorMessage() {
  alert(title: "No access to Camera Roll", 
    text: "You can grant access to Combinestagram from the Settings app")
    .subscribe(onCompleted: { [weak self] in
      self?.dismiss(animated: true, completion: nil)
      _ = self?.navigationController?.popViewController(animated: true)
    })
    .disposed(by: bag)
}

5. 시간에 기반한 filter 연산자

"time-based operators" == Scheduler

(11.Time Based Operators에서 계속)

- 구현 : 접근 제한 비동의한 곳에 접근하면 아래와 같이 경고문이 뜨는 상태,

            이 상태에서 Close를 누르지 않아도 일정시간 종료하면 이전 화면으로 이동

접근 제한된 곳에 접근했을 때 뜨는 경고창

<코드>

- .asObservable() 추가 : Completable to Observable (Completable은 take연산자를 사용하지 못하므로)

- .take(초, 스케줄러) 추가

alert(title: "No access to Camera Roll",
      text: "You can grant access to Combinestagram from the Settings app")
  .asObservable()
  .take(5.0, scheduler: MainScheduler.instance)
  .subscribe(onCompleted: { [weak self] in
    self?.dismiss(animated: true, completion: nil)
    _ = self?.navigationController?.popViewController(animated: true)
  })
  .disposed(by: bag)

 

Comments