Swiftly Salesforce: Build Great iOS Apps on the Salesforce Platform

I started working with Swift, Apple’s new, open-source programming language, about a year ago. It took me a while to wrap my head around the new syntax and features, especially optionals, but I’ve quickly become a big fan. Swift enables me to write better code more quickly, and much more succinctly. I believe there is great opportunity for Salesforce ISV partners, big and small alike, to build and sell more custom iOS apps. And it’s easier than ever, thanks to recent enhancements to the Salesforce APIs and the Mobile SDK – and the release of Swiftly Salesforce.

Swiftly Salesforce is an open-source framework, written entirely in Swift, that I created to simplify the development of native iOS apps that integrate with Force.com. By design, Swiftly Salesforce is much smaller and lighter than the Mobile SDK, which is written in Objective-C, and includes features that I didn’t need for relatively-simple apps. Swiftly Salesforce focuses on only two things: freeing the developer from dealing with user authentication, and simplifying the coding of complex, asynchronous interactions with Salesforce APIs.

If you want to dive right into Swiftly Salesforce, you can find the source code, and easy setup instructions at: https://github.com/mike4aday/SwiftlySalesforce

Setting up Swiftly Salesforce

You can get up and running in minutes with Swiftly Salesforce. Follow the steps below, and see the GitHub repository’s README file for more details:

  1. Get a free Salesforce Developer Edition.
  2. Create a back-end Salesforce Connected App.
  3. Add Swiftly Salesforce to your Xcode project (See Appendix 1).
  4. Register your Connected App’s callback URL scheme with iOS (See Appendix 2).
  5. Configure your app delegate for Swiftly Salesforce (See Appendix 3).
  6. Add an “application transport security” (ATS) exception for “salesforce.com” (See Appendix 4).

Minimum requirements: iOS 9.1, Swift 2, and Xcode 7.

Using Swiftly Salesforce

Behind the scenes, Swiftly Salesforce leverages Alamofire and PromiseKit, two very widely-adopted frameworks built for elegant handling of network requests and asynchronous operations.

Below are some examples to illustrate how to use Swiftly Salesforce, and how you can chain complex asynchronous calls. You can also find a complete example app here, which enables the user to retrieve and update task records.

Swiftly Salesforce will automatically manage the entire Salesforce OAuth2 process (a.k.a. the “OAuth dance”). If Swiftly Salesforce has a valid access token, it will include that token in the header of every API request. If the access token has expired, Swiftly Salesforce will attempt to refresh it, without bothering the user to re-enter the username and password. If Swiftly Salesforce can’t refresh the access token, then it will direct the user to the Salesforce-hosted login page.

Example: Retrieve a Salesforce Record

The following will retrieve all the fields for the account record with the specified record ID:

