MCSv3

Mobile Development


Ilya Loshkarev
loshkarev.i@gmail.com

Networking

Remote Data Cost

  • Battery Life
  • System Perfomance
  • Data Transfer Cost
  • Optimize Assets
  • Cache Data

Reachability Check


							var flags: SCNetworkReachabilityFlags = []
							if !SCNetworkReachabilityGetFlags(hostRoute, &flags) {
								return false
							}
							if flags.contains(.reachable){
								return true
							}
						

The reachability API helps to determine if
a remote host is reachable

App Transport Security

Starting in iOS 9.0 and OS X v10.11, a new security feature called App Transport Security (ATS) is enabled by default for all HTTP connections made with NSURLSession. ATS requires that HTTP connections use HTTPS.

Apple Docs

Disabling ATS


						NSAppTransportSecurity : Dictionary
							NSAllowsArbitraryLoads : YES      // http everywhere - unsafe!
						

Specify domains that are allowed to have http connections:


						NSAppTransportSecurity : Dictionary
							edu.mmcs.sfedu.ru : Dictionary
								NSAllowsArbitraryLoads : YES // allows http in the domain
						

iOS 10.0 introduced NSAllowsArbitraryLoadsInMedia and
NSAllowsArbitraryLoadsInWebContent

URL Session

URL Session Diagram

Dispatch queue for remote requests on top of TCP/IP sockets

Shared Session


						  let session = URLSession.shared
						  let url = URL(string: "http://edu.mmcs.sfedu.ru/")
						  let task = session.dataTask(with: url) {
						    data, response, error in
						    /* handle response */
						  }
						  task.resume() // start task
						

Default uncofigurable session for handling basic requests

Session Configurtion

  • Default default session configuration
    URLSessionConfiguration.default
  • Ephimeral doesn't write to cache or cookies
    URLSessionConfiguration.ephimeral
  • Background allows background upload and download
    URLSessionConfiguration.background("background.session")

Session Tasks

  • Data Task sends and receives data using NSData objects
    session.dataTask(with: URL)
  • Upload Task sends data and supports background uploads
    session.uploadTask(with: URLRequest, from: Data)
  • Download Task retrieves data and supports background downloads and uploads
    session.downloadTask(with: URL)

Session Delegate

  • URLSessionDelegate
    Authentication on a session-level, Session errors
  • URLSessionTaskDelegate
    Authentication requests, Redirects, Completion
  • URLSessionDataDelegate
    Recieved Data, Should use cache
  • URLSessionDownloadDelegate
    Download has finished, started, progressed

Building a Session


						  let config = URLSessionConfiguration.default
						  config.allowsCellularAccess = false
						  config.timeoutIntervalforRequest = 30.0
						  let session = URLSession(configuration: config)
						  let url = URL(string: "http://edu.mmcs.sfedu.ru/")
						  let task = session.dataTask(with: url) {
						      data, response, error in
						      /* handle response */
						  }
						  task.resume() // start task
						

If no delegate is assigned to a session,
a completion handler must be provided to recieve data

HTTP Response


						  let task = session.dataTask(with: url) {
						      data, response, error in
						      if let httpResponse = response as? HTTPURLResponse {
						          switch httpResponse.statusCode {
						            case 200: print("OK")
						            case 404: print("Not found")
						            default:
						              print("Something else")
						          }
						      }
						  }
						

HTTP Headers

HTTP headers can be setup as part of each request or
as default headers in configuration


						  let config = URLSessionConfiguration.default
						  config.allowsCellularAccess = false
						  config.timeoutIntervalforRequest = 30.0
						  config.httpAdditionalHeaders["Some Header"] = someValue
						

Download Data


						  let request = URLRequest( url: myUrl)
						  request.httpMethod = "GET"
						  /* set any additional HTTP Headers here */
						  let task = session.dataTask(with: request ){
						      data, response, error in
						      /* handle response */
						  }
						  task.resume()
						

Upload Data


						  let request = URLRequest(url: myUrl)
						  request.httpMethod = "POST"
						  /* set any additional HTTP Headers here */
						  let task = session.uploadTask(with: request, from: someData ){
						      data, response, error in
						      /* handle response */
						  }
						  task.resume()
						

URL with Parameters


						  let urlComponents = URLComponents(
						      URL: baseUrl,
						      resolvingAgainstBaseURL: true)!
						  urlComponents.path = relativePathString
						  urlComponents.query = parametersString.addingPercentEncoding(
						      withAllowedCharacters: .urlHostAllowed)
						  let request = URLRequest(url: urlComponents.url!)
						

