Wrapping Facebook's FBSDKCoreKit with ReactiveCocoa 3.0

I’m currently working on a side project that involves Facebook login, hence the previous post. The Facebook SDK is written in Objective-C and the interoperability with Swift is not that great. Using the API and chaining calls will quickly give you a nice, warm callback soup. Let’s see how we can’t make it (hopefully) better using RAC 3.0.

I’m assuming you already know how to setup a project with ReactiveCocoa and FBSDKCoreKit, using Cocoapods, Carthage or your bare hands.

Requesting a token

The Facebook framework contains a FBSDKLoginManager class you’ll use to request an authorization token with specific permissions. In our case, we care about the “public_profile” one so we can retrieve a user’s id, name and picture. Let’s wrap this call in a SignalProducer that will send an FBSDKAccessToken.

class FacebookService: NSObject {
    func requestToken() -> SignalProducer<FBSDKAccessToken, NSError> {
        return SignalProducer { sink, disposable in
            let loginManager = FBSDKLoginManager()
            loginManager.logInWithReadPermissions(["public_profile"], handler: { (result, error) -> Void in
                if let error = error {
                    sendError(sink, error)
                    return;
                }

                sendNext(sink, result.token)
                sendCompleted(sink)
            })
        }
    }
}

This code is pretty straightforward as most of the logic is already wrapped by the Facebook SDK, we’re only taking the results and the potential error and sending them down the sink. Of course there is room for improvement: we could catch the error, retry if something goes wrong or the user cancels, etc. but let’s just go straight to the point.

Performing a graph API request

Now that we have a SignalProducer ready to retrieve an authentication token as soon as it’s subscribed to. That’s not enough though. In a classic approach, we’d add the call to fetch the user profile in the completion block we were just in. This is no classic approach though, what we need is to transform that token in a way that we ultimately end up with the profile informations we’re looking for. To do that, we need another SignalProducer that will send a request to the Graph API.

func performGraphRequest(path: String, parameters: [NSObject : AnyObject]?, tokenString: String) -> SignalProducer<JSON, NSError> {
    return SignalProducer { sink, disposable in
        let request = FBSDKGraphRequest(graphPath: path, parameters: nil, tokenString: tokenString, version: nil, HTTPMethod: "GET")
        request.startWithCompletionHandler({ (connection, result, error) -> Void in
            if let error = error {
                sendError(sink, error)
            } else {
                sendNext(sink, JSON(result))
                sendCompleted(sink)
            }
        })
    }
}

You’ll notice that “performGraphRequest” is a standalone function, not a method on our FacebookService. It could probably be the same for the “requestToken” one, but this code is from an application I’m currently working on, where this class depends on a few other injected services. There are a couple good reasons to use as much standalone functions as possible in your code. Functions are now first class citizens in Swift, so it will help you write code you could easily test and reuse. It also makes it a lot less tempting to add state by relying on values of your class property. In our case, that could be the token we’ve just fetched.

There is also a JSON object that I haven’t talked about. I mentioned earlier that the interoperability between the Facebook framework written Objective-C and Swift wasn’t great: the callback used in startWithCompletionHandler is an instance of AnyObject. Trying to use it directly will lead you straight into Force-Unwrapping-Hell, which is probably not what you want, that’s why I ended up using SwiftyJSON.

At the end of the process, we’re actually looking for a more neutral object, like a simple Account struct with common informations like identifier, username and picture, for example.

protocol Account {
    var identifier: String { get }
    var username: String { get }
    var picture: String? { get }
}

So now we have a function that can fetch a Facebook authentication token, another that can perform a request to the Graph API. We’ll also need to map this JSON object to a value-object conforming to this protocol, for example a FacebookAccount:

struct FacebookAccount: Account {
    var id: String
    var username: String
    var picture: String?
}

It would be a bit tedious (and not optimal) to use all these methods in our main view controller, for example. That’s why we’ll wrap all this in a method that returns a Signal we can use to retrieve an account. We don’t really care about the required steps to end up with an Account.

    func authenticate() -> SignalProducer<Account, NSError> {
        return requestToken()
            |> flatMap(.Latest, { token in
                return performGraphRequest("me?fields=id,name,picture", nil, token.tokenString)
                    |> map { payload in
                        let id = payload["id"].string
                        let username = payload["name"].string
                        let picture = payload["picture"]["data"]["url"].string

                        let account = FacebookAccount(id: id!, username: username!, picture: picture)
                        return account
                    }
                    |> observeOn(UIScheduler())
            })
    }

Once we’ve got this method, all we need to do is to subscribe to the SignalProducer returned by the authenticate method, like this:

let service = FacebookService()
service.authenticate().start(next: { account in
    println("Welcome user \(account.username) (#\(account.identifier))")
})

This is a rough example of how you can use ReactiveCocoa with the Facebook SDK, hopefully it will make you want to try it in your own app.