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

Guillermo Gonzalez

September 04, 2013
Tweet

More Decks by Guillermo Gonzalez

Other Decks in Programming

Transcript

  1. @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
  2. @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
  3. NSError *error = nil; TGRBook *book = [TGRBook modelWithDictionary:@{ @"title"

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

    : @"The Sandman", @"author" : @"Neil Gaiman", @"genres" : @[@"Graphic Novels", @"Fantasy"], ... } error:&error]; TGRBook *anotherBook = [book copy];
  5. 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"];
  6. 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"];
  7. (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)"; }
  8. 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/...", ! ... }
  9. 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/...", ! ... }
  10. 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/...", ! ... }
  11. MTLJSONSerializing protocol Specify how to map properties to JSON keypaths

    @interface TGRBook : MTLModel <MTLJSONSerializing>
  12. 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>
  13. + (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"author" : @"artistName", @"overview"

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

    return @{ @"author" : @"artistName", @"overview" : @"description", @"identifier" : @"trackId", @"title" : @"trackName", @"coverURL" : @"artworkUrl100" }; }
  15. 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" }; }
  16. + (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]; }
  17. +<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]; }
  18. +<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]; }
  19. +<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]; }
  20. Mapping JSON child objects @interface TGRUser : MTLModel <MTLJSONSerializing> @property

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

    (copy, nonatomic, readonly) NSArray *purchasedBooks; @property (copy, nonatomic, readonly) TGRBook *nowReading; @end + (NSValueTransformer *)purchasedBooksJSONTransformer {
  22. 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];
  23. 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]; }
  24. 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 {
  25. 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];
  26. 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]; }
  27. Do we still need Core Data? Of course! Memory efficiency

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

    with large datasets NSFetchRequest is cool! Mantle and Core Data can work together
  29. MTLManagedObjectSerializing protocol Specify the entity name Specify how to map

    properties to managed object attributes @interface TGRBook : MTLModel <MTLManagedObjectSerializing>
  30. 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>
  31. + (NSString *)managedObjectEntityName { return @"Book"; } + (NSDictionary *)managedObjectKeysByPropertyKey

    { return @{ @"coverURL" : @"coverLink" }; } + (NSValueTransformer *)coverURLEntityAttributeTransformer { return [[NSValueTransformer valueTransformerForName:MTLURLValueTransformerName] mtl_invertedTransformer]; }
  32. 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
  33. 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) { ... }];
  34. 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", ! ! ...
  35. 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) { ... }];
  36. 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];
  37. Mapping can potentially take time NSArray *results = JSONResponse[@"results"]; NSValueTransformer

    *transformer; transformer = [NSValueTransformer mtl_JSONArrayTransformerWithModelClass:TGRBook.class]; NSArray *books = [transformer transformedValue:results];
  38. 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];
  39. 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];
  40. 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
  41. 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
  42. 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); }];
  43. 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...
  44. 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