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

iOS 8 Networking

iOS 8 Networking

A talk on iOS Networking, HTTP, Caching. Continually evolving.

Ben Scheirman

November 15, 2014
Tweet

More Decks by Ben Scheirman

Other Decks in Programming

Transcript

  1. iOS$8$Networking

    View Slide

  2. Ben$Scheirman
    benscheirman.com
    @subdigital

    View Slide

  3. ChaiOne

    View Slide

  4. nsscreencast.com
    @nsscreencast

    View Slide

  5. Agenda
    • NSURLSession"vs."NSURLConnection
    • NSURLSession"usage
    • Live"Demos"!
    • HTTP
    • Caching
    • Bonus"Round:"API"Tips

    View Slide

  6. NSURLSession
    vs.
    NSURLConnec+on

    View Slide

  7. View Slide

  8. View Slide

  9. Design
    • NSURLSessionConfigura2on
    • NSURLSession
    • NSURLSessionDataTask

    View Slide

  10. View Slide

  11. View Slide

  12. Basic&Usage
    • Create'config
    • Create'session
    • Build'a'task'(request)
    • Call'.resume()
    • Handle'response'in'callback'or'delegate

    View Slide

  13. NSURLSessionConfiguration

    View Slide

  14. + defaultSessionConfiguration

    View Slide

  15. + ephemeralSessionConfiguration
    ?

    View Slide

  16. "private)browsing"

    View Slide

  17. + backgroundSessionConfigurationWithIdentifier()

    View Slide

  18. Customize*it*further
    • All$of$these$class$methods$return$a$copy
    • Tweak$to$your$use$case

    View Slide

  19. Well,%what%can%you%
    do%with%it?

    View Slide

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

    View Slide

  21. Mark%Requests%as%low/priority
    config.discretionary = true
    • Note:&This&has&benefits,&such&as&retrying&when&connec6on&
    terminates,&avoiding&request&if&user&is&low&on&ba=ery,&or&if&Wi?Fi&
    performance&is&not&good&enough.

    View Slide

  22. Disable(Cellular
    config.allowsCellular = false

    View Slide

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

    View Slide

  24. Inject'your'own'custom'protocols!

    View Slide

  25. ben://awww.yeah

    View Slide

  26. (actually(quite(useful(for(mocking(
    requests)

    View Slide

  27. Next%Step:%build%an%NSURLSession%
    to%make%requests

    View Slide

  28. var session = NSURLSession(configuration: config)

    View Slide

  29. 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

  30. 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*comple=on*handler*blocks,*
    delegate*methods*are*not*called

    View Slide

  31. NSURLSessionTask

    View Slide

  32. View Slide

  33. NSURLSessionDataTask
    • Represents)GET,)PUT,)POST,)DELETE
    • Handle)Comple;on)Callback
    • If)error)is)present,)request)failed

    View Slide

  34. What%Cons*tutes%an%Error?
    • Connec'on(failed
    • Timeouts
    • Host(invalid
    • Bad(URL
    • Too(many(redirects
    • ...(dozens(more((check(URL(Loading(System(Error(Codes)

    View Slide

  35. 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+comple@on+block
    • MUST+move+it+somewhere+if+you+want+it+to+s@ck+around

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  39. 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

  40. 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

  41. NSURLSessionDelegate
    Provide(a(delegate(if(you(need(more(advanced(control(over:
    • Download)Progress
    • Authen3ca3on)Challenges
    • Connec3on)Failure

    View Slide

  42. NSURLSessionDelegate

    View Slide

  43. View Slide

  44. Demo:
    GET$/$POST$JSON$Data
    Searching*the*iTunes*Store

    View Slide

  45. Case%Study:%Reques.ng%Images

    View Slide

  46. 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

  47. View Slide

  48. 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

  49. These%are%blocking%calls
    → NSData *imageData = [NSData
    dataWithContentsOfURL:imageUrl];
    → UIImage *image = [UIImage imageWithData:imageData];

    View Slide

  50. View Slide

  51. Demo:
    Downloading*Images

    View Slide

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

    View Slide

  53. Demo:
    Resumable)Downloads

    View Slide

  54. Resume&Data
    Can$be$stored$on$disk$to$be$reused$later

    View Slide

  55. What%about%UITableViewCell%images?

    View Slide

  56. What%about%UICollec/onView%images?

    View Slide

  57. What%about%any%UIImageView?

    View Slide

  58. Let's&build&a&useful&thing.

    View Slide

  59. Requirements
    • Drop&in)solu-on,)work)for)any)UIImageView
    • Load)image)from)an)NSURL
    • Provide)op-onal)placeholderImage

    View Slide

  60. import UIKit
    import ObjectiveC
    extension UIImageView {
    func loadImageFromURL(url: NSURL, placeholderImage: UIImage? = nil) {
    }
    }

    View Slide

  61. import UIKit
    import ObjectiveC
    extension UIImageView {
    func loadImageFromURL(url: NSURL, placeholderImage: UIImage? = nil) {
    var session = ... // where do we store this?
    }
    }

    View Slide

  62. #import

    View Slide

  63. import'Objec-veC

    View Slide

  64. Get$/$set$value$associated$with$the$class$
    instance
    var session: NSURLSession? {
    get {
    return objc_getAssociatedObject(UIImageView.self, &sessionKey)
    as NSURLSession?
    }
    set {
    objc_setAssociatedObject(UIImageView.self, &sessionKey, newValue,
    UInt(OBJC_ASSOCIATION_RETAIN))
    }
    }

    View Slide

  65. Tracking)the)task)for)each)cell)object
    var imageTask: NSURLSessionTask? {
    get {
    return objc_getAssociatedObject(self, &taskKey)
    as NSURLSessionTask?
    }
    set {
    objc_setAssociatedObject(self, &taskKey, newValue,
    UInt(OBJC_ASSOCIATION_RETAIN))
    }
    }

    View Slide

  66. func loadImageFromURL(url: NSURL, placeholderImage: UIImage? = nil) {
    if session == nil {
    session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
    }
    // cancel any previous in-flight request
    imageTask?.cancel()
    // show the placeholder while we're loading
    image = placeholder
    // ...
    }

    View Slide

  67. imageTask = session?.dataTaskWithURL(url) {
    (data, response, error) in
    if error == nil {
    // handle success case
    } else {
    println("error with \(url): \(error)")
    }
    self.imageTask = nil
    }
    imageTask?.resume()

    View Slide

  68. let http = response as NSHTTPURLResponse
    if http.statusCode == 200 {
    // have we started loading a different image yet?
    if self.imageTask?.state == NSURLSessionTaskState.Canceling {
    return
    }
    let image = UIImage(data: data)
    dispatch_async(dispatch_get_main_queue()) {
    self.image = image
    }
    } else {
    println("\(url) received HTTP \(http.statusCode)")
    }

    View Slide

  69. Usage&is&easy:
    cell.imageView.loadImageFromURL(imageUrl)

    View Slide

  70. Demo:
    Load%Search%Result%Images

    View Slide

  71. Further'Improvements
    • Fast&caching
    • Post.processing
    • Anima4ng&presenta4on

    View Slide

  72. HTTP

    View Slide

  73. Status&Code&Cheat&Sheet
    1xx!"!Informa*onal!/!Transient
    2xx!"!A"OK!!
    3xx!"!It's!over!there!"
    4xx!"!You!Messed!up!✋
    5xx!"!We!Messed!Up!$

    View Slide

  74. HTTP$Caching

    View Slide

  75. HTTP$Caching$Primi.ves
    • If$Modified$Since
    • If$None$Match
    • Cache$Control

    View Slide

  76. 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

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

    View Slide

  78. Cache&Control
    • Server&indicates&when&the&resource&expires
    • Client&caches&the&data&un6l&that&6me
    • Client&will&immediately&return&local&cache&data&if&s6ll&fresh

    View Slide

  79. What%does%it%look%like%on%the%client?

    View Slide

  80. View Slide

  81. View Slide

  82. View Slide

  83. What%does%it%look%like%on%the%server?

    View Slide

  84. 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

  85. Observa(ons
    • Server&s(ll&executes&a&query&to&compute&E5Tag&and&Modified&
    Date
    • No&body&is&transfered&for&a&matching&E5Tag&or&Date
    • Client&doesn't&even&make&request&if&Cache5Control&is&used&and&
    content&is&s(ll&fresh

    View Slide

  86. Cache&Control,sounds,perfect
    • Not%everything%is%inherently%cacheable
    • Only%helpful%for%a%single%client,%a"er%the%ini8al%request

    View Slide

  87. View Slide

  88. Reverse&Proxy+Cache+to+the+Rescue

    View Slide

  89. View Slide

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

    View Slide

  91. Pros%/%Cons%of%E+Tag%&%IMS
    • Server&skips&rendering
    • Miniscule&Data&Transfer
    • Can&appear&instantaneous*
    • Server&s:ll&spending&resources&compu:ng&&&comparing&E>Tag&&&
    dates

    View Slide

  92. Reason'About'Your'Data
    • List&of&US&States
    • User’s&Profile
    • Customer’s&Order
    • Ac9vity&Timeline
    • Yesterday’s&stats

    View Slide

  93. Tradeoff(between
    fresh&data&and&user&experience

    View Slide

  94. Caching(with(NSURLSession

    View Slide

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

    View Slide

  96. NSURLCache!Gotchas

    View Slide

  97. NSURLCache!Gotchas
    • will%not%cache%on%disk%if%max2age%is%less%than%5%minutes

    View Slide

  98. NSURLCache!Gotchas
    • will%not%cache%on%disk%if%max2age%is%less%than%5%minutes
    • might%not%cache%at%all%if%Cache)Control%isn't%passed

    View Slide

  99. NSURLCache!Gotchas
    • will%not%cache%on%disk%if%max2age%is%less%than%5%minutes
    • might%not%cache%at%all%if%Cache)Control%isn't%passed
    • might%choose%an%arbitrarily%large%max2age%if%none%provided%*

    View Slide

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

    View Slide

  101. View Slide

  102. 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

  103. Default(Cache(Loca.on
    • ~/Library/Caches/com.acme.app/Cache.db

    View Slide

  104. View Slide

  105. What%do%I%have%to%do?

    View Slide

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

    View Slide

  107. The$Content$Flicker$Problem

    View Slide

  108. The$Content$Flicker$Problem
    • Data%is%already%cached%and%fresh%with%E3Tag%/%IMS%info
    • Client%must%validate%content%is%s=ll%fresh%and%wait%for%a%304
    • Response%is%fast,%but%not%fast%to%avoid%drawing%empty%screen
    • ...flicker

    View Slide

  109. Resolving*the*Content*Flicker*Problem

    View Slide

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

    View Slide

  111. 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

  112. 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

  113. 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

  114. Bonus&Round
    API$Tips

    View Slide

  115. Don't&Expose&your&Internal&Model

    View Slide

  116. Version(Your(API

    View Slide

  117. Send%Device%Info%as%Headers

    View Slide

  118. Turn%on%Gzip%Compression

    View Slide

  119. Measure'Response'Times

    View Slide

  120. Page%Unbounded%Data%Sets

    View Slide

  121. Thank&You!
    [email protected]
    @subdigital+•+@nsscreencast
    benscheirman.com-•-nsscreencast.com
    speakerdeck.com/subdigital/ios84networking
    github.com/subdigital/cocoa0conf0
    boston02014

    View Slide