SalesforceAPI.ReadRecord(type: "Account", id: "0013000001FjCcF".request()

To specify which fields should be retrieved:

let fields = ["AccountNumber", "BillingCity", "MyCustomField__c"]
SalesforceAPI.ReadRecord(type: "Account", id: "0013000001FjCcF", fields: fields).request()

Note that ‘request( )’ is an asynchronous function, whose return value is a “promise” that will be fulfilled at some point in the future:

let promise: Promise<AnyObject> = SalesforceAPI.ReadRecord(type: "Account", id: "0013000001FjCcF").request()

Now we can add a closure that will be called later, when the promise is fulfilled:

promise.then {
    (json) -> () in
    // Parse the JSON and do useful stuff
}

Example: Update a Salesforce Record

SalesforceAPI.UpdateRecord(type: "Task", id: "00T1500001h3V5NEAU", fields: ["Status": "Completed"]).request().then {
    (_) -> () in
    // Update the local model
}.always {
    // Update the UI
}

The ‘always’ closure will be called regardless of success or failure elsewhere in the promise chain.

Example: Querying Salesforce

let soql = "SELECT Id,Name FROM Account WHERE BillingPostalCode = '\(postalCode)'"
SalesforceAPI.Query(soql: soql).request()

See the next example for handling of the query results.

Example: Chaining Asynchronous Requests

Suppose we want to retrieve a random zip or postal code from a custom Apex REST resource, and then use that code in a query:

// Chained asynch requests 
firstly {
    // GET request to custom Apex REST resource
    SalesforceAPI.ApexRest(method: "GET", path: "/MyApexResourceThatEmitsRandomZip").request()
}.then {
    // Query accounts with that zip code
    (result) -> Promise in
    guard let zip = result["zip"] as? String else {
        throw NSError(domain: "TaskForce", code: -100, userInfo: nil)
    }
    let soql = "SELECT Id,Name FROM Account WHERE BillingPostalCode = '\(zip)'"
    return SalesforceAPI.Query(soql: soql).request()
}.then {
    // Parse JSON response
    (result) -> () in
    guard let records = result["records"] as? [[String: AnyObject]] else {
        throw NSError(domain: "TaskForce", code: -101, userInfo: nil)
    }
    for record in records {
        if let id = record["Id"] as? String, name = record["Name"] as? String {
            print("Account ID = \(id); name = \(name)")
        }
    }
}

You could repeat this chaining multiple times, feeding the result of one asynchronous operation as the input to the next operation. You could also spawn multiple, simultaneous operations and easily specify logic to be executed when all operations complete, when only the first operation completes, or when any operation fails, etc. PromiseKit is an amazingly-powerful framework for handling multiple asynchronous operations that would otherwise be very difficult to coordinate. See PromiseKit documentation for more examples.

Example: Handling Errors

The following code is from the example file, TaskStore.swift and shows how to handle errors:

firstly {
    SalesforceAPI.Identity.request()
}.then {
    // Extract user ID from JSON result
    (result) -> String in
    guard let userID = result["user_id"] as? String else {
        throw NSError(domain: "TaskForce", code: -100, userInfo: nil)
    }
    return userID
}.then {
    // Query tasks owned by user
    (userID) -> Promise in
    let soql = "SELECT Id,Subject,Status,What.Name FROM Task WHERE OwnerId = '\(userID)' ORDER BY CreatedDate DESC"
    return SalesforceAPI.Query(soql: soql).request()
}.then {
    // Parse JSON response into Task instances
    (result) -> () in
    guard let records = result["records"] as? [[String: AnyObject]] else {
        throw NSError(domain: "TaskForce", code: -101, userInfo: nil)
    }
    let tasks = records.map { Task(dictionary: $0) }
    self.cache = tasks
    fulfill(tasks)
}.error {
    // Any errors in the chain would be caught here
    (error) -> Void in
    reject(error)
}

You could also recover from an error, and continue with the chain, using a ‘recover’ closure. The following snippet is from PromiseKit’s documentation:

CLLocationManager.promise().recover { err in
    guard !err.fatal else { throw err }
    return CLLocationChicago
}.then { location in
    // the user’s location, or Chicago if an error occurred
}.error { err in
    // the error was fatal
}

Example: Log Out

If you want to log out the current Salesforce user, and then clear any locally-cached data, you could call the following. Swiftly Salesforce will revoke and remove any stored credentials, and automatically display a Safari View Controller with the Salesforce login page, ready for another user to log in.

// "Log Out" button was tapped
if let app = UIApplication.sharedApplication().delegate as? LoginViewPresentable {
    app.logOut().then {
        () -> () in
        // User's authorization now revoked - clear local data cache
        return
    }
}

Dependent Frameworks

The great Swift frameworks leveraged by Swiftly Salesforce:

  • PromiseKit (Version 3): “Not just a promises implementation, it is also a collection of helper functions that make the typical asynchronous patterns we use as iOS developers delightful too.”
  • Alamofire: “Elegant HTTP Networking in Swift”
  • Locksmith: “A powerful, protocol-oriented library for working with the keychain in Swift.”

Main Components of Swiftly Salesforce

  • SalesforceAPI: Acts as a ‘router‘ for Alamofire requests. The more important, or commonly-used Salesforce REST API endpoints are represented as enum values. You can also easily create Alamofire requests for your custom Apex REST endpoints, for example, by following the pattern established in this file.
  • Credentials: Swift struct that holds OAuth2 tokens, and other data, required for each request made to the Salesforce REST API. These values are stored securely in the iOS keychain.
  • Extensions: Swift extensions used by other components of Swiftly Salesforce. The extensions that you’ll likely use in your own code are ‘NSDateFormatter.SalesforceDateTime,’ and ‘NSDateFormatter.SalesforceDate,’ for converting Salesforce date/time and date fields to and from strings for JSON serialization.
  • OAuth2Manager: Singleton that coordinates the OAuth2 user-agent flow, and securely stores and retrieves the resulting access token. Though user-agent flow is more complex than the OAuth2 username-password flow, it is the preferred method of authenticating users to Salesforce, since their passwords are never handled by the client application.

Resources

If you’re new to Swift, the Salesforce Platform, or the Salesforce REST API, you might find the following resources useful.

About the Author

Michael Epstein is a senior technical evangelist at Salesforce, and works with ISV partners who are building applications on the Salesforce Platform.

LinkedIn: in/mike4aday | twitter: @mike4aday

Question, feedback, or bug report about Swiftly Salesforce?:

Appendices

Appendix 1: Add Swiftly Salesforce to Your CocoaPods Podfile

platform :ios, '9.1'
use_frameworks!

pod 'SwiftlySalesforce'
# Another pod could go here

Appendix 2: Register Your Connected App’s Callback URL Scheme with iOS

Upon successful OAuth2 authorization, Salesforce will redirect the user to the callback URL that you specified in your Connected App settings, and will append the access token (among other things) to that callback URL. Add the following to your app’s .plist file, so iOS will know how to handle the URL, and will pass it to your app’s delegate.

<!-- ADD TO YOUR APP'S .PLIST FILE -->
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>SalesforceOAuth2CallbackURLScheme</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string><!-- YOUR CALLBACK URL'S SCHEME HERE (scheme only, not entire URL) --></string>
    </array>
  </dict>
</array>

Then, you just need to add a single line in your app delegate class so that Swiftly Salesforce will handle the callback URL and the appended credentials.

func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
    handleRedirectURL(url)
    return true
}

