Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS 8 Networking

iOS 8 Networking

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

023a6a37e8177cb2f84a236bbce643cf?s=128

Ben Scheirman

November 15, 2014
Tweet

Transcript

  1. iOS$8$Networking

  2. Ben$Scheirman benscheirman.com @subdigital

  3. ChaiOne

  4. nsscreencast.com @nsscreencast

  5. Agenda • NSURLSession"vs."NSURLConnection • NSURLSession"usage • Live"Demos"! • HTTP •

    Caching • Bonus"Round:"API"Tips
  6. NSURLSession vs. NSURLConnec+on

  7. None
  8. None
  9. Design • NSURLSessionConfigura2on • NSURLSession • NSURLSessionDataTask

  10. None
  11. None
  12. Basic&Usage • Create'config • Create'session • Build'a'task'(request) • Call'.resume() •

    Handle'response'in'callback'or'delegate
  13. NSURLSessionConfiguration

  14. + defaultSessionConfiguration

  15. + ephemeralSessionConfiguration ?

  16. "private)browsing"

  17. + backgroundSessionConfigurationWithIdentifier()

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

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

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

    "1234abcd" ]
  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.

  22. Disable(Cellular config.allowsCellular = false

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

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

  25. ben://awww.yeah

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

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

  28. var session = NSURLSession(configuration: config)

  29. var session = NSURLSession(configuration: config) or#with#a#delegate... var delegate: NSURLSessionDelegate =

    self var session = NSURLSession(configuration: config, delegate: delegate, delegateQueue: NSOperationQueue.mainQueue())
  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

  31. NSURLSessionTask

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

  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)
  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
  36. Building(a(simple(GET(Request • Build'an'instance'of'NSURLSessionDataTask • (op2onally)'give'it'a'block'callback* • Call'resume

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

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

    url = NSURL(string: "https://www.nsscreencast.com/api/episodes.json")
  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 // ... }
  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()
  41. NSURLSessionDelegate Provide(a(delegate(if(you(need(more(advanced(control(over: • Download)Progress • Authen3ca3on)Challenges • Connec3on)Failure

  42. NSURLSessionDelegate

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

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

  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];
  47. None
  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];
  49. These%are%blocking%calls → NSData *imageData = [NSData dataWithContentsOfURL:imageUrl]; → UIImage *image

    = [UIImage imageWithData:imageData];
  50. None
  51. Demo: Downloading*Images

  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

  53. Demo: Resumable)Downloads

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

  55. What%about%UITableViewCell%images?

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

  57. What%about%any%UIImageView?

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

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

  60. import UIKit import ObjectiveC extension UIImageView { func loadImageFromURL(url: NSURL,

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

    placeholderImage: UIImage? = nil) { var session = ... // where do we store this? } }
  62. #import <objc/runtime.h>

  63. import'Objec-veC

  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)) } }
  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)) } }
  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 // ... }
  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()
  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)") }
  69. Usage&is&easy: cell.imageView.loadImageFromURL(imageUrl)

  70. Demo: Load%Search%Result%Images

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

  72. HTTP

  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!$

  74. HTTP$Caching

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

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

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

  80. None
  81. None
  82. None
  83. What%does%it%look%like%on%the%server?

  84. def show @band = Band.find(params[:id]) fresh_when(:etag => @band, :last_modified =>

    @band, :public => true) expires_in 10.minutes, :public => true end
  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

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

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

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

  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

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

    Yesterday’s&stats
  93. Tradeoff(between fresh&data&and&user&experience

  94. Caching(with(NSURLSession

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

  96. NSURLCache!Gotchas

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

  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

  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%*

  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

  101. None
  102. Tuning&the&built,in&cache let MB = 1024 * 1024 var cache =

    NSURLCache(memoryCapacity: 10 * MB, diskCapacity: 50 * MB, diskPath: nil) sessionConfiguration.URLCache = cache
  103. Default(Cache(Loca.on • ~/Library/Caches/com.acme.app/Cache.db

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

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

  107. The$Content$Flicker$Problem

  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

  109. Resolving*the*Content*Flicker*Problem

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

  111. let url = NSURL(string: "http://cache-tester.herokuapp.com/contacts.json") let request = NSURLRequest(URL: url)

    var task = session.dataTaskWithRequest(request) task.resume()
  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()
  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() }
  114. Bonus&Round API$Tips

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

  116. Version(Your(API

  117. Send%Device%Info%as%Headers

  118. Turn%on%Gzip%Compression

  119. Measure'Response'Times

  120. Page%Unbounded%Data%Sets

  121. Thank&You! ben@scheirman.com @subdigital+•+@nsscreencast benscheirman.com-•-nsscreencast.com speakerdeck.com/subdigital/ios84networking github.com/subdigital/cocoa0conf0 boston02014