Delegate 패턴, Rx로 변경하기

Leo Kang·2022년 1월 17일
0

DocumentPickerController를 rxSwift를 이용하여 호출하고 사용을 해보려고 합니다.

DocumentPickerController는 아이폰 내부의 파일을 가져와서 업로드를 하거나, 단순히 보여주기 위해서 사용을 합니다.

// psudo code
let picker = DocumentPickerController()
self.present(picker, animated: true, completion: nil)

현재 뷰컨트롤러에서 이렇게 호출하면 됩니다.
다만 picker를 생성할때 어떤 확장자를 읽어올지 파라미터로 넘기면 됩니다.

if #available(iOS 14.0, *) {
   return UIDocumentPickerViewController(forOpeningContentTypes: getUTIs(with: Constants.compositeContentsList))
} else {
   return UIDocumentPickerViewController(documentTypes: [kUTTypePDF as String], in: .import)
}

UTI를 넘기는 방식으로 14부터는 변경이 되었으니 이런식으로 분기가 필요합니다.
우리가 변경하려고 하는 delegate는 아래와 같은데, 파일을 선택할때, 취소버튼을 눌렀을때 실행되는 딜리게이트 프로토콜을 Rx로 변경하는 것이 목표 입니다.

public protocol UIDocumentPickerDelegate : NSObjectProtocol {    
    @available(iOS 11.0, *)
    optional func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
    
    // called if the user dismisses the document picker without selecting a document (using the Cancel button)
    @available(iOS 8.0, *)
    optional func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)

    @available(iOS, introduced: 8.0, deprecated: 11.0)
    optional func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL)
}

딜리게이트를 사용하기 위해서는 크게 두가지가 필요합니다.
1. DelegateProxy 클래스
2. UIDocumentPickerViewControllerReactive extension



class RxUIDocumentPickerDelegateProxy : DelegateProxy<UIDocumentPickerViewController, UIDocumentPickerDelegate>,
                                        DelegateProxyType,
                                        UIDocumentPickerDelegate {
    
    static func registerKnownImplementations() {
        self.register { (pickerview) -> RxUIDocumentPickerDelegateProxy in
            RxUIDocumentPickerDelegateProxy(parentObject: pickerview, delegateProxy: self)
        }
    }
    
    static func currentDelegate(for object: UIDocumentPickerViewController) -> UIDocumentPickerDelegate? {
        return object.delegate
    }
    
    static func setCurrentDelegate(_ delegate: UIDocumentPickerDelegate?, to object: UIDocumentPickerViewController) {
        object.delegate = delegate
    }
    
}

extension Reactive where Base: UIDocumentPickerViewController {
    
    public var delegate: DelegateProxy<UIDocumentPickerViewController, UIDocumentPickerDelegate> {
        return RxUIDocumentPickerDelegateProxy.proxy(for: base)
    }
    
    public var didPickDocumentsAt: Observable<[URL]> {
        return delegate.methodInvoked(#selector(UIDocumentPickerDelegate.documentPicker(_:didPickDocumentsAt:)))
            .map { (values: [Any]) in
                values.last as! [URL]
            }
    }
    
    public var documentPickerWasCancelled: Observable<()> {
        return delegate.methodInvoked(#selector(UIDocumentPickerDelegate.documentPickerWasCancelled(_:)))
            .map {_ in () }
    }
}

delegate로 이벤트가 발생하면 프록시를 통해서 RX 이벤트로 바꿔서 전달한다는 컨셉입니다.
다시금 rx라는 이름이 왜 적절한가를 알게됩니다.


//did pick
picker.rx.didPickDocumentsAt
.asObservable()
.bind { url in
	print("url: \(String(describing: url.first))")
}.disposed(by: disposeBag)

//cancelled
picker.rx.documentPickerWasCancelled
.asObservable()
.bind { _ in
	print("cancelled. ")
}.disposed(by: disposeBag)

delegate를 별도로 채택할 필요없이, 호출한 부분에서 깔끔하게 사용이 가능합니다.

사용시 주의할점 두가지.

첫째,

picker.delegate = self

를 사용하지 않는다.

둘째,
기존의 UIDocumentPickerDelegate를 채택하여 사용하지 않는다.

-> 주의사항을 어기면, app crash가 발생합니다.

Legacy에 있는 Delegate들도 이렇게 변경하여 사용할 수 있겠습니다.
끝.

profile
iOS developer

0개의 댓글