Appendix 3: Configure your App Delegate for Swiftly Salesforce

Update your app delegate class so that it:

  • Configures Swiftly Salesforce with your Connected App’s consumer key and callback URL
  • Implements LoginViewPresentable – you don’t have to implement any methods, though, thanks to the magic of Swift 2’s protocol extensions
  • Calls handleRedirectURL(NSURL:) when asked by iOS to open the callback URL.

See below:

import UIKit
import SwiftlySalesforce

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, LoginViewPresentable {

    var window: UIWindow?
    let consumerKey = "<<YOUR CONNECTED APP'S CONSUMER KEY>>"
    let redirectURL = NSURL(string: "<<YOUR CONNECTED APP'S CALLBACK URL>>")!

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        OAuth2Manager.sharedInstance.configureWithConsumerKey(consumerKey, redirectURL: redirectURL)
        OAuth2Manager.sharedInstance.authenticationDelegate = self
        return true
    }

    func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
        handleRedirectURL(url)
        return true
    }
}

Appendix 4: Add an ATS Exception for Salefsorce

As of this writing, you need to add an application transport security (ATS) exception to your iOS application’s .plist file to allow it to connect to salesforce.com, as follows:

<!-- ADD TO YOUR APP'S .PLIST FILE -->
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSExceptionDomains</key>
    <dict>
        <key>salesforce.com</key>
        <dict>
            <key>NSExceptionRequiresForwardSecrecy</key>
            <false/>
            <key>NSIncludesSubdomains</key>
            <true/>
        </dict>
    </dict>
</dict>

Leave your comments...

Swiftly Salesforce: Build Great iOS Apps on the Salesforce Platform