Swift Multipeer Rewrite

The amount of code that ran the Multipeer Protype was small, so I decided to just rewrite the damn thing in Swift to get a feel for the new language.

I decided for the hell of it to write something up journal style. Very stream of though-y, not sure how easy it is to read or not.

I posted the journal here

I posted the code on GitHub

I really like Swift, it's amazing just how much extranious code just falls away when you start getting the hang out it. It really reminds me much more of writing in Javascript or Ruby.

I've got to hand it to Apple, they didn't make any of my Cocoa knowledge obsolete at all. It's writing the same calls against the same frameworks tha that's amazing.

Multipeer Swift Rewrite Journal

(This is a companion post to this one)

June 9, 2014


Created a new project in Xcode 6, created a single view application and choosing Swift as my language. Took a quick poke around, SO WEIRD to see .swift extensions and no .m/.h files.

Looked at the Storyboard and saw the new Adaptive UI stuff. Good thing I shotgunned the What's New in Cocoa Touch session last night.


Figured out the Adaptive UI stuff pretty quickly, was able to get it up and running with some Auto Layout Magic™ easily.


Testing layouts with a toddler on your lap is difficult.


Started writing my first bits of Swift. Got all of the IBOutlets and IBactions created just like I would for Objective-C.

Read the docs, got the view controller saying that it can be delegates for the various multipeer framework objects.

import UIKit  
import MultipeerConnectivity

class ViewController: UIViewController, MCNearbyServiceAdvertiserDelegate, MCSessionDelegate, MCNearbyServiceBrowserDelegate  {  
    @IBOutlet var myPeerIDLabel : UILabel
    @IBOutlet var theirPeerIDLabel : UILabel
    @IBOutlet var theirCounterLabel : UILabel
    @IBOutlet var counterButton : UIButton

