$30 off During Our Annual Pro Sale. View Details »

Effective Networking with iOS 8 and Swift

Ben Scheirman
September 07, 2014

Effective Networking with iOS 8 and Swift

Presentation delivered to YOW! Connected in Melbourne, Australia, September, 2014.

Sample code demo'ed during this presentation can be found here: https://github.com/subdigital/yow-connected-effective-networking

Ben Scheirman

September 07, 2014
Tweet

More Decks by Ben Scheirman

Other Decks in Programming

Transcript

  1. Effective Networking
    with Swift and iOS 8

    View Slide

  2. Ben Scheirman
    @subdigital

    View Slide

  3. ChaiOne

    View Slide

  4. View Slide

  5. Agenda
    • Old and Crusty NSURLConnection
    • New Hotness
    • Live Demos !
    • HTTP
    • Caching
    • Bonus Round: API Tips

    View Slide

  6. NSURLConnection
    Invented for Safari ~2000
    Made public in 2003
    Poor separation of settings, config, cache

    View Slide

  7. View Slide

  8. View Slide

  9. NSURLConnection was replaced by NSURLSession in iOS 7

    View Slide

  10. NSURLSession

    View Slide

  11. Still use NSURLRequest
    Configurable Container (isolation w/ 3rd party libraries)
    Improved auth
    Rich Callbacks

    View Slide

  12. View Slide

  13. View Slide

  14. The New Family

    View Slide

  15. NSURLSessionConfiguration
    NSURLSession
    NSURLSessionTask
    NSURLSessionDelegate

    View Slide

  16. Start with
    NSURLSessionConfiguration

    View Slide

  17. Default Sessions
    + defaultSessionConfiguration

    View Slide

  18. Ephemeral Sessions
    + ephemeralSessionConfiguration

    View Slide

  19. Ephemeral Sessions
    + ephemeralSessionConfiguration
    ?

    View Slide

  20. "private browsing"

    View Slide

  21. Background Sessions
    + backgroundSessionConfiguration:

    View Slide

  22. Background Sessions
    + backgroundSessionConfiguration:
    • Takes an NSString identifier
    • Causes uploads / downloads to occur in background

    View Slide

  23. Customize it further
    All of these class methods return a copy, tweak from there...

    View Slide

  24. Well, what can you
    do with it?

    View Slide

  25. Set a default header
    var config = NSURLSessionConfiguration.defaultSessionConfiguration()
    config.HTTPAdditionalHeaders = [ "auth_token" : "1234abcd" ]

    View Slide

  26. Mark Requests as low-priority
    config.discretionary = true
    • Note: This has benefits, such as retrying when connection
    terminates, avoiding request if user is low on battery, or if Wi-
    Fi performance is not good enough.

    View Slide

  27. Disable Cellular
    config.allowsCellular = false

    View Slide

  28. Set custom caching behavior
    config.URLCache = MyCustomCache()
    config.requestCachePolicy = NSURLRequestReturnCacheDataElseLoad

    View Slide

  29. Inject your own custom
    protocols!

    View Slide

  30. ben://awww.yeah

    View Slide

  31. (actually quite useful for
    mocking requests)

    View Slide

  32. Next Step: build an
    NSURLSession to make
    reuqests

    View Slide

  33. var session = NSURLSession(configuration: config)

    View Slide

  34. var session = NSURLSession(configuration: config)
    or with a delegate...
    var delegate: NSURLSessionDelegate = self
    var session = NSURLSession(configuration: config,
    delegate: delegate,
    delegateQueue: NSOperationQueue.mainQueue())

    View Slide

  35. NSURLSession
    • Construct without the delegate if
    you want to use block callbacks
    • Construct with a delegate for
    advanced control (or background
    sessions)
    • If you pass completion handler
    blocks, delegate methods are not
    called

    View Slide

  36. NSURLSessionTask

    View Slide

  37. View Slide

  38. NSURLSessionDataTask
    • Represents GET, PUT, POST, DELETE
    • Handle Completion Callback
    • If error is present, request failed

    View Slide

  39. What Constitutes an Error?
    • Connection failed
    • Timeouts
    • Host invalid
    • Bad URL
    • Too many redirects
    • ... dozens more (check URL Loading System Error Codes)

    View Slide

  40. NSURLSessionDownloadTask
    • Downloading a file / resource
    • Streams to disk
    • Useful when size is large and can't fit in memory
    • Temp file path is provided in completion block
    • MUST move it somewhere if you want it to stick around

    View Slide

  41. Building a simple GET Request
    • Build an instance of NSURLSessionDataTask
    • (optionally) give it a block callback*
    • Call resume

    View Slide

  42. var config = NSURLSessionConfiguration.defaultSessionConfiguration()
    var session = NSURLSession(configuration: config)

    View Slide

  43. var config = NSURLSessionConfiguration.defaultSessionConfiguration()
    var session = NSURLSession(configuration: config)
    var url = NSURL(string: "https://www.nsscreencast.com/api/episodes.json")

    View Slide

  44. var config = NSURLSessionConfiguration.defaultSessionConfiguration()
    var session = NSURLSession(configuration: config)
    var url = NSURL(string: "https://www.nsscreencast.com/api/episodes.json")
    var task = session.dataTaskWithURL(url) {
    (let data, let response, let error) in
    // ...
    }

    View Slide

  45. var config = NSURLSessionConfiguration.defaultSessionConfiguration()
    var session = NSURLSession(configuration: config)
    var url = NSURL(string: "https://www.nsscreencast.com/api/episodes.json")
    var task = session.dataTaskWithURL(url) {
    (let data, let response, let error) in
    // ...
    }
    // don't forget to trigger the request
    task.resume()

    View Slide

  46. NSURLSessionDelegate
    Provide a delegate if you need more advanced control over:
    • Download Progress
    • Authentication Challenges
    • Connection Failure

    View Slide

  47. NSURLSessionDelegate

    View Slide

  48. Case Study: Requesting Images

    View Slide

  49. Have you ever seen this?
    NSURL *imageUrl = [NSURL URLWithString:@”http://i.imgur.com/kwpjYwQ.jpg”];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    UIImage *image = [UIImage imageWithData:imageData];
    [imageView setImage:image];

    View Slide

  50. View Slide

  51. What is wrong here?
    NSURL *imageUrl = [NSURL URLWithString:@”http://i.imgur.com/kwpjYwQ.jpg”];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    UIImage *image = [UIImage imageWithData:imageData];
    [imageView setImage:image];

    View Slide

  52. These are blocking calls
    ! NSData *imageData = [NSData
    dataWithContentsOfURL:imageUrl];
    ! UIImage *image = [UIImage imageWithData:imageData];

    View Slide

  53. View Slide

  54. Demo:
    Downloading Images

    View Slide

  55. • Images should probably be requested with download tasks
    • Block-based callbacks can be unwieldy (especially in
    Objective-C)
    • Use the delegate if you want to report download progress

    View Slide

  56. Demo:
    Searching the iTunes Store

    View Slide

  57. HTTP

    View Slide

  58. Status Code Cheat Sheet
    1xx - Informational / Transient
    2xx - A-OK !
    3xx - It's over there "
    4xx - You Messed up ✋
    5xx - We Messed Up $

    View Slide

  59. HTTP Caching

    View Slide

  60. HTTP Caching Techniques
    • If-Modified-Since
    • If-None-Match
    • Cache-Control

    View Slide

  61. If-Modified-Since
    • Client requests a resource
    • Server responds, includes a Date response header
    • Client Caches the data, including the date
    • Client sends request header If-Modified-Since
    • Server compares, can return 304 (Not Modified) with no
    body

    View Slide

  62. If-None-Match
    • Client requests a resource
    • Server responds, includes a E-Tag header
    • Client Caches the data, including the E-Tag
    • Client sends request header If-None-Match
    • Server compares, can return 304 (Not Modified) with no
    body

    View Slide

  63. Cache-Control
    • Server indicates when the resource expires
    • Client caches the data until that time
    • Client will immediately return local cache data if still fresh

    View Slide

  64. What does it look like on the client?

    View Slide

  65. View Slide

  66. View Slide

  67. View Slide

  68. What does it look like on the server?

    View Slide

  69. def show
    @band = Band.find(params[:id])
    fresh_when(:etag => @band,
    :last_modified => @band,
    :public => true)
    expires_in 10.minutes, :public => true
    end

    View Slide

  70. Observations
    • Server still executes a query to compute E-Tag and Modified
    Date
    • No body is transfered for a matching E-Tag or Date
    • Client doesn't even make request if Cache-Control is used
    and content is still fresh

    View Slide

  71. Cache-Control sounds perfect
    • Not everything is inherently cacheable
    • Only helpful for a single client, after the initial request

    View Slide

  72. View Slide

  73. Reverse-Proxy Cache to the
    Rescue

    View Slide

  74. View Slide

  75. Pros / Cons of Cache-Control
    • Client doesn't wait for a network request
    • Server spends zero resources
    • Clients May Render Stale Data

    View Slide

  76. Pros / Cons of E-Tag & IMS
    • Server skips rendering
    • Miniscule Data Transfer
    • Can appear instantaneous*
    • Server still spending resources computing & comparing E-
    Tag & dates

    View Slide

  77. Reason About Your Data
    • List of US States
    • User’s Profile
    • Customer’s Order
    • Activity Timeline
    • Yesterday’s stats

    View Slide

  78. Tradeoff between
    fresh data and user experience

    View Slide

  79. Caching with NSURLSession

    View Slide

  80. • Uses NSURLCache out of the box
    • Customize on the NSURLSessionConfiguration instance
    • Use ephemeralSessionConfiguration to disable caching.

    View Slide

  81. NSURLCache Gotchas

    View Slide

  82. NSURLCache Gotchas
    • will not cache on disk if max-age is less than 5 minutes

    View Slide

  83. NSURLCache Gotchas
    • will not cache on disk if max-age is less than 5 minutes
    • might not cache at all if Cache-Control isn't passed

    View Slide

  84. NSURLCache Gotchas
    • will not cache on disk if max-age is less than 5 minutes
    • might not cache at all if Cache-Control isn't passed
    • might choose an arbitrarily large max-age if none provided
    *

    View Slide

  85. NSURLCache Gotchas
    • will not cache on disk if max-age is less than 5 minutes
    • might not cache at all if Cache-Control isn't passed
    • might choose an arbitrarily large max-age if none provided
    *
    • might not cache if size > 5% of available capacity

    View Slide

  86. Tuning the built-in cache
    let MB = 1024 * 1024
    var cache = NSURLCache(memoryCapacity: 10 * MB,
    diskCapacity: 50 * MB,
    diskPath: nil)
    sessionConfiguration.URLCache = cache

    View Slide

  87. Default Cache Location
    • ~/Library/Caches/com.acme.app/Cache.db

    View Slide

  88. View Slide

  89. What do I have to do?

    View Slide

  90. Maybe nothing!
    • Server should return appropriate cache headers
    • Tweak caching behavior in willCacheResponse delegate
    method

    View Slide

  91. The Content Flicker Problem

    View Slide

  92. The Content Flicker Problem
    • Data is already cached and fresh with E-Tag / IMS info
    • Client must validate content is still fresh and wait for a 304
    • Response is fast, but not fast to avoid drawing empty screen
    • ...flicker

    View Slide

  93. Resolving the Content Flicker
    Problem

    View Slide

  94. 1. Return cached data immediately (if we have it)
    2. Proceed with Request
    3. Update callback (again) with fresh data

    View Slide

  95. let url = NSURL(string: "http://cache-tester.herokuapp.com/contacts.json")
    let request = NSURLRequest(URL: url)
    var task = session.dataTaskWithRequest(request)
    task.resume()

    View Slide

  96. let url = NSURL(string: "http://cache-tester.herokuapp.com/contacts.json")
    let request = NSURLRequest(URL: url)
    if let cachedResponse = config.URLCache.cachedResponseForRequest(request) {
    // update UI
    processData(cachedResponse.data)
    }
    var task = session.dataTaskWithRequest(request)
    task.resume()

    View Slide

  97. New in iOS 8: getCachedResponseForTask:
    • Provides asynchronous cache fetch:
    let url = NSURL(string: "http://localhost:3000/contacts.json")
    let request = NSURLRequest(URL: url)
    var task = session.dataTaskWithRequest(request)
    config.URLCache.getCachedResponseForDataTask(task) {
    (let cachedResponse: NSCachedURLResponse?) in
    if cachedResponse != nil {
    self.processData(cachedResponse!.data)
    }
    task.resume()
    }

    View Slide

  98. Bonus Round
    API Tips

    View Slide

  99. Don't Expose your
    Internal Model

    View Slide

  100. Version Your API

    View Slide

  101. Send Device Info as
    Headers

    View Slide

  102. Turn on Gzip
    Compression

    View Slide

  103. Measure Response
    Times

    View Slide

  104. Page Unbounded
    Data Sets

    View Slide

  105. Thank You!
    [email protected]
    @subdigital • @nsscreencast
    benscheirman.com • nsscreencast.com
    speakerdeck.com/subdigital/
    ios8-networking

    View Slide