Search code examples
iosswiftsprite-kitrx-swiftrx-cocoa

Implementing `DelegateProxy` (RxSwift/RxCocoa) when delegate field is not called `delegate`


I need some help with DelegateProxy implementation. Specifically, what is the proper way of implementing it when the delegate field has different name than simply delegate? Such as in SKPhysicsContactDelegate it is called contactDelegate. I tried defining a computed value delegate, but it did not do the trick - https://github.com/maxvol/RxSpriteKit/blob/master/Proxy/RxSKPhysicsContactDelegateProxy.swift

It fails with "DelegateProxy has no factory of <PKPhysicsWorld: 0x280b18990>. Implement DelegateProxy subclass for <PKPhysicsWorld: 0x280b18990> first." Perhaps it is not even related to the delegate field name, but this is the only difference with properly working proxies I could find.

UPDATE: Ha! I just noticed that error message says PKPhysicsWorld, not SKPhysicsWorld. So my hypothesis is that it has something to do with the fact that object in DelegateProxyFactory.createProxy is a PKPhysicsWorld instead of SKPhysicsWorld and _factories[ObjectIdentifier(mirror.subjectType)] returns nil.


Solution

  • The reason you are getting that error is because your registerKnownImplementations() function isn't getting run.

    The following gist should work: https://gist.github.com/dtartaglia/9f1f937628504ca56dbb1aac7d91df2b

    The code is also below but the gist can be kept up to date:

    //
    //  SKPhysicsWorld+Rx.swift
    //
    //  Created by Daniel Tartaglia on 21 Jan 2019.
    //  Copyright © 2019 Daniel Tartaglia. MIT License.
    //
    
    import RxSwift
    import SpriteKit
    
    public
    extension Reactive where Base: SKPhysicsWorld {
    
        var didBegin: Observable<SKPhysicsContact> {
            return Observable.create { observer in
                physicsContatctDelegatesLock.lock(); defer { physicsContatctDelegatesLock.unlock() }
                let uuid = UUID()
                if let (delegate, beginners, enders) = physicsContatctDelegates[self.base] {
                    var new = beginners
                    new[uuid] = observer
                    physicsContatctDelegates[self.base] = (delegate, new, enders)
                }
                else {
                    let delegate = PhysicsContactDelegate(for: self.base)
                    self.base.contactDelegate = delegate
                    physicsContatctDelegates[self.base] = (delegate, [uuid: observer], [:])
                }
    
                return Disposables.create {
                    physicsContatctDelegatesLock.lock(); defer { physicsContatctDelegatesLock.unlock() }
                    let (delegate, beginners, enders) = physicsContatctDelegates[self.base]!
                    var new = beginners
                    new.removeValue(forKey: uuid)
                    if new.isEmpty && enders.isEmpty {
                        physicsContatctDelegates.removeValue(forKey: self.base)
                    }
                    else {
                        physicsContatctDelegates[self.base] = (delegate, new, enders)
                    }
                }
            }
        }
    
        var didEnd: Observable<SKPhysicsContact> {
            return Observable.create { observer in
                physicsContatctDelegatesLock.lock(); defer { physicsContatctDelegatesLock.unlock() }
                let uuid = UUID()
                if let (delegate, beginners, enders) = physicsContatctDelegates[self.base] {
                    var new = enders
                    new[uuid] = observer
                    physicsContatctDelegates[self.base] = (delegate, beginners, new)
                }
                else {
                    let delegate = PhysicsContactDelegate(for: self.base)
                    self.base.contactDelegate = delegate
                    physicsContatctDelegates[self.base] = (delegate, [:], [uuid: observer])
                }
    
                return Disposables.create {
                    physicsContatctDelegatesLock.lock(); defer { physicsContatctDelegatesLock.unlock() }
                    let (delegate, beginners, enders) = physicsContatctDelegates[self.base]!
                    var new = enders
                    new.removeValue(forKey: uuid)
                    if new.isEmpty && enders.isEmpty {
                        physicsContatctDelegates.removeValue(forKey: self.base)
                    }
                    else {
                        physicsContatctDelegates[self.base] = (delegate, beginners, new)
                    }
                }
            }
        }
    }
    
    private
    class PhysicsContactDelegate: NSObject, SKPhysicsContactDelegate {
    
        init(for world: SKPhysicsWorld) {
            self.world = world
            super.init()
        }
    
        func didBegin(_ contact: SKPhysicsContact) {
            physicsContatctDelegatesLock.lock(); defer { physicsContatctDelegatesLock.unlock() }
            let (_, beginners, _) = physicsContatctDelegates[world]!
            for each in beginners.values {
                each.onNext(contact)
            }
        }
    
        func didEnd(_ contact: SKPhysicsContact) {
            physicsContatctDelegatesLock.lock(); defer { physicsContatctDelegatesLock.unlock() }
            let (_, _, enders) = physicsContatctDelegates[world]!
            for each in enders.values {
                each.onNext(contact)
            }
        }
    
        let world: SKPhysicsWorld
    }
    
    private let physicsContatctDelegatesLock = NSRecursiveLock()
    private var physicsContatctDelegates: [SKPhysicsWorld: (SKPhysicsContactDelegate, [UUID: AnyObserver<SKPhysicsContact>], [UUID: AnyObserver<SKPhysicsContact>])] = [:]