    override func viewDidLoad() {
        // Do any additional setup after loading the view, typically from a nib.

    override func didReceiveMemoryWarning() {
        // Dispose of any resources that can be recreated.

    @IBAction func incrementCounterAndSend(sender : UIButton) {



I can't seem to get the autocomplete to help me out when it comes to implementing all of the required protocols for these various objects though. Still looking.


Yeah, looks like there's just weird issues with this version of Xcode 6. Once, and only once did I get the autocomplete to spit out

func advertiser(advertiser: MCNearbyServiceAdvertiser!, didReceiveInvitationFromPeer peerID: MCPeerID!, withContext context: NSData!, invitationHandler: ((Bool, MCSession!) -> Void)!) {}  

But I did manage to get the compiler to stop complaining 'ViewController' does not conform to protocol MCNearbyServiceAdvertiserDelegate.


It looks like it's something that's up with typing 'advertiser' that the autocomplete doesn't kick in. Bug report time.


Weird. Setting the class properties kept giving me an error ClassviewController' has no initializers`.

Setting them as optionals fixes it

    var session: MCSession?
    var advertiser: MCNearbyServiceAdvertiser?
    var browser: MCNearbyServiceBrowser?
    var localPeerID: MCPeerID?
    var connectedPeers = []

Ah, the docs make it clear:

Classes and structures must set all of their stored properties to an appropriate initial value by the time an instance of that class or structure is created. Stored properties cannot be left in an indeterminate state.

Properties of optional type are automatically initialized with a value of nil, indicating that the property is deliberately intended to have “no value yet” during initialization.

They need to be set as optionals since they have no initial state. Gotta remember to unwrap them.


Booleans are false and true. Whole lot of muscle memory is going to keep me writing YES and NO.


Using the optionals to initialize a local instance isn't making any sense. I think I need to watch more sessions


Okay, what was I smoking a few hours ago. It's pretty simple. I had declared optionals and for some reason I was attempting to unfold the values before assigning them:

browser! = MCNearbyServiceBrowser(peer: localPeerID!, serviceType: HotColdServiceType)  

It's that one "!" that's the issue. Of course I don't want to unwrap the value, I'm trying to set the value.

The rest of this should go much more smoothly


Almost rewritten. Not really taking advantage of many of the new Swift features but that can be forgiven considering this is the first bit of actual Swift code I'm writing in earnest.

Had my first run-in with the array literal syntax. I needed to create a class level property for storing MCPeerIDs, ended up having to declare it like so:

var connectedPeers: MCPeerID[] = []

It's kind of weird declaring what the array will store when you initialize it, but I could get used to it.


Grand Central Dispatch! I was hitting my head as to how to pass a block into the dispatch_async method when I realized that I just pass in a closure.

dispatch_async(dispatch_get_main_queue(), {  
    self.theirPeerIDLabel.text = peerID.displayName
    self.counterButton.enabled = true

Honestly, it couldn't be any more simple. Interesting that it explicitly yelled at me to call self within the closure, I was expecting that to just error out.


So, I guess Swift native strings can't be created from NSData. Kind of odd, using native strings everywhere and then having to write some good ol' NSString. I think this is something that will confuse new programmers when they start.

let message = NSString(data: data, encoding: NSUTF8StringEncoding)  


Must be getting the hang of this, just converted a string to NSData in one line:

let data = "\(buttonCounter) times".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)  


Started debugging and immediately started getting EXC_BAD_ACCESS errors on the MCSessionDelegate methods. Ugh. Done for the night.

June 10, 2014


Decided to look at my testing strategy. I was using an instance of the app in the simulator and an instance of my old Objective-C app. Instead, this morning I changed the deployment target to iOS 7 and ran it on my two testing devices.
Lo and behold, it started working.
The last thing I did was I rewrote all of the NSLog statements as println statements. I think that was the last bit of objective-c habits I need to purge.

All done

Multipeer Networking and You

The goal of this prototype is to get two devices communicating via multipeer networking. If Apple's done their job right (Framework development + documentation) it should be easy, right?

Once you wrap your head around how the whole system is implemented then yes, it actually is fairly easy to get multiple devices communicating.

First off, let's lay our our user story/use case:

Two devices are connected via multipeer, each one has a button (which is activated when a connection is secured) which will increment the counter on the other device.  

Apple's Multipeer Framework page is a bit light but it does lay out the general pieces that we need.

  • Session objects handle the communications.
  • Advertiser objects tells others they're available.
  • Browser objects browse for advertised devices.
  • Peer ID's allow for unique identification.

One of the interesting things about the multipeer networks is that unlike a traditional client/server network each node could be advertising and discovering at the same time.

This lead me to one concept that I had to wrap my head around before I could really grok this concept: It's the browsing device that invites the advertiser to a session.

Initially, I was thinking of the advertiser as more of a server and the browser as a client. Wrong, wrong, wrong. Thinking like that made it even more difficult for me to really understand what was going on under the hood.

Let's describe the workflow:
- You declare a service identifier (just an NSString constant) so your app knows what to advertise/browse. - The app starts both advertising and browsing for that service at the same time. - If the app finds a peer, create a session and invite that peer to it. - If the app receives an invitation to a session, accept it. - Stop advertising or discovering if either of these things happens, restart is the peer is lost. - Start sharing that sweet, sweet data.

As a warning, it's what my app's implementing is an exceptionally simplified version of what this framework can do. It's assuming that there are only two peers that always want to connect to each other.

  • The app should be alerting users when you receive a peering invitation, allow them to decline.
  • You probably keep a list of found peers stored so that you can act when the peer is lost.

In true Apple Framework fashion, there's the 'Simple to implement but boring looking' Apple standard and the 'Handle everything yourself with base classes' approach. A lot of the examples out there is using the Apple way, which is to use MCAdvertiserAssistant to handle the advertising and MCBrowserViewController to browse for available peers.

The issue with each of these is that I was hoping to implement more of a SpaceTeam-esque method of matchmaking so I realized that I would have to roll my own.

Code - ASWASWLobbyInitViewController.m

Everything is contained inside one view controller.

Imports + Interface + Implementation stuff

@import MultipeerConnectivity;

static NSString * const HotColdServiceType = @"hotcold-service";

@interface ASWLobbyInitViewController () <MCNearbyServiceAdvertiserDelegate, MCSessionDelegate, MCNearbyServiceBrowserDelegate>

@property IBOutlet UILabel *myDeviceName;
@property IBOutlet UILabel *theirDeviceName;
@property IBOutlet UILabel *theirButtonCounter;
@property IBOutlet UIButton *incrementCounterButton;

@property MCSession *session;
@property MCNearbyServiceAdvertiser *advertiser;
@property MCNearbyServiceBrowser *browser;
@property MCPeerID *localPeerID;
@property NSMutableArray *connectedPeers;


@implementation ASWLobbyInitViewController {
    NSNumber *buttonCounter;

HotColdServiceType is simply a string we'll be using later as the service identifier.

We declare ourselves to be able to handle MCNearbyServiceAdvertiserDelegate, MCSessionDelegate and MCNearbyServiceBrowserDelegatedelegate messages.

We have our outlets for our our labels and a few private properties to keep instances of our session, advertiser, browser, peerID and an array of connected peers.

One private variable is an NSNumber of the button counter. It's the value we'll be sending across.


- (void)viewDidLoad {
    [super viewDidLoad];

    self.localPeerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]];

    self.myDeviceName.text = @"";
    self.theirDeviceName.text = @"";
    self.theirButtonCounter.text = @"0 times";
    self.incrementCounterButton.enabled = NO;
    buttonCounter = [[NSNumber alloc] initWithInt:0];
    self.connectedPeers = [[NSMutableArray alloc] init];

    // Browser for others
    self.browser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.localPeerID
    self.browser.delegate = self;

    // Advertise to others
    self.advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.localPeerID
    self.advertiser.delegate = self;


Right now, I'm using the device name as the device's Peer ID. In the future I think I'll be generating something a bit more fun.

After that, we set some default values and initialize the browser and the advertiser.

We're ready to get this show on the road.


-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];

    self.myDeviceName.text = self.localPeerID.displayName;

    [self.browser startBrowsingForPeers];
    [self.advertiser startAdvertisingPeer];

We set our display name on the label and start both browsing and advertising. From here things get event based.


-(void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID
      withContext:(NSData *)context
invitationHandler:(void (^)(BOOL, MCSession *))invitationHandler {  
    // Creates a session anytime someone connects using the service.
    NSLog(@"Received Invitation from %@", peerID.displayName);

    if (!self.session) {
        self.session = [[MCSession alloc] initWithPeer:self.localPeerID
        self.session.delegate = self;
        invitationHandler(YES, self.session);

        [self.advertiser stopAdvertisingPeer];
        [self.browser stopBrowsingForPeers];


When a browsing peer sends you an invitation, this method will fire.

Locking it down with an if (!self.session) means that we won't respond if we've already connected to a session. Since we're purposefully locking this down to 2 peers, we're good.

When we initialize session property we pass it our local peer id this caused me a great deal of confusion. I assumed that you create your session with the peerID value being passed in by your peer.

The docs are actually pretty clear about this:

Create an MCPeerID object that represents the local peer, and use it to initialize the session object.  

After setting the delegate for the session to self, we then have to call the handler block with a true boolean and the session object as parameters and then stop both advertising and browsing.


-(void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info {
    NSLog(@"FOUND PEER %@", peerID.displayName);

    if (!self.session){
        self.session = [[MCSession alloc] initWithPeer:self.localPeerID
        self.session.delegate = self;

        [browser invitePeer:peerID toSession:self.session withContext:nil timeout:5];

        [self.advertiser stopAdvertisingPeer];
        [self.browser stopBrowsingForPeers];

-(void)browser:(MCNearbyServiceBrowser *)browser lostPeer:(MCPeerID *)peerID {
    NSLog(@"LOST PEER %@", peerID);

    // Kill the session
    self.session = nil;

    // Start looking again.
    [self.advertiser startAdvertisingPeer];
    [self.browser startBrowsingForPeers];

On the browsing side, we're simply looking for any advertised services, inviting them to a session (Notice that on this end we're also initializing it with our local session ID) setting our delegate, and stopping both advertising and browsing once one is found.

Now, if we lose the peer after finding it, we're simply restarting the advertising and browsing services.

#pragma mark - MCSessionDelegate

-(void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID {
    NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"Message received: %@", message);
    dispatch_async(dispatch_get_main_queue(),^ {
        self.theirButtonCounter.text = message;

-(void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream
      withName:(NSString *)streamName
      fromPeer:(MCPeerID *)peerID {


-(void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName
      fromPeer:(MCPeerID *)peerID
  withProgress:(NSProgress *)progress {

-(void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName
      fromPeer:(MCPeerID *)peerID
         atURL:(NSURL *)localURL
     withError:(NSError *)error {

-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
    NSArray *stateStringRepresentation = @[@"MCSessionStateNotConnected", @"MCSessionStateConnecting", @"MCSessionStateConnected" ];

    NSLog(@"SESSION STATE CHANGE: %@", stateStringRepresentation[state] );

    if (state == MCSessionStateConnected) {
        NSLog(@"Connected to %@", peerID.displayName);

        [self.connectedPeers addObject:peerID];

        dispatch_async(dispatch_get_main_queue(),^ {
            self.theirDeviceName.text = peerID.displayName;
            self.incrementCounterButton.enabled = YES;

        [self.view setNeedsDisplay];

The two important delegate events are didReceiveData and didChangeState.

didReceiveData is simply called when some data is returned from your peer, It's sent as NSData, so you have to change it back to a NSString.

You also see that I'm setting the text on the label in the main thread using the main queue. If you want the UI to be updated immediately you need to make sure that you call those on the main thread.

Once a session has been created, you need to watch for MCSessionStateConnected to be returned in the didChangeState method. This means that you're good to go to begin sending information across.

I'm enabling the button and setting the peer label on the main queue once we're connected.

'Press This' Button Action

- (IBAction)incrementCounterAndSend:(UIButton *)sender {
    buttonCounter = @([buttonCounter intValue] + 1);
    NSString *message = [NSString stringWithFormat:@"%d times", [buttonCounter integerValue]];
    NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
    NSError *error = nil;
    if (![self.session sendData:data
                          error:&error]) {
        NSLog(@"[Error] %@", error);

Pretty simple stuff. Just incrementing the button integer by one, creating a 'X times' string and sending that across the wire.

That's it! You end up with a 2 peer system as soon as you arrive on the view controller. I haven't tested it with more than 2 though.

Now I think I'll rewrite this in Swift.