델리게이트 패턴
애플에서 제공하는 프레임워크는 다양한 곳에서 델리게이트 패턴을 사용합니다. DelegatePattern은 다양한 문제를 해결하고 알려진 이점이 있지만 RxSwift에는 적합하지 않습니다.
RxSwift와 함께 사용하는 데 전혀 문제가 없습니다. 위임 프록시 또는 위임 패턴 사용 여부는 선택 사항입니다.
대리인 대리인
Binder, ControlProperty, ControlEvent를 이용하여 RxSwift 방식으로 많은 부분을 확장할 수 있습니다. 그러나 모든 부분이 확장 가능한 것은 아닙니다. 예를 들어 위치 기반 코드를 구현할 때 CLLocationManager를 사용합니다. CLLocationManagerDelegate를 구현하고 위치 정보를 전달할 때 필요한 코드를 구현합니다. Binder, ControlProperty 및 ControlEvent를 사용하면 이러한 코드를 RxSwift 방식으로 확장할 수 있습니다.
DelegateProxy는 Delegate만 처리할 수 있다는 의미가 아닙니다. 동일한 패턴을 사용하여 데이터 소스를 확장할 수도 있습니다.
이때 필요한 것이 Delegate Proxy입니다.
대신 대리자를 처리하는 개체입니다.
특정 델리게이트 메소드가 호출되면 NE가 구독자로 전달됩니다.

대리자 메서드를 호출하는 개체와 구독자 사이에 위치합니다.
CLLocationManager를 확장하는 DelegateProxy는 CLLocaionManager와 구독자 사이에 위치합니다.
Delegate Proxy는 상대적으로 구현하기 어렵습니다. 하지만 구현 패턴에 익숙해지면 거의 모든 부분을 RxSwift 방식으로 확장할 수 있습니다.
처음에는 조금 어려울 수 있습니다.
대리자 프록시 구현

