Search code examples
swiftdelegatesrx-swiftfscalendar

error occurred when I using RxSwift's DelegateProxy with FSCalendar


RxFSCalendarDelegateProxy.swift

import Foundation
import RxSwift
import RxCocoa
import FSCalendar

class RxFSCalendarDelegateProxy: DelegateProxy<FSCalendar, FSCalendarDelegate>, DelegateProxyType, FSCalendarDelegate {
    static func registerKnownImplementations() {
        self.register { (calendar) -> RxFSCalendarDelegateProxy in
            RxFSCalendarDelegateProxy(parentObject: calendar, delegateProxy: self)
        }
    }
    
    static func currentDelegate(for object: FSCalendar) -> FSCalendarDelegate? {
        return object.delegate
    }
    
    static func setCurrentDelegate(_ delegate: FSCalendarDelegate?, to object: FSCalendar) {
        object.delegate = delegate
    }
}

extension Reactive where Base: FSCalendar {
    var delegate : DelegateProxy<FSCalendar, FSCalendarDelegate> {
        return RxFSCalendarDelegateProxy.proxy(for: self.base)
    }
    
    var didSelect : Observable<Date> {
        return delegate.methodInvoked(#selector(FSCalendarDelegate.calendar(_:didSelect:at:)))
            .map({ (params) in
                return params[1] as? Date ?? Date()
            })
    }
}

MainViewController.swift

import UIKit
import ReactorKit
import FSCalendar

class MainViewController: BaseViewController, View {
    
    typealias Reactor = MainViewReactor
    
    let calendar = FSCalendar()
    let label = UILabel()
    
    init(reactor: Reactor) {
        super.init()
        defer { self.reactor = reactor }
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func setupLayout() {
        self.view.addSubview(calendar)
    }
    
    override func makeConstraints() {
        
        self.calendar.snp.makeConstraints {
            $0.bottom.equalToSuperview()
            $0.top.equalToSuperview()
            $0.left.equalToSuperview()
            $0.right.equalToSuperview()
        }
    }
    
    func bind(reactor: MainViewReactor) {
        // MARK: input
        calendar.rx.didSelect.asObservable()
            .map { Reactor.Action.setDay($0) }
            .bind(to: reactor.action)
            .disposed(by: disposeBag)
        // MARK: output
    }

}

I am trying to change FSCalendarDelegate func to Observable to use ReactorKit. But When I run this code, error occurred "RxCocoa/DelegateProxy.swift:230: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value" I don't know how to fix my DelegateProxy. why aSelector is nil?

enter image description here


Solution

  • You have discovered a bug. The code is crashing because FSCalendar is asking the delegate if it responds to a selector but the selector is nil. Neither FSCalendar nor RxCocoa is checking the selector for nil.

    I have submitted a pull request to the RxSwift repository to correct this problem.

    While waiting for the pull request to go through the system, you can sub-class DelegateProxy and override the offending method:

    class MyDelegateProxy<P: AnyObject, D>: DelegateProxy<P, D> {
        override open func responds(to aSelector: Selector!) -> Bool {
            guard aSelector != nil else { return false }
            return super.responds(to: aSelector)
        }
    }
    
    class RxFSCalendarDelegateProxy: MyDelegateProxy<FSCalendar, FSCalendarDelegate>, DelegateProxyType, FSCalendarDelegate {
        static func registerKnownImplementations() {
            self.register { (calendar) -> RxFSCalendarDelegateProxy in
                RxFSCalendarDelegateProxy(parentObject: calendar, delegateProxy: self)
            }
        }
    
        static func currentDelegate(for object: FSCalendar) -> FSCalendarDelegate? {
            return object.delegate
        }
    
        static func setCurrentDelegate(_ delegate: FSCalendarDelegate?, to object: FSCalendar) {
            object.delegate = delegate
        }
    }
    

    As to why the aSelector is nil... When FSCalendar wants to use a delegate method, it first checks to see if the method has been implemented. If not, then it checks to see if a deprecated version of the method is implemented. If there is no deprecated version, then it will ask the delegate if the nil method is implemented. It feels kind of silly to me for FSCalender to do this, but apparently if this is done on a traditional delegate implementation, the OS responds with false. RxCocoa just assumes that no library would ever do this.