Notice
Recent Posts
Recent Comments
Link
관리 메뉴

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

[iOS - swift] Observable로 start, cancel할 수 있는 task 구현 (DispatchWorkItem, Operation 기능) 본문

iOS 응용 (swift)

[iOS - swift] Observable로 start, cancel할 수 있는 task 구현 (DispatchWorkItem, Operation 기능)

jake-kim 2022. 2. 10. 23:55

Observable로 구현된 start와 cancel 기능

사용한 framework

pod 'RxSwift'
pod 'RxCocoa'

시작, 취소 기능을 Observable로 구현

  • DispatchWorkItem과 Operation을 사용하여 task들을 관리할 수 있지만 Observable를 사용하면 더욱 단순화
    * Operation 포스팅 글 참고
    * DispatchWorkItem 포스팅 글 참고
  • Observable을 사용하면 다양한 Rx 연산자 사용이 가능하여 다른 기능을 붙이기에 용이

시작, 취소 기능 구현 아이디어

  • 시작 기능
    • 시작 버튼을 탭하면 task를 시작하고, 만약 기존에 task가 실행중이면 내부적으로 취소한 후 실행
    • Observable로 task를 관리하면, disposeBag = DisposeBag()과 같이 초기화하여 기존 작업을 손쉽게 취소 가능
  • 취소 기능
    • 작업중인 스트림을 초기화 disposeBag = DisposeBag()

구현

  • 사용하는 쪽에서는 startTask(), cancelTask()로 사용
    import UIKit
    
    class ViewController: UIViewController {
      @IBOutlet weak var startButton: UIButton!
      @IBOutlet weak var cancelButton: UIButton!
      override func viewDidLoad() {
        super.viewDidLoad()
      }
      @IBAction func didTapStartButton(_ sender: Any) {
        MyPopup.startTask()
      }
      @IBAction func didTapCancelButton(_ sender: Any) {
        MyPopup.cancelTask()
      }
    }​
     
  • protocol 정의
    protocol MyPopupType {
      static func startTask()
      static func cancelTask()
    }​
  • 구현에 앞서서, window의 맨 위에 팝업을 띄워야 하므로, topViewController에 접근하는 코드를 UIApplication+Extension에 추가
    // UIApplication+Extension.swift
    
    import UIKit
    
    // https://stackoverflow.com/questions/36284476/top-most-viewcontroller-under-uialertcontroller
    extension UIApplication {
      var rootViewController: UIViewController? {
        self.windows.compactMap(\.rootViewController).first
      }
      class var topViewController: UIViewController? {
        UIApplication.shared.rootViewController
          .flatMap(self.topViewController(base:))
      }
      private class func topViewController(base: UIViewController?) -> UIViewController? {
        if let nav = base as? UINavigationController {
          return topViewController(base: nav.visibleViewController)
        }
        if let tab = base as? UITabBarController {
          if let selected = tab.selectedViewController {
            return topViewController(base: selected)
          }
        }
        if let page = base as? UIPageViewController,
           page.viewControllers?.count == 1 {
          return topViewController(base: page.viewControllers?.first)
        }
        if let alert = base as? UIAlertController {
          if let presenting = alert.presentingViewController {
            return topViewController(base: presenting)
          }
        }
        if let presented = base?.presentedViewController {
          return topViewController(base: presented)
        }
        for subview in base?.view.subviews ?? [] {
          if let childVC = subview.next as? UIViewController {
            return topViewController(base: childVC)
          }
        }
        return base
      }
    }​
  • MyPopup 구현 준비
    class MyPopup: MyPopupType {
      static private var disposeBag = DisposeBag()
      static private var countDown = 3
      
      static func startTask() {
      
      }
      
      static func cancelTask() {
      
      }
    }​
  • cancelTask() 구현
    static func cancelTask() {
      Self.disposeBag = DisposeBag()
    }​
  • startTask() 구현
    • 기존 작업 먼저 취소
      static func startTask() {
        Self.cancelTask()​
        
        // 이어서 구현
      }
    • 카운트 다운이 들어갈 label 생성
    • let sampleLabel = UILabel()
      sampleLabel.backgroundColor = .systemOrange
      sampleLabel.textColor = .white
      sampleLabel.font = .systemFont(ofSize: 32)
      sampleLabel.textAlignment = .center
      
      guard let superview = UIApplication.topViewController?.view else { return }
      superview.addSubview(sampleLabel)
      
      sampleLabel.translatesAutoresizingMaskIntoConstraints = false
      sampleLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
      sampleLabel.heightAnchor.constraint(equalToConstant: 300).isActive = true
      sampleLabel.centerYAnchor.constraint(equalTo: superview.centerYAnchor).isActive = true
      sampleLabel.centerXAnchor.constraint(equalTo: superview.centerXAnchor).isActive = true
    • Task 정의: Observable<Void>.create로 정의하고 작업이 끝난 경우, observer.onCompleted()를 하고, Disposables.create에서는 removeFromSuperview()를 사용하여 해제하도록 구현
        Observable<Void>
          .create { [weak sampleLabel] observer in
            Observable<Int>.timer(.seconds(0), period: .seconds(1), scheduler: MainScheduler.instance)
              .take(Self.countDown + 1)
              .subscribe(onNext: { timePassed in
                let count = Self.countDown - timePassed
                sampleLabel?.text = "\(count)"
              }, onCompleted: {
                sampleLabel?.removeFromSuperview()
                observer.onCompleted()
              })
              .disposed(by: Self.disposeBag)
            
            return Disposables.create { sampleLabel?.removeFromSuperview() }
          }
          .subscribe()
          .disposed(by: Self.disposeBag)
        } // end, startTask()

 

* 전체 코드: https://github.com/JK0369/ExObservable

* 참고

https://stackoverflow.com/questions/36284476/top-most-viewcontroller-under-uialertcontroller

 

Comments