Socket.IO on iOS

We are pleased to announce the immediate availability of the Socket.IO Swift Client! You’ll now be able to write code that runs natively on iOS and OSX, while maintaining the simplicity and expressiveness of the JavaScript client!

import Foundation
let socket = SocketIOClient(socketURL: "localhost:8880")
socket.on("important message") {data, ack in
    println("Message for you! \(data?[0])")
    ack?("I got your message, and I'll send my response")
    socket.emit("response", "Hello!")
}
socket.connect()

To show how you can use it in a real project, I’ll show you how to create a small Tic Tac Toe app like the one shown above.

Overview

In this tutorial we’ll look at creating a small iOS app that demonstrates socket.io and iOS. If you learn better from looking at code you can look at it here. The point of the tutorial is not to explain developing an iOS app, but to demonstrate how you can incorporate socket.io-client-swift into your projects! So it is assumed you have a basic knowledge of XCode.

Note: This example uses Swift 1.2. However, 1.2 isn’t much different from Swift 1.1, and the library has branches for Swift 1.1 and 1.2. The only difference in this guide is I use 1.2’s expanded if let construct to avoid nesting.

Note 2: While this library is written in, and meant for, Swift applications, it can be used with Objective-C projects, but will require some extra work (you’ll probably need to create a Swift class that can interface with your Objective-C code, as not all methods in the client will be available to Objective-C i.e emit, onAny). See this for more information.

Introduction

I designed socket.io-client-swift to be as close to socket.io-client as I could. So many of the ways you do things in socket.io-client look similar here! This is not a step-by-step tutorial for making a Tic Tac Toe app, only adding the socket.io-client part of it.

Setting up the Project

From Xcode, create a new project with the layout of single-view iOS application. You can name it whatever you like, I’ll be naming mine TicTacIOiOS.

The next step is getting the code for socket.io-client-swift, you can either use git to clone the repo to a directory, or simply download a release. Either way you get it, the process of adding it to your project is the same. Simply drag the folder named SwiftIO to the same place you copied SocketRocket! (Again making sure you select copy.)

And that’s it, the hardest part of putting our app together is done! At this point, if you want to test that it’s setup properly, try building and running the app, it should compile.

Adding Our Code

Now, assuming you’ve created your user interface. It’s time to add the code that will interface with our socket.io server!

In our demo application we have one UIViewController subclass, named ViewController. All of our code will be added there. First, we need to add a member named socket of type SocketIOClient to our ViewController.

let socket = SocketIOClient(socketURL: "localhost:8900")

Now, in our overridden viewDidLoad method we want to add two things, a method call that will add our handlers to our socket, and a call to connect our socket.

self.addHandlers()
self.socket.connect()

Handlers

Now that we have our method calls, it’s time to implement the method that adds the handlers!

func addHandlers() {
    // Our socket handlers go here
}

Since we’re about to add the handlers, I think it’s worth mentioning the syntax I use for closures in Swift. Swift has many different ways of expressing closures, and they can be found here. But the form I use for adding handlers is a trailing closure, without explicit type annotations. You can use other closure forms if you wish.

I’m not going to show you all the handlers in our demo app here, just a few that demonstrate important things you’ll need to know when working with socket.io-client-swift.

The first handler we’re going to add is one that will be called on any event, since it’s useful for debugging the API.

// Using a shorthand parameter name for closures
self.socket.onAny {println("Got event: \($0.event), with items: \($0.items)")}

The next handler that we’ll add is the one that tells the app that the game has started.

self.socket.on("startGame") {[weak self] data, ack in
    self?.handleStart()
    return
}

Now to explain some things. [weak self] is a capture list. It tells the compiler that the reference to self in this closure should not add to the reference count of self. This is so when the socket object goes out of scope, the capture made in the closure doesn’t keep it from being deallocated. The first parameter in all .on callbacks is an optional NSArray, it will contain all the data received with the event, or nil. The second parameter in the callback is an optional with the type of AckEmitter. AckEmitter is simply a typealias of (AnyObject...) -> Void. We’ll see this used later.

The next handler we’ll add is the one for a win.

self.socket.on("win") {[weak self] data, ack in
    if let name = data?[0] as? String, typeDict = data?[1] as? NSDictionary {
        self?.handleWin(name, type: typeDict)
    }
}

As mentioned before, this is new syntax for if let introduced in Swift 1.2. It simplifies optional unwrapping (pyramid of doom). The important thing to gather from this handler is that you do not need to force unwrap the array to get the object. Also note that a JSON object will be exposed to Swift as an NSDictionary.

Another thing to note is that for almost all your handlers that have data, you’ll be doing some kind of optional unwrapping and type casting. This is an unfortunate consequence of working with JavaScript.

The final handler that I’ll demonstrate here is the one that handles whether the player wants to play again.

self.socket.on("gameReset") {data, ack in
    ack?(false)
}

In this simplified example, we simply send an acknowledgement to the server that we don’t ever want to play again. Remember that AckEmitter has a variadic definition, so you can send multiple things at once if you wanted.

Emitting Events

The next thing you’ll probably want to know is how to send events from the client. You’ll be pleased to know that it has a form almost exactly the same as socket.io-client!

In our ViewController we have a method that handles when a user wants to make a move. Without going into the logic of that, we’ll show how we send the data to the server.

@IBAction func btnClicked(btn:UIButton) {
    let coord:(x:Int, y:Int)

    // Long switch statement that determines what coord should be

    self.socket.emit("playerMove", coord.x, coord.y)
}

That’s all you need to do for sending data!

Some other examples of sending which aren’t covered in our demo are:

Sending JSON

As mentioned before, JSON in Swift is best represent as a Dictionary. Thankfully for you, you don’t need to worry about turning it into something socket.io will understand, that’s all done under-the-hood.

let myJSON = [
    "name": "bob"
]

socket.emit("jsonTest", myJSON)

Sending Binary

Binary data is also handled by the client, so you don’t need to worry about it.

let data = "Hello, ".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let data2 = "World".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!

socket.emit("dataTest", data, ["world": data2])

Requesting Acks

The client can also request that the server send an ack for an event. This is done by using the emitWithAck method which returns an object to which you can add a handler.

socket.emitWithAck("needsAck", "test").onAck {data in
    println("got ack with data: (data)")
}

A Note About Multitasking in iOS

As you probably know, iOS is very picky about what you can do in the background. As such, don’t expect that your socket connection will survive in the background! You’ll probably stop receiving events within seconds of the app going into the background. So it’s better to create a task that will gracefully close the connection when it enters the background (via AppDelegate), and then reconnect the socket when the app comes back into the foreground.

If you want to learn more about the client, check out the README! We also invite you to contribute by submitting issues, patches, documentation and examples.