WebViews

class to embed web content in your app


							let webView = UIWebView()
							let request = URLRequest(url: myUrl)
							webView.loadRequest(request)
						

Web​View objects can be used to display
Keynote, PDF, and Pages documents

Alamofire

Alamofire https://github.com/Alamofire

HTTP networking library

  • Chainable Request / Response Methods
  • URL / JSON / plist Parameter Encoding
  • Upload File / Data / Stream / MultipartFormData
  • HTTP Response Validation

CocoaPods

Dependency manager for Cocoa projects


              > sudo gem install cocoapods
              // initialize
              > pod init
              // create pod file
              > echo "source 'https://github.com/CocoaPods/Specs.git'
              platform :ios, '10.0'
              use_frameworks!
              target '<Your Target Name>' do
                pod 'Alamofire', '~> 4.0'
              end" > Podfile
              // install all dependences
              > pod install
            
https://cocoapods.org

Simple Request


              Alamofire.request("https://httpbin.org/get").response {
                  response in
                  print(response.request)  // original URL request
                  print(response.response) // HTTP URL response
                  print(response.data)     // server data
                  print(response.result)   // result of response serialization
                }
            

Responses are handled asynchronously

Response Validation


              Alamofire.request("https://httpbin.org/get")
                  .validate(statusCode: 200..<300)
                  .validate(contentType: ["application/json"])
                  .responseData { response in
                      switch response.result {
                      case .success:
                          print("Validation Successful")
                      case .failure(let error):
                          print(error)
                      }
                  }
            

Request with Parameters


              let parameters: Parameters = [
                  "foo": "bar",
                  "baz": ["a", 1],
                  ]
              ]

              Alamofire.request("https://httpbin.org/post",
                  parameters: parameters, encoding: URLEncoding.default)
              Alamofire.request("https://httpbin.org/post",
                  parameters: parameters, encoding: URLEncoding.httpBody)
              Alamofire.request("https://httpbin.org/post", method: .post,
                  parameters: parameters, encoding: JSONEncoding.default)
            

Parameters can be encoded into
URL, HTTP body or JSON

Additional Headers


              let headers: HTTPHeaders = [
                  "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
              ]
              Alamofire.request("https://httpbin.org/post",
                  method: .post, headers: headers)
            

Authentication

AAA Protocols

  • Authentication – confirming your identity
  • Authorization – confirming your right to make a request
  • Accounting – keeping track of user requests

Basic Access Authentication

Implemented by Apache modules

Unauthenticated requests return a response with
401 Unauthorized status and a WWW-Authenticate field


              WWW-Authenticate: Basic realm = "User Visible Realm"
            

Client sends authentication credentials
using HTTP header field Authorization


              Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
            

Session Level Authentication


              func urlSession(_ session: URLSession, task: URLSessionTask,
                  didReceive challenge: URLAuthenticationChallenge,
                  completionHandler: @escaping (URLSession.AuthChallengeDisposition,
                  URLCredential?) -> Void)
              {
                  let credential = URLCredential( // credentials to send
                      user: "test",
                      password: "test",
                      persistence: .forSession)
                  completionHandler(.useCredential, credential)
              }
            

If your authentication is a default Apache authentication
implementation of didReceiveChallenge delegate is prefered

Request Level Authentication


              let authData = Data("\(user):\(password)".utf8)
              let base64String = authData.base64EncodedString()
              let request = URLRequest( url: myUrl)
              request.addValue("Basic \(base64String)",
                  forHTTPHeaderField: "Authorization")
            

Alamofire Request Authentication


              Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)")
                  .authenticate(user: user, password: password)
                  .responseJSON { response in
                      debugPrint(response)
                  }
            

Open Authorisation Protocol

2010 – ver. 2.0

Provides secure deligated access on behalf of resource owner

OAuth Logo

Peudo Authentication

PseudOAuth

3-legged Authorization

  1. Register
  2. Receive Authorization Token
  3. Ask for Authorised Access with Auth.Token
  4. Receive Access Token
  5. Access APIs with given token

Theese two tokens could be given by servers differnet from an API provider

Ask for Authentication Token


              func startOAuth2Login() {
                  let authPath = "https://github.com/login/oauth/authorize?
                      client_id=\(clientID)&scope=repo&state=TEST_STATE"

                  if let authURL = URL(string: authPath) {
                    UIApplication.shared.openURL(authURL)
                  }
              }
            

openURL is a system-wide call for an App
that is registered for the requested URL Scheme

