NO APP IS AN ISLAND Stewart Gleadow @stewgleadow - - PowerPoint PPT Presentation

no app is an island
SMART_READER_LITE
LIVE PREVIEW

NO APP IS AN ISLAND Stewart Gleadow @stewgleadow - - PowerPoint PPT Presentation

NO APP IS AN ISLAND Stewart Gleadow @stewgleadow stew@rea-group.com http://www.flickr.com/photos/markselliott/48699538/ Mobile APIs: More than just object.to_json Stewart Gleadow @stewgleadow stew@rea-group.com


slide-1
SLIDE 1

Stewart Gleadow @stewgleadow stew@rea-group.com

http://www.flickr.com/photos/markselliott/48699538/

NO APP IS AN ISLAND

slide-2
SLIDE 2

Mobile APIs: More than just

  • bject.to_json

Stewart Gleadow @stewgleadow stew@rea-group.com

http://www.flickr.com/photos/markselliott/48699538/

slide-3
SLIDE 3

Stewart Gleadow @stewgleadow stew@rea-group.com

http://www.flickr.com/photos/markselliott/48699538/

NO APP IS AN ISLAND

slide-4
SLIDE 4
slide-5
SLIDE 5

Mobile APIs and REST ›❰ Discoverable iOS APIs ›❰ Evolving APIs and mobile apps ›❰ Simple apps, smart backends ›❰

slide-6
SLIDE 6

Mobile APIs and REST ›❰ Discoverable iOS APIs ›❰ Evolving APIs and mobile apps ›❰ Simple apps, smart backends ›❰

slide-7
SLIDE 7

Is there an API for that? Can I see your API?

slide-8
SLIDE 8

POST /InStock HTTP/1.1 Host: www.example.org Content-Type: application/soap+xml; charset=utf-8 Content-Length: 299 SOAPAction: "http://www.w3.org/2003/05/soap-envelope" <?xml version="1.0"?> <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"> <soap:Header> </soap:Header> <soap:Body> <m:GetStockPrice xmlns:m="http://www.example.org/stock"> <m:StockName>IBM</m:StockName> </m:GetStockPrice> </soap:Body> </soap:Envelope>

slide-9
SLIDE 9

We have a REST API, just construct one

  • f these “RESTful calls”
slide-10
SLIDE 10
slide-11
SLIDE 11
slide-12
SLIDE 12
slide-13
SLIDE 13

http://schmidtbrotherscutlery.com/blog/tag/crocodile-dundee/

THAT’S NOT AN API

http://www.flickr.com/photos/juan-alogico/10935384913/

slide-14
SLIDE 14

http://schmidtbrotherscutlery.com/blog/tag/crocodile-dundee/

THAT’S NOT AN API

http://www.flickr.com/photos/juan-alogico/10935384913/

slide-15
SLIDE 15

How should we build our API?

slide-16
SLIDE 16

Level 0 Level 1 Level 2 Level 3

slide-17
SLIDE 17

Level 0 Level 1 Level 2 Level 3

The swamp of POX

slide-18
SLIDE 18

Level 0 Level 1 Level 2 Level 3

The swamp of POX URIs and Resources

slide-19
SLIDE 19

Level 0 Level 1 Level 2 Level 3

The swamp of POX URIs and Resources HTTP verbs and status codes

slide-20
SLIDE 20

Level 0 Level 1 Level 2 Level 3

The swamp of POX URIs and Resources HTTP verbs and status codes Hypermedia controls

slide-21
SLIDE 21

Roy Fielding - http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

slide-22
SLIDE 22

“What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint?”

Roy Fielding - http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

slide-23
SLIDE 23

“What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint?”

Roy Fielding - http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

“Is there some broken manual somewhere that needs to be fixed?”

slide-24
SLIDE 24

“What needs to be done to make the REST architectural style clear on the notion that hypertext is a constraint?”

Roy Fielding - http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

“Is there some broken manual somewhere that needs to be fixed?” “Please try to adhere to them or choose some other buzzword for your API.”

slide-25
SLIDE 25

