Search code examples
macosobjective-c++multipeer-connectivity

Multipeer Connectivity: Browsing in Objective-C++ Silently Fails


I'm trying to write the browser/discovery side for a Multipeer Connectivity app in Objective-C++. I think I can advertise, as far as I can tell anyways since I can see it using Discovery (https://itunes.apple.com/us/app/discovery-dns-sd-browser/id1381004916?mt=12). But my browser doesn't see anything. What am I doing wrong?

#include <iostream>
#include <thread>

#import <MultipeerConnectivity/MultipeerConnectivity.h>

@interface Bowser : NSObject<MCNearbyServiceBrowserDelegate>

- (void)browser:(MCNearbyServiceBrowser *)browser 
    foundPeer:(MCPeerID *)peerID 
    withDiscoveryInfo:(NSDictionary *)info;

- (void)browser:(MCNearbyServiceBrowser *)browser 
    lostPeer:(MCPeerID *)peerID;

@end

@implementation Bowser

- (void)browser:(MCNearbyServiceBrowser *)browser 
    foundPeer:(MCPeerID *)peerID 
    withDiscoveryInfo:(NSDictionary *)info {
    std::cout << "Hello" << std::endl;
}

- (void)browser:(MCNearbyServiceBrowser *)browser 
    lostPeer:(MCPeerID *)peerID {
    std::cout << "Goodbye" << std::endl;
}

@end

int main() {
    MCPeerID* peerid = [[MCPeerID alloc] initWithDisplayName:@"PeerId"];
    Bowser* delegate = [[Bowser alloc] init];

    MCNearbyServiceBrowser* browser = [MCNearbyServiceBrowser alloc];
    [browser initWithPeer:peerid serviceType:@"m"];

    browser.delegate = delegate;

    [browser startBrowsingForPeers];

    using namespace std::chrono_literals;
    std::this_thread::sleep_for(10s);

    [browser stopBrowsingForPeers];
}

Also useful would be suggestions on how to debug what's happening. Anybody...?


Solution

  • I (finally) figured it out. MultipeerConnectivity needs a run loop. This is not in the documentation.

    I assumed that the MultipeerConnectivity API created the threads and/or loops it needed upon the method call [browser startBrowsingForPeers]. It does not.

    Nowhere in this code is a run loop started. Also, interestingly enough, using a NSThread directly doesn't start a run loop, even though it's implied that it will:

    Your application neither creates or explicitly manages NSRunLoop objects. Each NSThread object—including the application’s main thread—has an NSRunLoop object automatically created for it as needed. If you need to access the current thread’s run loop, you do so with the class method currentRunLoop.

    What will create a run loop (and start it) is CFRunLoopRun():

    The current thread’s run loop runs in the default mode (see Default Run Loop Mode) until the run loop is stopped with CFRunLoopStop or all the sources and timers are removed from the default run loop mode.

    What will stop it, then, is CFRunLoopStop(CFRunLoopRef rl):

    This function forces rl to stop running and return control to the function that called CFRunLoopRun or CFRunLoopRunInMode for the current run loop activation.

    Of course, CFRunLoopStop takes a CFRunLoopRef as an argument. You can get that by using CFRunLoopGetCurrent, just remember it's a reference and may expire at any time. I think you can be pretty sure that the run loop won't die while you're in the callback that's running in the run loop. But I wouldn't count on it sticking around afterwards. In fact, in this case, the whole point is to kill it at this point; so I expect it to go away.