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,206