Bonjour in Python

A Tip

In Python, it’s very common to not worry too hard about memory management; it is, after all, a fully garbage-collected language, and PyObjC extends this to the Cocoa classes you create from Python. However, this can bite you when dealing with Bonjour. You may remember me mentioning Bonjour issues before, and this is closely-related.

When a service appears on your network, of a type you’ve told Bonjour that you care about, you receive a netServiceBrowser:didFindService:moreComing: message. On receiving this, there are two common patterns, depending on your application: one is to store the NSNetService that’s passed in to you, probably in a list which is the basis of a browser you’re showing to the user. If the user selects the service, you resolve it.

The other is when your service publishes data in its TXT record that you need before you can display it to the user. The TXT data isn’t available until you resolve it, so you’ll typically just do this:

python

bad example:
def netServiceBrowser_didFindService_moreComing_(self, browser, service, more): service.setDelegate_(self) service.resolveWithTimeout_(5.0)

and then quietly ignore it until either netServiceDidResolveAddress: or netService:didNotResolve: arrive. In fact, you might not care about the resolve failing if you’re filtering services for inclusion: Bad service! No resolve, no includey!

Unfortunately, if you follow the bad example above you’ll never receive netServiceDidResolveAddress: or netService:didNotResolve:!

Why? Because the thread that Cocoa performs Bonjour resolves on does not retain the service. Instead, when netServiceBrowser:didFindService:moreComing: returns, the only copy of service is released — and NSNetService automatically and silently cancels the resolve inside its destructor.

Doh!

So you must retain the NSNetService yourself, typically by stashing it in a list or dictionary:

python

better example:
def netServiceBrowser_didFindService_moreComing_(self, browser, service, more): self.servicesByName[service.name()] = service service.setDelegate_(self) service.resolveWithTimeout_(5.0)

Hopefully this will help a few people wondering why their NSNetServices aren’t resolving for no explicable reason…