Browser window will pop up and ask the user
to allow access for our app

Handling URL Callbacks


              URL types
                Item 0
                  URL Schemes
                    Item 0 - yourURLScheme
                  URL identifier - yourAppID
            

iOS app can be register to handle URL Scheme calls

Most OAuth2 systems allow for an URL callback

Receive Authentication Token


              class AppDelegate: UIResponder, UIApplicationDelegate {
                  func application(application: UIApplication, handleOpenURL url: URL) -> Bool {
                      // url == yourURLScheme://...&code=12345&...
                      proceedOAuth2Access(with: getOAuth2Token(url))
                      return true
                  }
              }
            

Your app is getting a callback to handle URL with Auth.Token

Ask for Access Token


              func getOAuth2Token(_ url: URL) -> String? {
                /* parse URL Components for code */
              }
              func proceedOAuth2Access(with authToken: String?) {
                  guard let receivedToken = authToken { return }
                  let getTokenPath = "https://github.com/login/oauth/access_token"
                  let tokenParams = [ "client_id": clientID,
                      "client_secret": clientSecret,
                      "code": receivedToken ]
                  Alamofire.request(.POST, getTokenPath, parameters: tokenParams)
                    .responseString { (request, response, results, error) in
                     /*  handle response to extract access token */
                    }
              }
            

Alamofire Adapter & Retrier


              let oauthHandler = OAuth2Handler(
                  clientID: "12345678",
                  baseURLString: baseURLString,
                  accessToken: "abcd1234",
                  refreshToken: "ef56789a"
              )

              let sessionManager = SessionManager()
              sessionManager.adapter = oauthHandler
              sessionManager.retrier = oauthHandler

              let urlString = "\(baseURLString)/some/endpoint"
              sessionManager.request(urlString)
            

Alamofire proides abstractions to handle
some of the OAuth2 interactions on the session-level

Restful API

Representational State Transfer

RESTful API

Non standartized protocol that utilises HTTP headers and JSON data to access server endpoints

Expected RESTful API

URL/HeaderGETPOSTPUTDELETE
Entity/id select by idinsert with id update with iddelete with id
Entity select all/filteredinsert all/filtered update all/filtereddelete all/filtered

Query Parameters
with URLSession

  • URL Encoded
    urlComponents.query = parametersString
  • HTTP Headers
    request.addValue(key, parameters[key])
  • HTTP Body
    request.body = parametersString

Query Parameters
with Alamofire

  • URL Encoded
    
    									Alamofire.request("https://httpbin.org/get", parameters: parameters,
    										encoding: URLEncoding.queryString)
    								
  • HTTP Headers
    
    									Alamofire.request("https://httpbin.org/headers",
    										headers: parameters)
    								
  • HTTP Body
    
    									Alamofire.request("https://httpbin.org/get", parameters: parameters,
    										encoding: URLEncoding.httpBody)
    								

Javascript Object Notation


									{
									  "firstName": "Иван",
									  "lastName": "Иванов",
									  "address": {
									    "streetAddress": "Московское ш., 101, кв.101",
									    "city": "Ленинград",
									    "postalCode": 101101
									  }
									}
								
json logo

JSON Serialization


							// Serialize any KVC compliant object to JSON string
							let data = JSONSerialization.data(withJsonObject: person, options:[])
							// Deserialize string into NSObject
							if let json = ( try? JSONSerialization.jsonObject(with: data!,
									options: []) ) as? [String: AnyObject]
							{
								print(json["firstName"] as? String)
							}
						

Object Mapper

github.com/ObjectMapper

Simple JSON Object mapping written in Swift

  • Mapping JSON to objects
  • Mapping objects to JSON
  • Nested Objects
  • Custom transformations during mapping

Mappable

a protocol, defines mapping of JSON parameters to object properties

							class Temperature: Mappable {
								var celsius: Float
								required init?(map: Map) {
									guard let c = map.JSON["celsius"] else { return nil }
									celsius = c
								}
								mutating func mapping(map: Map) {
									celsius  <- map["celsius"]
								}
							}
						

Alamofire Object Mapper

github.com/AlamofireObjectMaper

						Alamofire.request(url).responseObject {
							(response: DataResponse<Temperature>) in
							print(response.result.value.celsius)
						}
						

Database Synchronization

SyncDB

github.com/SyncDB

Automatically maps CoreData objects to JSON


							Sync.changes( json["data"] as! [[String : Any]],
									inEntityNamed: "Data",
									dataStack: self.dataStack) {
								error in
								completion(error)
							}
						

Related Resources