NetServiceBrowser (Bonjour) - why must stop() be called before searches work?

Hello,


In the documentation for Foundation's NetServiceBrowser (https://developer.apple.com/documentation/foundation/netservicebrowser) it states:


"To use an

NSNetServiceBrowser
object to search for services, allocate it, initialize it, and assign a delegate. (If you wish, you can also use the
schedule(in:forMode:)
and
remove(from:forMode:)
methods to execute searches on a run loop other than the current one.) Once your object is ready, you begin by gathering the list of accessible domains using either the
searchForRegistrationDomains()
or
searchForBrowsableDomains()
methods. From the list of returned domains, you can pick one and use the
searchForServices(ofType:inDomain:)
method to search for services in that domain."


However, that's not quite true. There must be a call to the stop() function of the browser before searches work.


e.g. if you place the code below into a Swift Playground you get the error:


Didn't search for service because: ["NSNetServicesErrorDomain": 10, "NSNetServicesErrorCode": -72003]


If you uncomment line 21 then the code works. Why does one have to call stop() on the NetServiceBrowser before searching, and shouldn't the docs mention this?


Thanks.


import Foundation

class DenonFinder: NSObject, NetServiceBrowserDelegate  {
    let browser = NetServiceBrowser()

    func start() {
        browser.delegate = self
        print("Search for registration domains")
        browser.searchForRegistrationDomains()
    }
    
    //MARK: NetServiceBrowserDelegate
    
    func netServiceBrowser(_ browser: NetServiceBrowser, didFindDomain domainString: String, moreComing: Bool) {
        print("didFindDomain: \(domainString)")
        if (moreComing) {
            print("moreComing")
        }

        //  Why must stop be called before search?
//        browser.stop()

        let webService = "_http._tcp."
        print("searchForServices: \(webService) in domain \(domainString)")
        browser.searchForServices(ofType: webService, inDomain: domainString)
    }
    
    func netServiceBrowserWillSearch(_ browser: NetServiceBrowser) {
        print("Search about to begin")
    }
    
    func netService(_ sender: NetService, didNotResolve errorDict: [String : NSNumber]) {
        print("Resolve error:", sender, errorDict)
    }
    
    func netServiceBrowserDidStopSearch(_ browser: NetServiceBrowser) {
        print("Search stopped")
    }

    func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
        print("Service found: \(service)")
        if (moreComing) {
            print("moreComing")
        } else {
            browser.stop()
        }
    }
    
    func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
        print("Service went away: \(service)")
    }
    
    func netServiceBrowser(_ browser: NetServiceBrowser, didNotSearch errorDict: [String : NSNumber]) {
        print("Didn't search for service because: \(errorDict)")
    }
}




class MainApplication {
    let runLoop = RunLoop.current
    let distantFuture = Date.distantFuture
//    let testTimer = Timer.scheduledTimer(withTimeInterval: 4.0, repeats: false, block: {_ in print("timer fired")})
    let denonFinder = DenonFinder()

    ///    Set this to false when we want to exit the app...
    var shouldKeepRunning = true


    init() {
        denonFinder.start()
    }

    func run() {
        while shouldKeepRunning == true &&
            runLoop.run(mode:.default, before: distantFuture) {}
    }
}

//    Entry Point
let app = MainApplication()
app.run()

Accepted Reply

The error you’re seeing, -72003, is not public but it translates to

kCFNetServiceErrorInProgress
. The issue here is that you’re using the same
NetServiceBrowser
object for two tasks:
  • Searching for registration domains, via

    searchForRegistrationDomains
  • Searching for services, via

    searchForServices(ofType:inDomain:)

An

NetServiceBrowser
object can only do one thing at a time, so if you try to start a service search while you’re already doing a registration domain search it fails with this error.

Why are you search for registration domains as part of a browser? Most folks implementing a browser only search for services, pass in the empty string as the domain parameter so that they automatically search all browsable domains. In advanced applications it can make sense to search for browsable domains, using

-searchForBrowsableDomains
, and then search for services independently in each domain. I can’t think of any reason why you’d want to search for registration domains as part of a browser.

Also, if you’re not already familiar with

NWBrowser
, you should check it out.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

The error you’re seeing, -72003, is not public but it translates to

kCFNetServiceErrorInProgress
. The issue here is that you’re using the same
NetServiceBrowser
object for two tasks:
  • Searching for registration domains, via

    searchForRegistrationDomains
  • Searching for services, via

    searchForServices(ofType:inDomain:)

An

NetServiceBrowser
object can only do one thing at a time, so if you try to start a service search while you’re already doing a registration domain search it fails with this error.

Why are you search for registration domains as part of a browser? Most folks implementing a browser only search for services, pass in the empty string as the domain parameter so that they automatically search all browsable domains. In advanced applications it can make sense to search for browsable domains, using

-searchForBrowsableDomains
, and then search for services independently in each domain. I can’t think of any reason why you’d want to search for registration domains as part of a browser.

Also, if you’re not already familiar with

NWBrowser
, you should check it out.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I see. Thanks very much for your detaiied reply Quinn.


I was searching for domains as (the way that I read them) the docs rather implied that one should. They don't mention that the inDomain: parameter is valid with an empty string (or what that would do), and they state in the overview:


"Once your object is ready, you begin by gathering the list of accessible domains using either the

searchForRegistrationDomains()
or
searchForBrowsableDomains()
methods. From the list of returned domains, you can pick one and use the
searchForServices(ofType:inDomain:)
method to search for services in that domain.

The

NSNetServiceBrowser
class provides two ways to search for domains. In most cases, your client should use the
searchForRegistrationDomains()
method to search only for local domains to which the host machine has registration authority. This is the preferred method for accessing domains as it guarantees that the host machine can connect to services in the returned domains. Access to domains outside this list may be more limited."


Thanks for the linkl to NWBrowser - I was going to say that I was avoiding the Network Framework as it's only available on Apple platforms, whereas Foundation is available on Linux as well (this may potentially end up on a Raspberry Pi) - although I've just noticed that NetServiceBrowser isn't implemented in the Open Source version of Foundation.... 😟


Thanks for your help.

If you’re getting start with Bonjour, I strongly the Bonjour Overview. While it’s not being updated with the latest stuff, it’s the best place to get an understanding of how everything fits together.

the docs rather implied that one should.

Please do file a bug against the docs.

this may potentially end up on a Raspberry Pi

For cross-platform work you need to go down to the DNS-SD API. For an example of how to use this from high-level code, see DNSSDObjects. It might make sense to factor your

NWBrowser
code out into your own class that you can then re-implement on non-Apple platforms using DNS-SD.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"