Slide 1

Slide 1 text

iOS$8$Networking

Slide 2

Slide 2 text

Ben$Scheirman benscheirman.com @subdigital

Slide 3

Slide 3 text

ChaiOne

Slide 4

Slide 4 text

nsscreencast.com @nsscreencast

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

NSURLSession vs. NSURLConnec+on

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

Design • NSURLSessionConfigura2on • NSURLSession • NSURLSessionDataTask

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

NSURLSessionConfiguration

Slide 14

Slide 14 text

+ defaultSessionConfiguration

Slide 15

Slide 15 text

+ ephemeralSessionConfiguration ?

Slide 16

Slide 16 text

"private)browsing"

Slide 17

Slide 17 text

+ backgroundSessionConfigurationWithIdentifier()

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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¬&good&enough.

Slide 22

Slide 22 text

Disable(Cellular config.allowsCellular = false

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Inject'your'own'custom'protocols!

Slide 25

Slide 25 text

ben://awww.yeah

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

var session = NSURLSession(configuration: config)

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

NSURLSessionTask

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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 // ... }

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

NSURLSessionDelegate

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Case%Study:%Reques.ng%Images

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

Demo: Downloading*Images

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

Demo: Resumable)Downloads

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

What%about%UITableViewCell%images?

Slide 56

Slide 56 text

What%about%UICollec/onView%images?

Slide 57

Slide 57 text

What%about%any%UIImageView?

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

#import

Slide 63

Slide 63 text

import'Objec-veC

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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 // ... }

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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)") }

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Demo: Load%Search%Result%Images

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

HTTP

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

HTTP$Caching

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

No content

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

No content

Slide 88

Slide 88 text

Reverse&Proxy+Cache+to+the+Rescue

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

Caching(with(NSURLSession

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

NSURLCache!Gotchas

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

No content

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

No content

Slide 105

Slide 105 text

What%do%I%have%to%do?

Slide 106

Slide 106 text

Maybe¬hing! • Server&should&return&appropriate&cache&headers • Tweak&caching&behavior&in&willCacheResponse&delegate& method

Slide 107

Slide 107 text

The$Content$Flicker$Problem

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

Resolving*the*Content*Flicker*Problem

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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() }

Slide 114

Slide 114 text

Bonus&Round API$Tips

Slide 115

Slide 115 text

Don't&Expose&your&Internal&Model

Slide 116

Slide 116 text

Version(Your(API

Slide 117

Slide 117 text

Send%Device%Info%as%Headers

Slide 118

Slide 118 text

Turn%on%Gzip%Compression

Slide 119

Slide 119 text

Measure'Response'Times

Slide 120

Slide 120 text

Page%Unbounded%Data%Sets

Slide 121

Slide 121 text

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