I am very new to Rx and have been trying to make a Bonjour discovery client that resolves services. This is very simple to do imperatively, but I wanted to try with RxSwift.
Since the discovered NSNetService objects need to be persisted before resolution, I'm having to make a nested subscription call, the outer one for discovery, and the inner on for resolution...but something tells me this is not the best way.
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()
}
}
My proxy classes are as follows:
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
}
}
}
If I use flatMap
after the browser.rx_netServiceBrowserDidFindServiceMoreComing
call instead of subscribeNext
, the service won't resolve because I can't persist it to an array from within flatMap
for reasons that escape me, mostly from never having touched Rx. Am I bound to using nested calls?
Short version of my problem is the above works, but seems convoluted to me. Any ideas would be greatly appreciated.
You can use scan
to avoid nested subscription. It will add each NSNetService
from rx_netServiceBrowserDidFindServiceMoreComing
to the array. Note that you don't event have to store servicesArray
as a member variable in this case, unless you need it for another reason.
Then you can use flatMap
as follows:
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)
This requires adding a method in NSNetService
extension, as you have to return an Observable
from flatMap
:
extension NSNetService {
//existing methods omitted
public func rx_resolveWithTimeout(timeout: NSTimeInterval) -> Observable<NSNetService> {
self.resolveWithTimeout(timeout)
return rx_netServiceDidResolveAddress.filter {
$0 == self
}
}
}