티스토리 뷰

반응형

[1] Delegate Proxy 개념

Rxswift의 DelegateProxy.swiftDelegateProxyType.swift
이 두 파일은 delegate를 사용하는 프레임워크랑 
Rxswift의 다리역할을 해주는 파일이다 
(즉 delegate를 사용하는 친구들을 Rx에서도 편하게 활용할 수 있도록 연결해준다)

DelegateProxy Object는 fake delegate object를 만드는데, 이 fake delegate object은 수신된 모든 데이터를 전용 observables로 프록시한다(표현한다?!)




==> 설명은 너무 어렵다 모호하다.. 예제로 이해하자 

delegate를 가지고 있는 객체 중, MKMapView 를 살펴본다 


open class MKMapView : UIView, NSCoding {


    

    weak open var delegate: MKMapViewDelegate?



< Rx쓰기전 > 

import MapKit


class MapViewController: UIViewController {

    

    @IBOutlet weak var mapView: MKMapView!

   

    override func viewDidLoad() {

        super.viewDidLoad()

        mapView.delegate = self

    }

}


extension MapViewController: MKMapViewDelegate {

    func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {

        print(animated)

    }

}



Rxswift를 안쓴다면, 보통 이런식으로 delegate 안의 함수들을 활용한다

regionDidChangeAnimated 는 지도가 줌인/줌아웃 될때 animated가 true로 불리는 메소드이다 왼쪽/오른쪽/위/아래 같이 그냥 이동하는 것은 false가 불린다 



하지만 Rxswift를 써서 


class MapViewController: UIViewController {

    

    @IBOutlet weak var mapView: MKMapView!

    let disposeBag = DisposeBag()

    

    override func viewDidLoad() {

        super.viewDidLoad()

        bind()

    }

    

    func bind() {

        mapView.rx.regionDidChangeAnimated.asObservable()

        .subscribe(onNext: { (animated) in

            print(animated)

        })

        .disposed(by:disposeBag)

    }

}


이런식으로 해주고 싶은 것이당 

이렇게 rx. 해서 뒤에 뭐가 나오려면 



UIButton+Rx 파일 안에 있는


#if os(iOS)


import RxSwift

import UIKit


extension Reactive where Base: UIButton {

    

    /// Reactive wrapper for `TouchUpInside` control event.

    public var tap: ControlEvent<Void> {

        return controlEvent(.touchUpInside)

    }

}


#endif


이런 것 처럼 구현을 해줘야지 button.rx.tap 이런식으로 쓸 수 있는 것이다 



그래서 MKMapView를 Base로 한 Reactive extension을 해주어야한다 


extension Reactive where Base: MKMapView {

    var delegate : DelegateProxy<MKMapView, MKMapViewDelegate> {

        return RxMKMapViewDelegateProxy.proxy(for: self.base)

    }

    

    var regionDidChangeAnimated : Observable<Bool> {

        return delegate.methodInvoked(#selector(MKMapViewDelegate.mapView(_:regionDidChangeAnimated:)))

            .map({ (parameters) in

                return parameters[1] as? Bool ?? false

            })

    }

}



이 과정에서 바로바로 delegate Proxy가 필요한 것이다 !_! 


차근차근 만들어보자 


[2] Delegate Proxy 만들기 

⚠️필요한 부분만 복붙했음 ⚠️

Delegate Proxy는 이런식으로 생겼고 

    open class DelegateProxy<P: AnyObject, D>: _RXDelegateProxy {

        public typealias ParentObject = P

        public typealias Delegate = D


        /// Initializes new instance.

        ///

        /// - parameter parentObject: Optional parent object that owns `DelegateProxy` as associated object.

        public init<Proxy: DelegateProxyType>(parentObject: ParentObject, delegateProxy: Proxy.Type)

            where Proxy: DelegateProxy<ParentObject, Delegate>, Proxy.ParentObject == ParentObject, Proxy.Delegate == Delegate {

            self._parentObject = parentObject

            self._currentDelegateFor = delegateProxy._currentDelegate

            self._setCurrentDelegateTo = delegateProxy._setCurrentDelegate


            MainScheduler.ensureExecutingOnScheduler()

            #if TRACE_RESOURCES

                _ = Resources.incrementTotal()

            #endif

            super.init()

        }

}



이런식으로 생긴 DelegateProxyType의 메소들을 가지고 delegate를 설정해준다 



public protocol DelegateProxyType: class {

    associatedtype ParentObject: AnyObject

    associatedtype Delegate

}



extension DelegateProxyType {

    static func _currentDelegate(for object: ParentObject) -> AnyObject? {

        return currentDelegate(for: object).map { $0 as AnyObject }

    }

    

