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

Better Web Clients with Mantle and AFNetworking

Better Web Clients with Mantle and AFNetworking

52edc3d953e8df9d23c2943542f151bc?s=128

Guillermo Gonzalez

September 04, 2013
Tweet

Transcript

  1. Better Web Clients with Mantle & AFNetworking

  2. Guillermo González @gonzalezreal

  3. Mantle Model framework for Cocoa and Cocoa Touch

  4. https://github.com/github/Mantle

  5. Makes it easy to write a simple model layer

  6. Gets rid of boilerplate code

  7. Gets rid of boilerplate code -hash

  8. Gets rid of boilerplate code -hash -isEqual:

  9. Gets rid of boilerplate code -hash -isEqual: NSCopying

  10. Gets rid of boilerplate code -hash -isEqual: NSCopying NSCoding

  11. @interface TGRBook : MTLModel @property (copy, nonatomic, readonly) NSString *author;

    @property (copy, nonatomic, readonly) NSString *overview; @property (copy, nonatomic, readonly) NSArray *genres; @property (copy, nonatomic, readonly) NSDate *releaseDate; @property (copy, nonatomic, readonly) NSNumber *identifier; @property (copy, nonatomic, readonly) NSString *title; @property (copy, nonatomic, readonly) NSURL *coverURL; @end Example: Book Model
  12. @interface TGRBook : MTLModel @property (copy, nonatomic, readonly) NSString *author;

    @property (copy, nonatomic, readonly) NSString *overview; @property (copy, nonatomic, readonly) NSArray *genres; @property (copy, nonatomic, readonly) NSDate *releaseDate; @property (copy, nonatomic, readonly) NSNumber *identifier; @property (copy, nonatomic, readonly) NSString *title; @property (copy, nonatomic, readonly) NSURL *coverURL; @end Example: Book Model Prefer immutable model objects
  13. None
  14. NSError *error = nil; TGRBook *book = [TGRBook modelWithDictionary:@{ @"title"

    : @"The Sandman", @"author" : @"Neil Gaiman", @"genres" : @[@"Graphic Novels", @"Fantasy"], ... } error:&error];
  15. NSError *error = nil; TGRBook *book = [TGRBook modelWithDictionary:@{ @"title"

    : @"The Sandman", @"author" : @"Neil Gaiman", @"genres" : @[@"Graphic Novels", @"Fantasy"], ... } error:&error]; TGRBook *anotherBook = [book copy];
  16. NSError *error = nil; TGRBook *book = [TGRBook modelWithDictionary:@{ @"title"

    : @"The Sandman", @"author" : @"Neil Gaiman", @"genres" : @[@"Graphic Novels", @"Fantasy"], ... } error:&error]; TGRBook *anotherBook = [book copy]; [NSKeyedArchiver archiveRootObject:book toFile:@"my_file"];
  17. NSError *error = nil; TGRBook *book = [TGRBook modelWithDictionary:@{ @"title"

    : @"The Sandman", @"author" : @"Neil Gaiman", @"genres" : @[@"Graphic Novels", @"Fantasy"], ... } error:&error]; TGRBook *anotherBook = [book copy]; [NSKeyedArchiver archiveRootObject:book toFile:@"my_file"]; TGRBook *b = [NSKeyedUnarchiver unarchiveObjectWithFile:@"my_file"];
  18. (lldb) po book

  19. (lldb) po book $0 = 0x0a349050 <TGRBook: 0xa349050> { author

    = "Neil Gaiman, Sam Keith & Mike Dringenberg"; coverURL = "http://a4.mzstatic.com/us/r30/Publication/..."; genres = ( "Graphic Novels", Books, "Comics & Graphic Novels" ); identifier = 554016043; overview = "<p>NEW YORK TIMES bestselling author Neil Gaiman's..."; releaseDate = "2012-08-21 05:00:00 +0000"; title = "The Sandman, Vol. 1: Preludes & Nocturnes (New Edition)"; }
  20. All this just by subclassing MTLModel

  21. Our Book Model could have a JSON representation

  22. Our Book Model could have a JSON representation { !

    ... ! "artistName": "Neil Gaiman, Sam Keith & Mike Dringenberg", ! "description": "<p>NEW YORK TIMES bestselling author...", ! "genres": ["Graphic Novels", "Books", "Comics & Graphic Novels"], ! "releaseDate": "2012-08-21T07:00:00Z", ! "trackId": 554016043, ! "trackName": "The Sandman, Vol. 1: Preludes & Nocturnes (New Edition)", ! "artworkUrl100": "http://a4.mzstatic.com/us/r30/Publication/...", ! ... }
  23. Our Book Model could have a JSON representation This is

    how iTunes represents media (including books) { ! ... ! "artistName": "Neil Gaiman, Sam Keith & Mike Dringenberg", ! "description": "<p>NEW YORK TIMES bestselling author...", ! "genres": ["Graphic Novels", "Books", "Comics & Graphic Novels"], ! "releaseDate": "2012-08-21T07:00:00Z", ! "trackId": 554016043, ! "trackName": "The Sandman, Vol. 1: Preludes & Nocturnes (New Edition)", ! "artworkUrl100": "http://a4.mzstatic.com/us/r30/Publication/...", ! ... }
  24. Our Book Model could have a JSON representation This is

    how iTunes represents media (including books) See http://www.apple.com/itunes/affiliates/resources/ documentation/itunes-store-web-service-search-api.html { ! ... ! "artistName": "Neil Gaiman, Sam Keith & Mike Dringenberg", ! "description": "<p>NEW YORK TIMES bestselling author...", ! "genres": ["Graphic Novels", "Books", "Comics & Graphic Novels"], ! "releaseDate": "2012-08-21T07:00:00Z", ! "trackId": 554016043, ! "trackName": "The Sandman, Vol. 1: Preludes & Nocturnes (New Edition)", ! "artworkUrl100": "http://a4.mzstatic.com/us/r30/Publication/...", ! ... }
  25. MTLJSONSerializing protocol @interface TGRBook : MTLModel <MTLJSONSerializing>

  26. MTLJSONSerializing protocol Specify how to map properties to JSON keypaths

    @interface TGRBook : MTLModel <MTLJSONSerializing>
  27. MTLJSONSerializing protocol Specify how to map properties to JSON keypaths

    Specify how to convert a JSON value to a property key @interface TGRBook : MTLModel <MTLJSONSerializing>
  28. + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"author" : @"artistName", @"overview"

    : @"description", @"identifier" : @"trackId", @"title" : @"trackName", @"coverURL" : @"artworkUrl100" }; }
  29. Only properties with a corresponding ivar + (NSDictionary *)JSONKeyPathsByPropertyKey {

    return @{ @"author" : @"artistName", @"overview" : @"description", @"identifier" : @"trackId", @"title" : @"trackName", @"coverURL" : @"artworkUrl100" }; }
  30. Only properties with a corresponding ivar Property keys not present

    in the dictionary are assumed to match the JSON + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"author" : @"artistName", @"overview" : @"description", @"identifier" : @"trackId", @"title" : @"trackName", @"coverURL" : @"artworkUrl100" }; }
  31. + (NSValueTransformer *)releaseDateJSONTransformer { return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) { return

    [self.dateFormatter dateFromString:str]; } reverseBlock:^(NSDate *date) { return [self.dateFormatter stringFromDate:date]; }]; } + (NSValueTransformer *)coverURLJSONTransformer { return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName]; }
  32. +<key>JSONTransformer methods + (NSValueTransformer *)releaseDateJSONTransformer { return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str)

    { return [self.dateFormatter dateFromString:str]; } reverseBlock:^(NSDate *date) { return [self.dateFormatter stringFromDate:date]; }]; } + (NSValueTransformer *)coverURLJSONTransformer { return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName]; }
  33. +<key>JSONTransformer methods MTLValueTransformer is a block-based value transformer + (NSValueTransformer

    *)releaseDateJSONTransformer { return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) { return [self.dateFormatter dateFromString:str]; } reverseBlock:^(NSDate *date) { return [self.dateFormatter stringFromDate:date]; }]; } + (NSValueTransformer *)coverURLJSONTransformer { return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName]; }
  34. +<key>JSONTransformer methods MTLValueTransformer is a block-based value transformer Predefined transformers:

    MTLURLValueTransformerName and MTLBooleanValueTransformerName + (NSValueTransformer *)releaseDateJSONTransformer { return [MTLValueTransformer reversibleTransformerWithForwardBlock:^(NSString *str) { return [self.dateFormatter dateFromString:str]; } reverseBlock:^(NSDate *date) { return [self.dateFormatter stringFromDate:date]; }]; } + (NSValueTransformer *)coverURLJSONTransformer { return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName]; }
  35. Mapping JSON child objects

  36. Mapping JSON child objects @interface TGRUser : MTLModel <MTLJSONSerializing> @property

    (copy, nonatomic, readonly) NSArray *purchasedBooks; @property (copy, nonatomic, readonly) TGRBook *nowReading; @end
  37. Mapping JSON child objects @interface TGRUser : MTLModel <MTLJSONSerializing> @property

    (copy, nonatomic, readonly) NSArray *purchasedBooks; @property (copy, nonatomic, readonly) TGRBook *nowReading; @end + (NSValueTransformer *)purchasedBooksJSONTransformer {
  38. Mapping JSON child objects @interface TGRUser : MTLModel <MTLJSONSerializing> @property

    (copy, nonatomic, readonly) NSArray *purchasedBooks; @property (copy, nonatomic, readonly) TGRBook *nowReading; @end + (NSValueTransformer *)purchasedBooksJSONTransformer { return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class];
  39. Mapping JSON child objects @interface TGRUser : MTLModel <MTLJSONSerializing> @property

    (copy, nonatomic, readonly) NSArray *purchasedBooks; @property (copy, nonatomic, readonly) TGRBook *nowReading; @end + (NSValueTransformer *)purchasedBooksJSONTransformer { return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; }
  40. Mapping JSON child objects @interface TGRUser : MTLModel <MTLJSONSerializing> @property

    (copy, nonatomic, readonly) NSArray *purchasedBooks; @property (copy, nonatomic, readonly) TGRBook *nowReading; @end + (NSValueTransformer *)purchasedBooksJSONTransformer { return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; } + (NSValueTransformer *)nowReadingJSONTransformer {
  41. Mapping JSON child objects @interface TGRUser : MTLModel <MTLJSONSerializing> @property

    (copy, nonatomic, readonly) NSArray *purchasedBooks; @property (copy, nonatomic, readonly) TGRBook *nowReading; @end + (NSValueTransformer *)purchasedBooksJSONTransformer { return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; } + (NSValueTransformer *)nowReadingJSONTransformer { return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:TGRBook.class];
  42. Mapping JSON child objects @interface TGRUser : MTLModel <MTLJSONSerializing> @property

    (copy, nonatomic, readonly) NSArray *purchasedBooks; @property (copy, nonatomic, readonly) TGRBook *nowReading; @end + (NSValueTransformer *)purchasedBooksJSONTransformer { return [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; } + (NSValueTransformer *)nowReadingJSONTransformer { return [NSValueTransformer mtl_JSONDictionaryTransformerWithModelClass:TGRBook.class]; }
  43. MTLJSONAdapter TGRBook *book = [MTLJSONAdapter modelOfClass:TGRBook.class fromJSONDictionary:JSONDictionary error:&error]; NSDictionary *dictionary

    = [MTLJSONAdapter JSONDictionaryFromModel:book];
  44. Do we still need Core Data?

  45. Do we still need Core Data? Of course!

  46. Do we still need Core Data? Of course! Memory efficiency

    with large datasets
  47. Do we still need Core Data? Of course! Memory efficiency

    with large datasets NSFetchRequest is cool!
  48. Do we still need Core Data? Of course! Memory efficiency

    with large datasets NSFetchRequest is cool! Mantle and Core Data can work together
  49. An entity for our Book Model

  50. MTLManagedObjectSerializing protocol @interface TGRBook : MTLModel <MTLManagedObjectSerializing>

  51. MTLManagedObjectSerializing protocol Specify the entity name @interface TGRBook : MTLModel

    <MTLManagedObjectSerializing>
  52. MTLManagedObjectSerializing protocol Specify the entity name Specify how to map

    properties to managed object attributes @interface TGRBook : MTLModel <MTLManagedObjectSerializing>
  53. MTLManagedObjectSerializing protocol Specify the entity name Specify how to map

    properties to managed object attributes Specify how to convert a managed object attribute value to a property key @interface TGRBook : MTLModel <MTLManagedObjectSerializing>
  54. + (NSString *)managedObjectEntityName { return @"Book"; } + (NSDictionary *)managedObjectKeysByPropertyKey

    { return @{ @"coverURL" : @"coverLink" }; } + (NSValueTransformer *)coverURLEntityAttributeTransformer { return [[NSValueTransformer valueTransformerForName:MTLURLValueTransformerName] mtl_invertedTransformer]; }
  55. MTLManagedObjectAdapter NSManagedObject *managedBook = [MTLManagedObjectAdapter managedObjectFromModel:book insertingIntoContext:moc error:&error]; TGRBook *book

    = [MTLManagedObjectAdapter modelOfClass:TGRBook.class fromManagedObject:object error:&error];
  56. A delightful iOS and OS X networking framework Built on

    top of Foundation technologies Easy to use block-based API Amazing community of developers Used by some of the most popular apps on iOS and OSX https://github.com/AFNetworking/AFNetworking
  57. Example: Search books NSURL *url = [NSURL URLWithString:@"https://itunes.apple.com"]; AFHTTPClient *client

    = [[AFHTTPClient alloc] initWithBaseURL:url]; [client getPath:@"search" parameters:@{ @"term" : @"the sandman", @"entity" : @"ebook" } success:^(AFHTTPRequestOperation *operation, id JSONResponse) { NSLog(@"Search results: %@", JSONResponse); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { ... }];
  58. JSON Response { ! "resultCount": 50, ! "results": [{ !

    ! "artistId": 3603584, ! ! "artistName": "Neil Gaiman, Sam Kieth & Mike Dringenberg", ! ! "kind": "ebook", ! ! "price": 1.99, ! ! "description": "<p>The first issue of the first volume...", ! ! "currency": "USD", ! ! "genres": ["Graphic Novels", "Books", "Comics & Graphic Novels"], ! ! "genreIds": ["10015", "38", "9026"], ! ! "releaseDate": "2013-05-01T07:00:00Z", ! ! "trackId": 642469670, ! ! "trackName": "Sandman #1", ! ! ...
  59. Let’s add a Mantle [client getPath:@"search" parameters:@{ @"term" : @"Neil

    Gaiman", @"entity" : @"ebook" } success:^(AFHTTPRequestOperation *operation, NSDictionary *JSONResponse) { NSArray *results = JSONResponse[@"results"]; NSValueTransformer *transformer; transformer = [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; NSArray *books = [transformer transformedValue:results]; NSLog(@"Books: %@", books); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { ... }];
  60. Let’s add a Mantle [client getPath:@"search" parameters:@{ @"term" : @"Neil

    Gaiman", @"entity" : @"ebook" } success:^(AFHTTPRequestOperation *operation, NSDictionary *JSONResponse) { NSArray *results = JSONResponse[@"results"]; NSValueTransformer *transformer; transformer = [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; NSArray *books = [transformer transformedValue:results]; NSLog(@"Books: %@", books); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { ... }]; NSArray *results = JSONResponse[@"results"]; NSValueTransformer *transformer; transformer = [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; NSArray *books = [transformer transformedValue:results];
  61. NSArray *results = JSONResponse[@"results"]; NSValueTransformer *transformer; transformer = [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class];

    NSArray *books = [transformer transformedValue:results];
  62. Mapping can potentially take time NSArray *results = JSONResponse[@"results"]; NSValueTransformer

    *transformer; transformer = [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; NSArray *books = [transformer transformedValue:results];
  63. Mapping can potentially take time It should be done in

    a background queue NSArray *results = JSONResponse[@"results"]; NSValueTransformer *transformer; transformer = [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; NSArray *books = [transformer transformedValue:results];
  64. Mapping can potentially take time It should be done in

    a background queue Boilerplate code again! NSArray *results = JSONResponse[@"results"]; NSValueTransformer *transformer; transformer = [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; NSArray *books = [transformer transformedValue:results];
  65. Overcoat The perfect accessory for Mantle Makes it dead simple

    to use Mantle model objects with a RESTful client AFNetworking extension https://github.com/gonzalezreal/Overcoat
  66. Overcoat AFHTTPClient AFJSONRequestOperation AFHTTPRequestOperation OVCRequestOperation OVCClient OVCSocialClient OVCQuery

  67. OVCQuery *query = [OVCQuery queryWithMethod:OVCQueryMethodGet path:@"search" parameters:@{ @"term" : term,

    @"entity" : @"ebook" } modelClass:TGRBook.class objectKeyPath:@"results"]; [client executeQuery:query completionBlock:^(OVCRequestOperation *operation, NSArray *books, NSError *error) { NSLog(@"Books: %@", books); }]; Overcoat 0.x
  68. Overcoat 1.0

  69. Overcoat 1.0 NSDictionary *parameters = @{ @"term" : term, @"entity"

    : @"ebook" }; [client GET:@"search" parameters:parameters resultClass:TGRBook.class resultKeyPath:@"results" completion:^(AFHTTPRequestOperation *operation, NSArray *books, NSError *error) { NSLog(@"Books: %@", books); }];
  70. Overcoat 1.0 NSDictionary *parameters = @{ @"term" : term, @"entity"

    : @"ebook" }; [client GET:@"search" parameters:parameters resultClass:TGRBook.class resultKeyPath:@"results" completion:^(AFHTTPRequestOperation *operation, NSArray *books, NSError *error) { NSLog(@"Books: %@", books); }]; Coming soon...
  71. Overcoat Server API requests are defined by OVCQuery objects. HTTP

    method, path, parameters, model class, multipart data, etc. Maps JSON into model(s) In a private background queue
  72. Overcoat Pull Requests are welcome!

  73. Demo Application https://github.com/gonzalezreal/ReadingList

  74. Thanks!