browseResultsChangedHandler called multiple times

I'm working on a game that uses NWBrowser and NWListener to create a connection between an iOS and tvOS app.

I've got the initial networking up and running and it works perfectly when running in the simulator(s). However, when I run on-device(s), I've found that browseResultsChangedHandler gets called multiple times for what is ostensibly the same service.

My browser handler (which runs on iOS) looks like this:

browser.browseResultsChangedHandler = { [weak self] results, changes in
    if let result = browser.browseResults.first {
        self?.onPeerConnected?(PeerConnection(endpoint: result.endpoint))
    }
}

The first time it gets called, the interface in the NWBrowser.Result is en0, but the 2nd time it gets called, it is en0 AND awdl0.

Because my current handling is so naive, this re-invocation ends up with two connections being made to the remote server (the Apple TV).

Now, I know that this handler, by its very name, is designed to be called multiple times as things change, so I'm curious as to what strategies I might employ here.

Is there any value in tearing down any previous connections and re-connecting using the latest one? Should I just kill the browser as soon as I handle the first one? Just ignore subsequent ones?

I'm sure that, to a degree, the answer is probably "it depends"... but I'm curious to see if there might be at least some high-level strategies like "whatever you do, don't do xxxx" or "most apps do yyyy" :-)

Thanks.

Answered by DTS Engineer in 824456022

Yeah, that’s quite reasonable. en0 is likely your infrastructure Wi-Fi interface whereas awdl0 is a peer-to-peer Wi-Fi interface.

IMPORTANT BSD interface names, like en0, are not considered API, so you shouldn’t rely on them in your code. However, it’s helpful to know what to expect when you’re debugging.

My general advice is that you not connect on discovery. Rather, show the user a list and allow them to choose. That’ll help with this problem because the list is likely to have stabilised before the user makes a choice.

However, I understand that this model doesn’t work for everyone. And, even if you follow the model, it’s still possible for new browse results to come in while you’re connecting.

As to what you do about that, well… you guess it… it depends (-: You have a bunch of options:

  • ‘Debounce’ the browser results, so you only start your connection once things have been stable for a bit.

  • Start an immediate connection and then, if it fails to go through quickly, cancel it and start a new connection with the additional results.

  • Start multiple connections and let them race.

  • Combine the above, so start an immediate connection and, if it it doesn’t through promptly, start the additional ones and then use whichever one connects first.

  • Probably more that I’ve not thought of.

And how do you choose between these? Well, one obvious factor is complexity. The other factor I’d consider is real world behaviour. That’s especially important with peer-to-peer Wi-Fi, which has significant limitations in practice.

Oh, and as you’re combining iOS and tvsOS, I wanted to make sure you’re aware of DeviceDiscoveryUI framework.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

Yeah, that’s quite reasonable. en0 is likely your infrastructure Wi-Fi interface whereas awdl0 is a peer-to-peer Wi-Fi interface.

IMPORTANT BSD interface names, like en0, are not considered API, so you shouldn’t rely on them in your code. However, it’s helpful to know what to expect when you’re debugging.

My general advice is that you not connect on discovery. Rather, show the user a list and allow them to choose. That’ll help with this problem because the list is likely to have stabilised before the user makes a choice.

However, I understand that this model doesn’t work for everyone. And, even if you follow the model, it’s still possible for new browse results to come in while you’re connecting.

As to what you do about that, well… you guess it… it depends (-: You have a bunch of options:

  • ‘Debounce’ the browser results, so you only start your connection once things have been stable for a bit.

  • Start an immediate connection and then, if it fails to go through quickly, cancel it and start a new connection with the additional results.

  • Start multiple connections and let them race.

  • Combine the above, so start an immediate connection and, if it it doesn’t through promptly, start the additional ones and then use whichever one connects first.

  • Probably more that I’ve not thought of.

And how do you choose between these? Well, one obvious factor is complexity. The other factor I’d consider is real world behaviour. That’s especially important with peer-to-peer Wi-Fi, which has significant limitations in practice.

Oh, and as you’re combining iOS and tvsOS, I wanted to make sure you’re aware of DeviceDiscoveryUI framework.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for the detailed reply.

At the moment, for simplicity's sake, I'm inclined to just use the first one and just ignore any subsequent results... but I'm curious if there's a reason why subsequent results might be "better"? ie. let's say I decide to debounce to catch the later ones, what is better about the later ones that makes it worth doing that little bit of extra debouncing?

Thanks also for the pointer to the Device Discovery framework... I did initially look at that, but sadly my game is designed to have multiple clients connecting to the Apple TV, so I think that rules it out.

.

... peer-to-peer Wi-Fi, which has significant limitations in practice.

I don't suppose you happen to have a link to a tech note, or a previous post, that might outline a few things to watch out for?

Thanks again for your support - not just here, but in all aspects of the Apple ecosystem.

Written by edwardaux in 824534022
I'm curious if there's a reason why subsequent results might be "better"?

In most cases I’d expect the first result to be sufficient. The fact that it was found first suggests that it’s going via the fastest path.

However, when it comes to networking there are no universal truths. Imagine that your Apple TV and iPhone are on the same infrastructure Wi-Fi, and that Wi-Fi is borked in a way that allows multicasts but blocks STA-to-STA unicasts [1]. In that case the first connection might not go through, because it’s based on infrastructure discovery. However, the later peer-to-peer discovery will go through.

However^2, this is largely theoretical. Such a network would be weird — although not the weirdest thing I’ve seen — and, even if it did exist, there’s no guarantee that Network framework would fail to connect. It has a whole bunch of smarts to try to get the connection through.

Written by edwardaux in 824534022
I don't suppose you happen to have a link to a tech note, or a previous post, that might outline a few things to watch out for?

Nothing that’s been assembled into a coherent whole. The most common issues are performance, and specifically latency. Enabling peer-to-peer Wi-Fi can introduce latency into infrastructure Wi-Fi, as the device has only one radio, so it must pause infrastructure Wi-Fi to run peer-to-peer.

I do, however, have an important tip: Once you’re successfully connected, make sure to cancel any outstanding peer-to-peer work, including your browse and any outstanding connections. Those operations rely on multicasts, and the performance impact of multicasts is much greater than that of unicasts [2].

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] See Wi-Fi Fundamentals for a Wi-Fi terminology decoder.

[2] Another thing I explain in Wi-Fi Fundamentals.

browseResultsChangedHandler called multiple times
 
 
Q