    static func _setCurrentDelegate(_ delegate: AnyObject?, to object: ParentObject) {

        return setCurrentDelegate(castOptionalOrFatalError(delegate), to: object)

    }




RxMKMapViewDelegateProxy 라는 클래스를 하나 만들고 parentObject = MKMapView / delegate = MKMapViewDelegate 로 세팅해준 DelegateProxy를 상속하고  DelegateProxyType과 MKMapViewDelegate 프로토콜을 채택하여준다 


class RxMKMapViewDelegateProxy: DelegateProxy<MKMapView, MKMapViewDelegate>, DelegateProxyType, MKMapViewDelegate {

    

}




그러면 DelegateProxyType이 이 세가지 메소드를 구현하라고 요구해준다 


class RxMKMapViewDelegateProxy: DelegateProxy<MKMapView, MKMapViewDelegate>, DelegateProxyType, MKMapViewDelegate {

    static func registerKnownImplementations() {

   

    }

    

    static func currentDelegate(for object: MKMapView) -> MKMapViewDelegate? {

     

    }

    

    static func setCurrentDelegate(_ delegate: MKMapViewDelegate?, to object: MKMapView) {


    }

}



다음과 같이 구현하여준다 


class RxMKMapViewDelegateProxy: DelegateProxy<MKMapView, MKMapViewDelegate>, DelegateProxyType, MKMapViewDelegate {


    static func registerKnownImplementations() {

        self.register { (mapView) -> RxMKMapViewDelegateProxy in

            RxMKMapViewDelegateProxy(parentObject: mapView, delegateProxy: self)

        }

    }



    static func currentDelegate(for object: MKMapView) -> MKMapViewDelegate? {

        return object.delegate

    }


    static func setCurrentDelegate(_ delegate: MKMapViewDelegate?, to object: MKMapView) {

        object.delegate = delegate

    }

}



그 다음 Reactive extension에서 fake delegate를 만들고 
Observable로 만들고 싶은 메소드들을 구현해준다


func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) 의 두번째 파라미터인 Bool을 가지고 Observalbe<Bool>을 만들어준다 


extension Reactive where Base: MKMapView {

    var delegate : DelegateProxy<MKMapView, MKMapViewDelegate> {

        return RxMKMapViewDelegateProxy.proxy(for: self.base)

    }


    var regionDidChangeAnimated : Observable<Bool> {

        return delegate.methodInvoked(#selector(MKMapViewDelegate.mapView(_:regionDidChangeAnimated:)))

            .map({ (parameters) in

                return parameters[1] as? Bool ?? false

            })

    }

}



그러면 이제 mapView.rx.뒤에 regionDidChageAnimated가 뜨게 되고 


        mapView.rx.regionDidChangeAnimated.asObservable()

        .subscribe(onNext: { (animated) in

            print(animated)

        })

        .disposed(by:disposeBag)



이런식으로 쓸 수 있게 되는 것이다 : ) 신기하고 재밌다 🙃


https://medium.com/@sudomax/rxswift-migrate-delegates-to-beautiful-observables-3e606a863048 참고했어요 


[3] 새로운 method 추가 

extension MapViewController: MKMapViewDelegate {

    func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {

        print(userLocation.coordinate)

    }

}


이런식으로 쓰는 didupdate 메소드를 Reactive Method에 추가한다


extension Reactive where Base: MKMapView {

    var delegate: DelegateProxy<MKMapView, MKMapViewDelegate> {

        return RxMKMapViewDelegateProxy.proxy(for: self.base)

    }

    

    var regionDidChangeAnimated: Observable<Bool> {

        return delegate.methodInvoked(#selector(MKMapViewDelegate.mapView(_:regionDidChangeAnimated:)))

            .map({ (parameters) in

                return parameters[1] as? Bool ?? false

            })

    }

    

    var didUpdate: Observable<CLLocationCoordinate2D> {

        return delegate.methodInvoked(#selector(MKMapViewDelegate.mapView(_:didUpdate:))).map({ (parameters) in

            return (parameters[1] as? MKUserLocation)?.coordinate ?? CLLocationCoordinate2D.init(latitude: 0, longitude: 0)

        })

    }

}



그리고 현재 위치를 보여주는 locationLabel을 추가하고

showuserlocation = true 해준다 (이거 해줘야지 메소드 불림)

추가한 메소드는 mapView.rx.didUpdate 로 사용할 수 있게 되고 

이 옵져버블을 locationLabel 에 바인딩 해준다 


class MapViewController: UIViewController {

    

    @IBOutlet weak var mapView: MKMapView!

    @IBOutlet weak var locationLabel: UILabel!

    let disposeBag = DisposeBag()

    

    override func viewDidLoad() {

        super.viewDidLoad()

        mapView.showsUserLocation = true

        bind()

    }

    

    func bind() {

        mapView.rx.regionDidChangeAnimated.asObservable()

            .subscribe(onNext: { (animated) in

                print(animated)

            })

            .disposed(by:disposeBag)

        

        mapView.rx.didUpdate.asObservable()

            .map({ (coordinate) in

                return "현재 위치 : \(coordinate)"

            })

            .bind(to: locationLabel.rx.text)

            .disposed(by: disposeBag)

    }

}



그러면 




현재 위치가 바뀔 때마다 location Label 이 바뀌는 것을 확인할수 있다 : ) 


반응형
댓글