Hello!
Siddharth Gupta
@sids7
iOS at Flipkart
Agenda
- Why Another Networking Library?
- State of the Art
- WebService - How does it help?
- Swifty - How does it help?
- Performance Benchmarks
"Why another networking library?"
Modern Apps have complex
networking requirements
Capabilities
- Request Conditions: OAuth Token? Location Access?
- Session Systems: Attaching headers to every request
- Adaptive to network connectivity
Where to keep my networking code?
- The Networking Subsystem
- Some kind of abstraction over popular libraries like Alamofire / Siesta
- Requests can be scattered in view/controller/store layers
- Helpers / Singletons can be used to share common request creation code
Let's Look at Artsy
Let's Look at Artsy
Let's Look at Artsy
State of the Art:
Moya
Can we do
better?
What do we need?
Organised
One place for all your requests
Concise
Simple & Familiar
Readable
Easy to Understand
WebService
simple, readable, declarative way of writing network requests
all request creation code in one place
your API becomes a type
your requests become functions
Demo
class Bin: WebService {
static var serverURL = "https://httpbin.org"
static var networkInterface: WebServiceNetworkInterface = Swifty()
static func getIP() -> NetworkResource {
return server.get("ip")
}
static func createUser(user: [String: String]) -> NetworkResource {
return server.post("post").json(body: user).header(key: "My", value: "Header")
}
}
This is your API ( a type)
This is a request (a function)
Usage
in Objective-C
@implementation ViewController: UIViewController {
- (void) viewDidLoad {
}
@end
in Objective-C
@implementation ViewController: UIViewController {
- (void) viewDidLoad {
[Bin getIP]
}
@end
in Objective-C
@implementation ViewController: UIViewController {
- (void) viewDidLoad {
[Bin getIP] load:^{
}];
}
@end
in Objective-C
@implementation ViewController: UIViewController {
- (void) viewDidLoad {
[Bin getIP] load:^(NSURLResponse *response, id data, NSError *error){
// Here's your response
}];
}
@end
static func getIP() -> NetworkResource {
return server.get("ip")
}
static func getIP() -> NetworkResource {
return server.get("ip")
.json("user": "name")
}
static func getIP() -> NetworkResource {
return server.get("ip")
.query("user": "name")
}
Compile Time Checks
server
.get()
.post()
.put()
.delete()
.query()
.fields()
.data()
.json()
.header()
.contentType()
.priority()
.mock()
.deliver(on:)
.tag()
.deliver(on:)
class ViewController: UIViewController {
override viewDidLoad(){
Bin.getIP().load(){ (response, data, error) in
// Here's your response
}
}
}
class DataStore {
// Should work in the background
override refreshIP(){
}
}
.deliver(on:)
class DataStore {
// Should work in the background
override refreshIP(){
let backgroundQueue = DispatchQueue("com.background.processing")
}
}
.deliver(on:)
class DataStore {
// Should work in the background
override refreshData(){
let backgroundQueue = DispatchQueue("com.background.processing")
Bin.getIP()
}
}
.deliver(on:)
class DataStore {
// Should work in the background
override refreshData(){
let backgroundQueue = DispatchQueue("com.background.processing")
Bin.getIP().deliver(on: backgroundQueue)
}
}
.deliver(on:)
class DataStore {
// Should work in the background
override refreshData(){
let backgroundQueue = DispatchQueue("com.background.processing")
Bin.getIP().deliver(on: backgroundQueue).load(){ ... }
}
}
.priority()
class Bin: WebService {
static var serverURL = "https://httpbin.org"
static var networkInterface: WebServiceNetworkInterface = Swifty()
static func getIP() -> NetworkResource {
return server.get("ip")
}
static func createUser(user: [String: String]) -> NetworkResource {
return server.post("post").json(body: user)
}
}
.priority()
class Bin: WebService {
static var serverURL = "https://httpbin.org"
static var networkInterface: WebServiceNetworkInterface = Swifty()
static func getIP() -> NetworkResource {
return server.get("ip").priority(.high)
}
static func createUser(user: [String: String]) -> NetworkResource {
return server.post("post").json(body: user)
}
}
.mock()
static func getIP() -> NetworkResource {
return server.get("ip")
}
.mock()
static func getIP() -> NetworkResource {
return server.get("ip").mock
}
.mock()
static func getIP() -> NetworkResource {
return server.get("ip").mock(file: "response.json")
}
server
.get()
.post()
.put()
.delete()
.query()
.fields()
.data()
.json()
.header()
.contentType()
.priority()
.mock()
.deliver(on:)
.tag()
Extending WebService
extension NetworkResource {
}
Encrypt Data and Set Body
Extending WebService
extension NetworkResource {
func encrypt(data: Data){
}
}
Encrypt Data and Set Body
Extending WebService
extension NetworkResource {
func encrypt(data: Data) -> NetworkResource {
}
}
Encrypt Data and Set Body
Extending WebService
extension NetworkResource {
func encrypt(data: Data) -> NetworkResource {
let encrptedData = ourSecretEncryptor(data)
}
}
Encrypt Data and Set Body
Extending WebService
extension NetworkResource {
func encrypt(data: Data) -> NetworkResource {
let encrptedData = ourSecretEncryptor(data)
self.data(encryptedData)
}
}
Encrypt Data and Set Body
Extending WebService
extension NetworkResource {
func encrypt(data: Data) -> NetworkResource {
let encrptedData = ourSecretEncryptor(data)
self.data(encryptedData)
return self
}
}
Encrypt Data and Set Body
server
.get()
.post()
.put()
.delete()
.query()
.fields()
.data()
.json()
.header()
.contentType()
.encrypt()
.priority()
.mock()
.deliver(on:)
.tag()
WebService abstracts the actual networking from the upper layers
Gives you a simple async syntax: .load()
Multiple WebServices in your app is completely Acceptable
Example: one for contacting your server, one for external servers
You still have granular control over the networking
the module that powers your .load() function
Swifty
Swifty
- Based on NSURLSession
- Works using GCD APIs like DispatchGroup & Serial DispatchQueue Mutexes
- Initializer only needs an NSURLSessionConfiguration
- Deep integration with URLSessionTaskMetrics API
Constraints
- Classes which determine a network request has everything it needs to succeed.
- If not, the class can try to satisfy the constraint
- Classic Example: OAuth Token Constraint
Constraints
- Another use case: Requiring system permissions before you perform actions
- Location access required to perform a request
- Constraints provide asynchronous APIs: No hurry/blocking threads to satisfy constraints, the requests will wait.
Constraints
class OAuthConstraint: Constraint {
override func isConstraintSatisfied(for resource: NetworkResource) -> Bool {
// return false if we don't have the OAuth Token
// return true if we already have the OAuth Token
}
override func satisfyConstraint(for resource: NetworkResource) {
// Get the OAuth token from the server
// Make sure to call finish() when done
finish()
}
}
Interceptors
- Request Interceptors: Run before each request is fired over the network
- Response Interceptors: Run right after each response is received, and before it is returned to the caller
Request Interceptors
- A great time to attach authentication/important headers into your requests
Request Interceptors
class OAuthTokenAddingInterceptor: RequestInterceptor {
func intercept(resource: NetworkResource) -> NetworkResource {
// Get the token from where your Constraint
let token = Keychain.string(key: "OAuth")
// Attach it to the resource:
resource.header(key: "Token", value: token)
// Return the modified resource
return resource
}
}
Response Interceptors
- A great time to extract information in responses for system use (eg. session)
- Other use cases:
- Collecting/Logging Statistics on Request Failure Rates / URLSessionTaskMetrics
- Modify Faulty Responses / Errors Sent by Server
Response Interceptors
class ErrorCheckingInterceptor: ResponseInterceptor {
func intercept(response: NetworkResponse) -> NetworkResponse {
if let statusCode = response.response?.statusCode, statusCode == 204 {
response.fail(error: SwiftyError.responseValidation())
}
return response
}
}
The Best of Both Worlds
- The Constraint + Request Interceptor Combo
-
Best Example: OAuth Logic
- Get an OAuth Token from server if not already present (Constraint)
- Attach it to every request that goes through (Request Interceptor)
Swifty or Alamofire
- Alamofire is great if you want to quick access to networking. Simple syntax, fast access to network. Hence the singleton access.
- Swifty is something you make your own. You customize it according to your own needs.
- Multiple Instances per app with different configurations.
Performance Benchmarks
Performance Benchmarks
Performance Benchmarks
Performance Tests in the Example Project
Limitations
- Request RetriesCancellation
- HTTP Digest Authentication
- SSL Certificate Pinning
Roadmap
- Request Retries .retry()
- HTTP Digest Authentication
- .authentication() .digestAuthentication()
Roadmap
- Request Retries .retry()
- HTTP Digest Authentication
- .authentication() .digestAuthentication()
- Expose More URLSession: SSL Certificate Pinning
- Type Safe Response Adapters
The Swifty Inspector
~ 8000 downloads ⏬
58 apps 🚀
Swifty
Open Source
https://github.com/Flipkart/Swifty
pod try Swifty or pod 'Swifty'
Swifty Final
By sidsweb
Swifty Final
- 1,366