RxSwift и вложенные подписки для Bonjour Discovery

Я новичок в Rx и пытаюсь создать клиент обнаружения Bonjour, который разрешает службы. Это очень просто сделать императивно, но я хотел попробовать с RxSwift.

Поскольку обнаруженные объекты NSNetService должны быть сохранены перед разрешением, мне приходится делать вызов вложенной подписки, внешний для обнаружения и внутренний для разрешения... но что-то мне подсказывает, что это не лучший способ.

import UIKit
import RxSwift

class BonjourClient: NSObject {

    let disposeBag = DisposeBag()
    var servicesArray = [NSNetService]()

    func startBrowsing() {
        let browser = NSNetServiceBrowser()
        browser.rx_netServiceBrowserDidFindServiceMoreComing
            .subscribeNext { (service: NSNetService) in
                        self.servicesArray.append(service)
                        self.servicesArray.last!.rx_netServiceDidResolveAddress
                            .subscribeNext { (sender: NSNetService) in
                                print("Resolved \(sender.name)")
                                let data = sender.TXTRecordData()
                                let dict: [String: NSData?] = NSNetService.dictionaryFromTXTRecordData(data!)
                                for (key, value) in dict {
                                    print("\(key) : \(String(data: value!, encoding: NSUTF8StringEncoding)!)")
                                }
                        }.addDisposableTo(self.disposeBag)
                        self.servicesArray.last!.resolveWithTimeout(5)
                }.addDisposableTo(disposeBag)
        browser.searchForServicesOfType("_amzn-wplay._tcp.", inDomain: "local.")
        NSRunLoop.currentRunLoop().run()
    }

}

Мои прокси-классы следующие:

import UIKit
import RxSwift
import RxCocoa

class RxNSNetServiceBrowserDelegateProxy: DelegateProxy, NSNetServiceBrowserDelegate, DelegateProxyType {

    static func currentDelegateFor(object: AnyObject) -> AnyObject? {
        let browser: NSNetServiceBrowser = object as! NSNetServiceBrowser
        return browser.delegate
    }

    static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
        let browser: NSNetServiceBrowser = object as! NSNetServiceBrowser
        browser.delegate = delegate as? NSNetServiceBrowserDelegate
    }

}

class RxNSNetServiceDelegateProxy: DelegateProxy, NSNetServiceDelegate, DelegateProxyType {

    static func currentDelegateFor(object: AnyObject) -> AnyObject? {
        let service: NSNetService = object as! NSNetService
        return service.delegate
    }

    static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
        let service: NSNetService = object as! NSNetService
        service.delegate = delegate as? NSNetServiceDelegate
    }

}

extension NSNetServiceBrowser {

    public var rx_delegate: DelegateProxy {
        return proxyForObject(RxNSNetServiceBrowserDelegateProxy.self, self)
    }

    public var rx_netServiceBrowserDidFindServiceMoreComing: Observable<NSNetService> {
        return rx_delegate.observe("netServiceBrowser:didFindService:moreComing:")
            .map { params in
                let service = params[1] as! NSNetService
                return service
        }
    }

}

extension NSNetService {

    public var rx_delegate: DelegateProxy {
        return proxyForObject(RxNSNetServiceDelegateProxy.self, self)
    }

    public var rx_netServiceDidResolveAddress: Observable<NSNetService> {
        return rx_delegate.observe("netServiceDidResolveAddress:")
            .map { params in
                return params[0] as! NSNetService

        }
    }
}

Если я использую flatMap после вызова browser.rx_netServiceBrowserDidFindServiceMoreComing вместо subscribeNext, служба не разрешится, потому что я не могу сохранить ее в массиве из flatMap по причинам, которые ускользают от меня, в основном из-за того, что я никогда не касался Rx. Должен ли я использовать вложенные вызовы?

Краткая версия моей проблемы - это вышеперечисленные работы, но мне они кажутся запутанными. Любые идеи очень приветствуются.


person diatrevolo    schedule 08.03.2016    source источник


Ответы (1)


Вы можете использовать scan, чтобы избежать вложенной подписки. Он добавит каждый NSNetService из rx_netServiceBrowserDidFindServiceMoreComing в массив. Обратите внимание, что в этом случае вам не нужно сохранять событие servicesArray в качестве переменной-члена, если только оно вам не нужно по другой причине.

Затем вы можете использовать flatMap следующим образом:

browser.rx_netServiceBrowserDidFindServiceMoreComing
    .scan([NSNetService]()) { (services: [NSNetService], service: NSNetService)  in
        return services + [service]
    }.flatMap { (services: [NSNetService]) in
        return services.last!.rx_resolveWithTimeout(5)
    }.subscribeNext { (sender: NSNetService) in
        print("Resolved \(sender.name)")
        let data = sender.TXTRecordData()
        let dict: [String: NSData?] = NSNetService.dictionaryFromTXTRecordData(data!)
        for (key, value) in dict {
            print("\(key) : \(String(data: value!, encoding: NSUTF8StringEncoding)!)")
        }
}.addDisposableTo(disposeBag)

Это требует добавления метода в расширение NSNetService, так как вы должны вернуть Observable из flatMap:

extension NSNetService {
//existing methods omitted
 public func rx_resolveWithTimeout(timeout: NSTimeInterval) -> Observable<NSNetService> {
        self.resolveWithTimeout(timeout)
        return rx_netServiceDidResolveAddress.filter {
            $0 == self
        }
    }
}
person Michał Ciuba    schedule 14.03.2016