ServerSync for iOS Apps
Sometimes your app can't rely on the device's date and time settings being correct. This library will help you calibrate your app's (NB: Not the device) time to the correct UTC time using either your own server or Google's. This allows you to coordinate events between your server and your app.
You can use websockets, but that tends to be overkill and adds extra complexity client-and-server-side. Ideally, the app will periodically calibrate with the server. This library uses an exponential moving average to increase accuracy and reduce the effects of variations in latency.
Other solutions are Kronos and TrueTime.swift. They both work the same way but they miss the main point. They don't synchronize to your server. They synchronize to the true global UTC time, which means you have to also synchronize your server. They don't provide a complementary solution for your server. This is an extra hassle which is unnecessary (and probably overkill) for 99% of all apps.
The library is written in Swift 2. It will be converted to Swift 3 when my project makes the transition in a few months. If you want, you can change it and do a pull-request. It will be a trivial change.
Installation
CocoaPods (iOS 8+)
Not set up yet
Manual (iOS 7+)
Copy ServerSync.swift
into your project.
(Optional) If you want to calibrate using Google's server, then Copy GoogleCalibrate.swift
into your project.
Usage
"Client" will refer to the iPhone in the below contexts.
General Usage
In order to calibrate NSDate
, you must first calibrate with a server. You can use Google or your own server (see below). If you do not calibrate, then this library will return the client's time unchanged - no detriment to you.
Ideally, you should run the calibration periodically or upon observing UIApplicationWillEnterForegroundNotification
because you can't rely on one HTTP request having low latency. High latency will reduce the accuracy of the calibration. This library will utilize an exponential moving average to get as accurate as possible.
It is also wise to recalibrate after observing these notifications: UIApplicationSignificantTimeChangeNotification
, NSSystemClockDidChangeNotification
and NSSystemTimeZoneDidChangeNotification
.
After calibrating, the functions of note are:
func toClientTime() -> NSDate - If you have a time that is calibrated to the server's time (i.e. server's UTC time) and you want to translate it to the client's time:
//Server states that something in your app should happen at this time: "2016-11-18 01:18:00" (UTC)
dateFormat = NSDateFormatter()
dateFormat.timeZone = NSTimeZone(abbreviation: "GMT")
dateFormat.dateFormat = "yyyy-MM-dd HH:mm:ss"
let serverDate: NSDate = dateFormat.dateFromString("2016-11-18 01:18:00")
let clientDate: NSDate = serverDate.toClientTime()
//Now use `clientDate` in your app. It has been calibrated.
static func reset() - Uncalibrates NSDate
so it is no longer synchronized to the server.
NSDate.reset()
If you have your own server
Concepts
Let's assume that from the moment the client sends it's request, the total response time is made up of 3 components:
- Lreq - The transmission time before your server can start processing the request.
- Operation Duration - How long the server took to process the request
- Lres - The transmission time afterwards
Your server must respond with it's UTC time in UNIX format (nanoseconds) - ideally generated as late as possible. It can be embedded in the JSON response for example. If not possible, you can use the Date
header field which provides second-level accuracy. Whether the Date
header field is generated as late as possible is dependent on the server.
Ideally, the server will also return how long it took to perform the operation (i.e. process the request). The units must be in nanoseconds. If this is not possible, you can approximate it as 0 nanoseconds, at the cost of some accuracy.
static func updateOffsetRaw(clientRequestUTCUnixNano: Int64, serverOperationDurationNano: Int64, serverUTCUnixNano: Int64) -> Int64
Parameters:
clientRequestUTCUnixNano
- Before the client sends the request, you record the client-side UTC Unix time in nanoseconds
serverOperationDurationNano
- The server should respond with how long it took to process the response from start of receiving request to start of sending out response. If you don't have access to the server, you can approximate this value to 0
serverUTCUnixNano
- The server should respond with it's internal UTC time in UTC Unix time in nanoseconds - preferably as late as possible before sending response.
Server setup
Sample Go Code:
import (
"time"
"net/http"
)
type Response struct {
SyncDuration int64 `json:"SyncDuration"` //How long the request took to process in nanoseconds
SyncUTC int64 `json:"SyncUTC"` //UTC time at end of response in UNIX time (nanoseconds)
}
func RequestHandler (w http.ResponseWriter, r *http.Request) {
operationStartTime := time.Now().UTC()
//Process stuff. Do what ever. Compile Response etc
response := Response{}
//As late as possible: For UTC server-client synchronization
operationEndTime := time.Now().UTC()
response.SyncDuration = operationEndTime.Sub(operationStartTime).Nanoseconds()
response.SyncUTC = operationEndTime.UnixNano()
ReturnSuccess(w, response) //Return response in JSON format
}
Client setup
The client must record the exact moment (in Unix nanosecond format) it sent the http request.
Sample Swift Code (Using AFNetworking):
let sm: AFHTTPSessionManager = AFHTTPSessionManager(baseURL: apiBaseUrl)
let clientRequestTime: Int64 = NSDate.UTCToUnixNano() //Store time of sending request in Unix nanosecond format
sm.GET(path, parameters: nil, progress: nil, success: {(task: NSURLSessionDataTask, responseObject: AnyObject?) -> Void in
//response will have 2 json fields
// `syncDuration` - See Go code above
// `syncUTC` - See Go code above
let response = f(responseObject)
//UTC server-client synchronization
NSDate.updateOffsetRaw(clientRequestTime, serverOperationDurationNano: response.syncDuration, serverUTCUnixNano: response.syncUTC)
})
Using Google's server
You can asynchronously calibrate using Google's servers. The library will attempt to use the lowest-latency server available.
NSDate.calibrate()
Other Useful Packages
Check out "github.com/pjebs/Obfuscator-iOS"
library. Secure your app by obfuscating all the hard-coded security-sensitive strings embedded in the binary.
Check out "github.com/pjebs/GAE-Toolkit-Go"
package. Escape CloudSQL and save money by using an external MYSQL database with Google App Engine - Go.
Final Notes
If you found this package useful, please Star it on github. Feel free to fork and/or provide pull requests. Any bug reports will be warmly received.