http://www.flickr.com/photos/50144889@N08/8711099493/

This guy is possibly an island…

slide-26
SLIDE 26

Mobile APIs and REST ›❰ Discoverable iOS APIs ›❰ Evolving APIs and mobile apps ›❰ Simple apps, smart backends ›❰

slide-27
SLIDE 27

http://www.flickr.com/photos/calsidyrose/4925267732/

Navigating your API

slide-28
SLIDE 28

http://www.flickr.com/photos/calsidyrose/4925267732/

Navigating your API

Paths to resources within a service change The location of service endpoints change

slide-29
SLIDE 29

HAL JSON: Standardized hypermedia links in JSON

slide-30
SLIDE 30

Example: Sign in and retrieve user details

slide-31
SLIDE 31

POST “http://example.com/session” { email : "dundee", password : "password" } HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd” }

Sign in request Sign in response

slide-32
SLIDE 32

POST “http://example.com/session” { email : "dundee", password : "password" } HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd” }

Which actions are available now? Where do I perform those actions?

Sign in request Sign in response

slide-33
SLIDE 33

POST “http://example.com/session” { email : "dundee", password : "password" } HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd” }

Which actions are available now? Where do I perform those actions?

Sign in request Sign in response

slide-34
SLIDE 34

NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password" }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil];

Sign in & get details from an iOS client

slide-35
SLIDE 35

NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password" }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.userId]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil];

Sign in & get details from an iOS client

slide-36
SLIDE 36

NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password" }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.userId]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil];

Sign in & get details from an iOS client

slide-37
SLIDE 37

NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password" }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.userId]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil];

Sign in & get details from an iOS client

slide-38
SLIDE 38

NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password" }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.userId]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil];

Sign in & get details from an iOS client

slide-39
SLIDE 39

Response

HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd”, _links : { details : { href : “http://example.com/users/c3Rld0ByZWEtZ3Jvd/details” } } }

slide-40
SLIDE 40

Response

HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd”, _links : { details : { href : “http://example.com/users/c3Rld0ByZWEtZ3Jvd/details” } } }

Which actions are available now? Where do I perform those actions?

slide-41
SLIDE 41

Response

HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd”, _links : { details : { href : “http://example.com/users/c3Rld0ByZWEtZ3Jvd/details” } } }

Which actions are available now? Where do I perform those actions?

slide-42
SLIDE 42

Response

HTTP/1.1 201 Created { user_id : “c3Rld0ByZWEtZ3Jvd”, _links : { details : { href : “http://example.com/users/c3Rld0ByZWEtZ3Jvd/details” } } }

Which actions are available now? Where do I perform those actions?

slide-43
SLIDE 43

NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password" }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.userId = response[@"user_id"]; } failure:nil]; NSString *detailsUrl = [NSString stringWithFormat:@"http://example.com/details?id=%@", self.user_id]; [self.manager GET:detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil];

Sign in & get details from an iOS client

slide-44
SLIDE 44

NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password" }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.detailsUrl = response[@"_links"][@"details"][@"href"]; } failure:nil]; [self.manager GET:self.detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil];

Sign in & get details from an iOS client

slide-45
SLIDE 45

NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password" }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.detailsUrl = response[@"_links"][@"details"][@"href"]; } failure:nil]; [self.manager GET:self.detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil];

Sign in & get details from an iOS client

slide-46
SLIDE 46

NSDictionary *params = @{ @"email" : @"dundee", @"password" : @"password" }; self.manager = [AFHTTPRequestOperationManager manager]; [self.manager POST:@"http://example.com/session" parameters:params success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.detailsUrl = response[@"_links"][@"details"][@"href"]; } failure:nil]; [self.manager GET:self.detailsUrl parameters:nil success:^(AFHTTPRequestOperation *op, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:nil];

Sign in & get details from an iOS client

slide-47
SLIDE 47

Create discoverable service endpoints

http://commons.wikimedia.org/wiki/File:World_Map_1689.JPG

slide-48
SLIDE 48

GET “http://example.com/services” ... HTTP/1.1 200 OK { _links : { signIn : { name : “Authenticate a user”, href : “http://example.com/session” }, suggest : { name : “Retrieve suburb suggestions matching a string”, href : “http://suggest.realestate.com.au/suggest{?query}”, templated : true }, ... } }

slide-49
SLIDE 49

GET “http://example.com/services” ... HTTP/1.1 200 OK { _links : { signIn : { name : “Authenticate a user”, href : “http://example.com/session” }, suggest : { name : “Retrieve suburb suggestions matching a string”, href : “http://suggest.realestate.com.au/suggest{?query}”, templated : true }, ... } }

RFC-6570

(using CSURITemplate for iOS)

slide-50
SLIDE 50

Chaining multiple requests gets messy

slide-51
SLIDE 51

[self.manager GET:@"http://example.com/services" parameters:nil success:^(AFHTTPRequestOperation *op1, NSDictionary *servicesResponse) { NSString *signInEndpoint = servicesResponse[@"_links"][@"signIn"][@"href"]; [self.manager POST:signInEndpoint parameters:params success:^(AFHTTPRequestOperation *op2, NSDictionary *signInResponse) { NSString *detailsEndpoint = signInResponse[@"_links"][@"details"][@"href"]; [self.manager GET:detailsEndpoint parameters:nil success:^(AFHTTPRequestOperation *op3, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:^(AFHTTPRequestOperation *op3, NSError *error) { self.status.text = @"Failed to get user details."; }]; } failure:^(AFHTTPRequestOperation *op2, NSError *error) { self.status.text = @"Failed to sign in."; }]; } failure:^(AFHTTPRequestOperation *op1, NSError *error) { self.status.text = @"Failed to configure service"; }];

Chaining multiple requests gets messy

slide-52
SLIDE 52

[self.manager GET:@"http://example.com/services" parameters:nil success:^(AFHTTPRequestOperation *op1, NSDictionary *servicesResponse) { NSString *signInEndpoint = servicesResponse[@"_links"][@"signIn"][@"href"]; [self.manager POST:signInEndpoint parameters:params success:^(AFHTTPRequestOperation *op2, NSDictionary *signInResponse) { NSString *detailsEndpoint = signInResponse[@"_links"][@"details"][@"href"]; [self.manager GET:detailsEndpoint parameters:nil success:^(AFHTTPRequestOperation *op3, NSDictionary *response) { self.name.text = response[@"name"]; self.phone.text = response[@"phone"]; } failure:^(AFHTTPRequestOperation *op3, NSError *error) { self.status.text = @"Failed to get user details."; }]; } failure:^(AFHTTPRequestOperation *op2, NSError *error) { self.status.text = @"Failed to sign in."; }]; } failure:^(AFHTTPRequestOperation *op1, NSError *error) { self.status.text = @"Failed to configure service"; }];

Chaining multiple requests gets messy

slide-53
SLIDE 53

ReactiveCocoa to the rescue

slide-54
SLIDE 54

NSString *services = @"http://example.com/services"; RACSignal *userDetailsSignal = [[[[[[self.manager rac_getPath:services] map:^NSString *(NSDictionary *response) { return response[@"_links"][@"signIn"][@"href"]; }] flattenMap:^RACSignal *(NSString *signInEndpoint) { return [self.manager rac_postPath:signInEndpoint parameters:params]; }] map:^NSString *(NSDictionary *response) { return response[@"_links"][@"details"][@"href"]; }] flattenMap:^RACSignal *(NSString *detailsEndpoint) { return [self.manager rac_getPath:detailsEndpoint]; }] replayLast];

ReactiveCocoa to the rescue

slide-55
SLIDE 55

NSString *services = @"http://example.com/services"; RACSignal *userDetailsSignal = [[[[[[self.manager rac_getPath:services] map:^NSString *(NSDictionary *response) { return response[@"_links"][@"signIn"][@"href"]; }] flattenMap:^RACSignal *(NSString *signInEndpoint) { return [self.manager rac_postPath:signInEndpoint parameters:params]; }] map:^NSString *(NSDictionary *response) { return response[@"_links"][@"details"][@"href"]; }] flattenMap:^RACSignal *(NSString *detailsEndpoint) { return [self.manager rac_getPath:detailsEndpoint]; }] replayLast];

ReactiveCocoa to the rescue

slide-56
SLIDE 56

Mobile APIs and REST ›❰ Discoverable iOS APIs ›❰ Evolving APIs and mobile apps ›❰ Simple apps, smart backends ›❰

slide-57
SLIDE 57

Some people, when confronted with a problem, think "I know, I'll use versioning." Now they have 2.1.0 problems.

  • Brandon Byars

http://martinfowler.com/articles/enterpriseREST.html

slide-58
SLIDE 58

0% 25.00% 50.00% 75.00% 100.00% 1 Mar 2013 12 Mar 2013 23 Mar 2013 3 Apr 2013

1.12.2 1.11.2 1.11.1 1.11.0 1.10.0

Update rate on iOS 6

slide-59
SLIDE 59

0% 25.00% 50.00% 75.00% 100.00% 26 Sep 2013 9 Oct 2013 22 Oct 2013 4 Nov 2013 17 Nov 2013 30 Nov 2013

2.2.0 2.1.0 2.0.2 2.0.0 1.14.2

Update rate on iOS 7

slide-60
SLIDE 60

agency : { name: ”You Can Trust Me Real Estate”, website: “http://you-can-trust-me-realestate.com.au”, address: { display: “15 Victoria St, Richmond 3121”, street: “15 Victoria St”, suburb: “Richmond”, postcode: “3121”, } }

Tolerant JSON parsing

slide-61
SLIDE 61

agency : { name: ”You Can Trust Me Real Estate”, website: “http://you-can-trust-me-realestate.com.au”, address: { display: “15 Victoria St, Richmond 3121”, street: “15 Victoria St”, suburb: “Richmond”, postcode: “3121”, } } Agency *agency = [Agency new]; agency.name = [json stringValueForKey:@"name"]; agency.address = [json stringValueForKeyPath:@"address.display"];

Tolerant JSON parsing

slide-62
SLIDE 62

Links as remote feature toggles

slide-63
SLIDE 63

Encourage updates to the latest version

slide-64
SLIDE 64

Encourage updates to the latest version

slide-65
SLIDE 65

Listing API User API

Isolate your apps from backend changes

Legacy Thing External System

slide-66
SLIDE 66

Listing API User API Mobile API

Isolate your apps from backend changes

Legacy Thing External System

slide-67
SLIDE 67

http://farm4.staticflickr.com/3007/2655233117_7d5feaa305_o_d.jpg

This guy is almost definitely an island…

slide-68
SLIDE 68

Mobile APIs and REST ›❰ Discoverable iOS APIs ›❰ Evolving APIs and mobile apps ›❰ Simple apps, smart backends ›❰

slide-69
SLIDE 69 http://www.flickr.com/photos/jakecaptive/3205277810

Forcing logic to the server

slide-70
SLIDE 70 http://www.flickr.com/photos/jakecaptive/3205277810

Forcing logic to the server

What if you could only use NSDictionary

  • bjects for your domain model?
slide-71
SLIDE 71

landSize : { size: 1440, units: “metres squared” }

slide-72
SLIDE 72

landSize : { size: 1440, units: “metres squared”, display: “Land size: 1440 m²” }

slide-73
SLIDE 73

landSize : { size: 1440, units: “metres squared”, display: “Land size: 1440 m²” }

Less logic in the iOS client!

slide-74
SLIDE 74

features: {

  • utdoor: [

{garage: 2}, ], indoor: [ “air-conditioning”, “alarm-system”, ... {ensuite: 1} ] }

slide-75
SLIDE 75

features: [ { label: “Outdoor Features”, features: [ “Garage: 2”, ] }, { label: “Indoor Features”, features: [ “Air Conditioning”, “Alarm System”, ... “Ensuite: 1” ] } ]

slide-76
SLIDE 76

<h3>Outdoor Features</h3> <ul> <li>Garage: 2</li> </ul> <h3> Indoor Features </h3> <ul> <li>Air Conditioning</li> <li>Alarm System</li> ... <li>Ensuite: 1</li> </ul>

slide-77
SLIDE 77

Sometimes you need to format data

  • n the client
slide-78
SLIDE 78

Sometimes you need to format data

  • n the client
slide-79
SLIDE 79
slide-80
SLIDE 80

Using an icon font for beds, baths, carparks

slide-81
SLIDE 81

propertyFeatures: { display: “\e021 4 \e022 1 \e023 2” }

Using an icon font for beds, baths, carparks

slide-82
SLIDE 82

http://commons.wikimedia.org/wiki/File:MeditationsMarcusAurelius1811.jpg

Almost found it, wrong author

slide-83
SLIDE 83

Mobile APIs and REST ›❰ Discoverable iOS APIs ›❰ Evolving APIs and mobile apps ›❰ Simple apps, smart backends ›❰

slide-84
SLIDE 84

http://commons.wikimedia.org/wiki/File:Pont_du_Gard_HDR.jpg

Adopt a standard way of linking your data

slide-85
SLIDE 85

Reduce coupling using hypermedia

http://www.flickr.com/photos/peternijenhuis/6959653638/

slide-86
SLIDE 86

Keep the app to just the tip of the iceberg

slide-87
SLIDE 87

Write mobile clients that make future versions easy

http://www.flickr.com/photos/setaou/2935943672/

slide-88
SLIDE 88

Thank you

Stewart Gleadow @stewgleadow stew@rea-group.com

slide-89
SLIDE 89

References

John Donne, No Man Is An Island http://en.wikipedia.org/wiki/Meditation_XVII Martin Fowler, Richardson Maturity Model http://martinfowler.com/articles/richardsonMaturityModel.html Leonard Richardson, Justice Will Take Us Millions Of Intricate Moves http://www.crummy.com/writing/speaking/2008-QCon/act3.html Roy Fielding - REST APIs Must Be Hypertext Driven http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven Leonard Richardson, API Design is Stuck in 2008 http://blog.programmableweb.com/2013/10/07/api-design-is-stuck-in-2008/ Cam Barrie, Design your API, Mother %$^ http://vimeo.com/61342270 Mike Kelly, HAL JSON http://stateless.co/hal_specification.html

slide-90
SLIDE 90

References

Perryn Fowler, Richardson Maturity Model By Example http://prezi.com/1-0gtuokjcqo/rmm-by-example/ The Internet Engineering Task Force (IETF), RFC 6570 - URI Template http://tools.ietf.org/html/rfc6570 Wikipedia, RSDL: RESTful Service Description Language http://en.wikipedia.org/wiki/RSDL Jim Webber, Savas Parastatidis, Ian Robinson; REST in Practice http://restinpractice.com/book/ Leonard Richardson, Sam Ruby; RESTful Web Services http://shop.oreilly.com/product/9780596529260.do Martin Fowler, Tolerant Reader http://martinfowler.com/bliki/TolerantReader.html Ronald Holshausen, Testing Interactions With Web Services Without Integration Tests In Ruby http://techblog.realestate.com.au/testing-interactions-with-web-services-without-integration-tests-in-ruby/

slide-91
SLIDE 91

References

Ian Robinson, Consumer Driven Contracts http://martinfowler.com/articles/consumerDrivenContracts.html Brandon Byars, Enterprise REST http://martinfowler.com/articles/enterpriseREST.html Steve Klabnik, Designing Hypermedia APIs http://www.designinghypermediaapis.com/index.html Jon Moore, Hypermedia APIs http://s3.amazonaws.com/cimlabs/Oredev-Hypermedia-APIs.pdf Sam Ramji, Darwin's Finches, 20th Century Business, and APIs: Evolve your Business Model http://vimeo.com/11511078 Cogenta Systems, CSURITemplate https://github.com/cogenta/CSURITemplate Mattt Thompson, AFNetworking https://github.com/AFNetworking/AFNetworking