MapView를 연결하고 콘센트로 연결했습니다. 위치가 업데이트되면 현재 위치가 지도 중앙에 업데이트됩니다.
DelegateProxy를 구현할 때 일반적으로 클래스와 두 개의 확장을 구현합니다.
1. 확장 대상에 HasDelegate 프로토콜 구현을 추가합니다.
확장 대상과 연결된 대리자 프로토콜로 Delegate라는 관련 유형을 설정하기만 하면 됩니다.
CLLocationManager와 관련된 프로토콜은 CLLocationManagerDelegate입니다.
2.
DelegateProxy 구현, 클래스 이름은 일반적으로 Rx + 대상 + DelegateProxy이며 DelegateProxy를 상속해야 합니다.
DelegateProxy는 제네릭 클래스이며 확장할 클래스와 연결된 대리자 프로토콜이라는 두 가지 공식 매개 변수를 선언해야 합니다.
그런 다음 DelegateProxyType 프로토콜을 사용해야 하며 마지막으로 연결된 대리자 프로토콜을 사용해야 합니다.
2-1
public protocol DelegateProxyType: AnyObject {
associatedtype ParentObject: AnyObject
associatedtype Delegate
/// It is require that enumerate call `register` of the extended DelegateProxy subclasses here.
static func registerKnownImplementations()
DelegateProxyType 프로토콜은 클래스 프로토콜로 선언되며 총 6개의 필수 멤버가 있습니다.
첫 번째 방법을 제외하고 기본 구현은 ProtocolExtention에서 제공합니다.
따라서 특별한 이유가 없는 한 첫 번째 방법을 구현하는 것으로 충분합니다.
2-2
//HasDelegate
public protocol HasDelegate: AnyObject {
/// Delegate type
associatedtype Delegate
/// Delegate
var delegate: Delegate? { get set }
}
//HasDelegate를 채용한 경우의 기본 구현을 제공
extension DelegateProxyType where ParentObject: HasDelegate, Self.Delegate == ParentObject.Delegate {
public static func currentDelegate(for object: ParentObject) -> Delegate? {
object.delegate
}
public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
object.delegate = delegate
}
}
전체
import UIKit
import CoreLocation
import RxSwift
import RxCocoa
import MapKit
class DelegateProxyViewController: UIViewController {
let bag = DisposeBag()
@IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
//8. DelegateProxy가 잘 작동하는지 확인
//9. 사용자로부터 허가를 요청함
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
//10. didupdatelocation속성에 구독자를 추가하고 전달된 위치정보 출력
locationManager.rx.didUpdateLocation
.subscribe(onNext: { locations in
print(locations)
//결과) (<+37.78583400,-122.40641700> +/- 5.00m (speed -1.00 mps / course -1.00)
// @ 2/24/23 오후 4:36:03 대한민국 표준시)
})
.disposed(by: bag)
//11. didupdatelocation속성이 방출하는 첫 번째 좌표를 center속성에 binding
locationManager.rx.didUpdateLocation
.map{$0(0)}
.bind(to: mapView.rx.center)
.disposed(by: bag)
}
}
//0. center는 Binder로 선언되어있고 전달된 CLLocation에 포함된 좌표를 지도 중앙에 표시하도록 구현.
extension Reactive where Base: MKMapView {
public var center: Binder<CLLocation> {
return Binder(self.base) { mapView, location in
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 10000, longitudinalMeters: 10000)
self.base.setRegion(region, animated: true)
}
}
}
//1. 확장 대상에 HasDelegate Protocol 구현을 추가한다.
extension CLLocationManager: HasDelegate{
//Delegate라는 연관 형식을 확장 대상과 연결된 Delegate Protocol로 지정해주면 끝난다.
//CLLocationManager과 연관된 프로토콜은 CLLocationManagerDelegate이다.
public typealias Delegate = CLLocationManagerDelegate
}
//2.
//DelegateProxy구현, 클래스명은 보통 Rx + 대상 + DelegateProxy, DelegateProxy를 상속해야함
//DelegateProxy는 제네릭클래스이고 형식파라미터 두개를 선언해야한다.(확장할클래스, 연관된Delegate프로토콜
//다음 DelegateProxyType프로토콜을 채용하고 마지막으로 연관된 델리게이트 프로토콜을 채용해야한다.
class RxCLLocationManagerDelegateProxy: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>, DelegateProxyType, CLLocationManagerDelegate{
//3. Proxy클래스에 새로운 속성(확장대상)을 추가
//클래스 내부에서 확장 대상에 접근해야한다면 약한 참조로 확장대상을 저장해야 참조사이클문제가 발생하지 않는다.
//클래스 내부에서 확장 대상에 접근하지 않아도 된다면 속성을 선언하지 않아도 된다.
weak private(set) var locationManager: CLLocationManager?
//4. 생성자 구현
init(locationManager: CLLocationManager){
//생성자는 보통 확장대상을 파라미터로 받는다
self.locationManager = locationManager
//슈퍼클래스에 생성자를 호출하고 확장 대상과 델리게이트 프록시 타입을 전달한다.
super.init(parentObject: locationManager, delegateProxy: RxCLLocationManagerDelegateProxy.self)
}
//5. DelegateProxyType이 요구하는 필수 멤버를 구현
static func registerKnownImplementations() {
//레지스터 메서드를 호출하고 클로저에서 델리게이트 프록시의 인스턴스를 리턴해야한다.
//이 메서드는 필요한 시점에 자동으로 호출된다.
//RxCocoa는 내부적으로 ProxyFactory를 가지고 있는데 구현한 Proxy가 팩토리에 자동으로 등록된다.
self.register{
RxCLLocationManagerDelegateProxy(locationManager: $0)
}
}
}
//6. Reactive Extension 구현
extension Reactive where Base: CLLocationManager{
//7. 델리게이트속성 추가, 속성의형식은 2. 에서 구현한 Proxy클래스에 슈퍼클래스로 선언해야함.
var delegate: DelegateProxy<CLLocationManager, CLLocationManagerDelegate>{
//DelegateProxy인스턴스를 리턴해야하는데 한 가지 주의할 점이 있다.
// 4에서 구현했던 생성자를 사용하면 새로운 인스턴스를 생성할 수 있다. 그럼 내부적으로 여러가지 문제가 발생한다.
//문제1 인스턴스가 두개이상 생성되는 문제 이렇게되면 델리게이트프록시가 예상과 다르게 동작
//그래서 생성자 대신 proxy(for:) 메서드 사용 -> 프록시팩토리가 프록시생성을 담당하게되고 이미 생성된
//인스턴스가있다면 새로 생성하지 않고 기존 인스턴스를 리턴
return RxCLLocationManagerDelegateProxy.proxy(for: base)
}
//8. 델리게이트에서 didupdateLocation메서드가 호출되면 옵저버블을통해 새로운 위치정보를 방출하도록 구현
//속성 이름은 연관된 델리게이트메서드와 동일하게 선언하거나 특정 부분츨 채용한 형태로
var didUpdateLocation: Observable<(CLLocation)>{
//옵저버블은 직접 만들지 않고 methonInvoked메서드를 사용한다
//didUpdateLocation메서드의 selector를 생성하고 파라미터로 전달해야한다.
//이메서드가 리턴하는 옵저버블은 셀렉터로 전달한 메서드가 호출되는 시점에 NE를 방출 NE에는 델리게이트메서드로 전달된 파라미터가 (Any)로 저장되어있다. didUpdateLocation메서드에는 두개의 파라미터가 전달된다. 첫 번째는 LocationManager, 두 번쨰는 CLLocation배열이 전달. NE도 두개의요소가 저장되어있다. map연산자로 두번째 파라미터만 방출
return delegate.methodInvoked(#selector(CLLocationManagerDelegate.locationManager(_:didUpdateLocations:)))
.map{ parameters in
return parameters(1) as! (CLLocation)
}
}
}