2013-08-08 | Mantle (Cocoaheads Vienna)

715 views

Published on

Introduction to Mantle, a Model framework for Cocoa and Cocoa Touch.

Talk given at Cocoaheads Vienna on Aug. 8, 2013

Published in: Technology
  • Be the first to comment

  • Be the first to like this

2013-08-08 | Mantle (Cocoaheads Vienna)

  1. 1. Mantle https://github.com/github/Mantle Dominik Gruber, @the_dom Cocoaheads Vienna – Aug. 8, 2013
  2. 2. Agenda • • • • Context Core Data Alternatives to Core Data Mantle Dominik Gruber @the_dom
  3. 3. Core Data • • Obvious choice • • Well-documented It works But... • • • It’s slow No direct SQL queries A lot of boilerplate Dominik Gruber @the_dom
  4. 4. Why is Core Data SO slow? • • • No mass update/delete Synchronization between NSManagedObjectContext   No transactions Dominik Gruber @the_dom
  5. 5. Alternatives • • Raw SQLite • • libsqlite3.dylib FMDB NSCoder • Mantle Dominik Gruber @the_dom
  6. 6. NSCoder “(...) the interface used by concrete subclasses to transfer objects and other Objective-C data items between memory and some other format. This capability provides the basis for archiving and distribution.” Dominik Gruber @the_dom
  7. 7. typedef  enum  :  NSUInteger  {          GHIssueStateOpen,          GHIssueStateClosed   }  GHIssueState;   ! @interface  GHIssue  :  NSObject  <NSCoding,  NSCopying>   ! @property  (nonatomic,  copy,  readonly)  NSURL  *URL;   @property  (nonatomic,  copy,  readonly)  NSURL  *HTMLURL;   @property  (nonatomic,  copy,  readonly)  NSNumber  *number;   @property  (nonatomic,  assign,  readonly)  GHIssueState  state;   @property  (nonatomic,  copy,  readonly)  NSString  *reporterLogin;   @property  (nonatomic,  copy,  readonly)  NSDate  *updatedAt;   @property  (nonatomic,  strong,  readonly)  GHUser  *assignee;   ! @property  (nonatomic,  copy)  NSString  *title;   @property  (nonatomic,  copy)  NSString  *body;   ! -­‐  (id)initWithDictionary:(NSDictionary  *)dictionary;   ! @end Dominik Gruber @the_dom
  8. 8. -­‐  (id)initWithDictionary:(NSDictionary  *)dictionary  {          self  =  [self  init];          if  (self  ==  nil)  return  nil;   !        _URL  =  [NSURL  URLWithString:dictionary[@"url"]];          _HTMLURL  =  [NSURL  URLWithString:dictionary[@"html_url"]];          _number  =  dictionary[@"number"];   !        if  ([dictionary[@"state"]  isEqualToString:@"open"])  {                  _state  =  GHIssueStateOpen;          }  else  if  ([dictionary[@"state"]  isEqualToString:@"closed"])  {                  _state  =  GHIssueStateClosed;          }   !        _title  =  [dictionary[@"title"]  copy];          _body  =  [dictionary[@"body"]  copy];          _reporterLogin  =  [dictionary[@"user"][@"login"]  copy];          _assignee  =  [[GHUser  alloc]   initWithDictionary:dictionary[@"assignee"]];   !        _updatedAt  =  [self.class.dateFormatter   dateFromString:dictionary[@"updated_at"]];   !        return  self;   Dominik Gruber @the_dom
  9. 9. -­‐  (id)initWithCoder:(NSCoder  *)coder  {          self  =  [self  init];          if  (self  ==  nil)  return  nil;   !        _URL  =  [coder  decodeObjectForKey:@"URL"];          _HTMLURL  =  [coder  decodeObjectForKey:@"HTMLURL"];          _number  =  [coder  decodeObjectForKey:@"number"];          _state  =  [coder  decodeUnsignedIntegerForKey:@"state"];          _title  =  [coder  decodeObjectForKey:@"title"];          _body  =  [coder  decodeObjectForKey:@"body"];          _reporterLogin  =  [coder   decodeObjectForKey:@"reporterLogin"];          _assignee  =  [coder  decodeObjectForKey:@"assignee"];          _updatedAt  =  [coder  decodeObjectForKey:@"updatedAt"];   !        return  self;   } Dominik Gruber @the_dom
  10. 10. -­‐  (void)encodeWithCoder:(NSCoder  *)coder  {          if  (self.URL  !=  nil)                  [coder  encodeObject:self.URL  forKey:@"URL"];          if  (self.HTMLURL  !=  nil)                  [coder  encodeObject:self.HTMLURL  forKey:@"HTMLURL"];          if  (self.number  !=  nil)                  [coder  encodeObject:self.number  forKey:@"number"];          if  (self.title  !=  nil)                  [coder  encodeObject:self.title  forKey:@"title"];          if  (self.body  !=  nil)                  [coder  encodeObject:self.body  forKey:@"body"];          if  (self.reporterLogin  !=  nil)                  [coder  encodeObject:self.reporterLogin   forKey:@"reporterLogin"];          if  (self.assignee  !=  nil)                  [coder  encodeObject:self.assignee  forKey:@"assignee"];          if  (self.updatedAt  !=  nil)                  [coder  encodeObject:self.updatedAt  forKey:@"updatedAt"];   !        [coder  encodeUnsignedInteger:self.state  forKey:@"state"];   } Dominik Gruber @the_dom
  11. 11. -­‐  (id)copyWithZone:(NSZone  *)zone  {          GHIssue  *issue  =  [[self.class  allocWithZone:zone]  init];          issue-­‐>_URL  =  self.URL;          issue-­‐>_HTMLURL  =  self.HTMLURL;          issue-­‐>_number  =  self.number;          issue-­‐>_state  =  self.state;          issue-­‐>_reporterLogin  =  self.reporterLogin;          issue-­‐>_assignee  =  self.assignee;          issue-­‐>_updatedAt  =  self.updatedAt;   !        issue.title  =  self.title;          issue.body  =  self.body;   } Dominik Gruber @the_dom
  12. 12. -­‐  (NSUInteger)hash  {          return  self.number.hash;   }   ! -­‐  (BOOL)isEqual:(GHIssue  *)issue  {          if  (![issue  isKindOfClass:GHIssue.class])  return  NO;   !        return  [self.number  isEqual:issue.number]  &&   [self.title  isEqual:issue.title]  &&  [self.body   isEqual:issue.body];   }   Dominik Gruber @the_dom
  13. 13. Mantle • Simple Model Layer for iOS and OS X • Currently Version 1.2 • First release in October 2012 • Developed by GitHub
 https://github.com/github/Mantle Dominik Gruber @the_dom
  14. 14. typedef  enum  :  NSUInteger  {          GHIssueStateOpen,          GHIssueStateClosed   }  GHIssueState;   ! @interface  GHIssue  :  MTLModel  <MTLJSONSerializing>   ! @property  (nonatomic,  copy,  readonly)  NSURL  *URL;   @property  (nonatomic,  copy,  readonly)  NSURL  *HTMLURL;   @property  (nonatomic,  copy,  readonly)  NSNumber  *number;   @property  (nonatomic,  assign,  readonly)  GHIssueState  state;   @property  (nonatomic,  copy,  readonly)  NSString  *reporterLogin;   @property  (nonatomic,  strong,  readonly)  GHUser  *assignee;   @property  (nonatomic,  copy,  readonly)  NSDate  *updatedAt;   ! @property  (nonatomic,  copy)  NSString  *title;   @property  (nonatomic,  copy)  NSString  *body;   ! @end Dominik Gruber @the_dom
  15. 15. @implementation  GHIssue   ! +  (NSDictionary  *)JSONKeyPathsByPropertyKey  {          return  @{                  @"URL":  @"url",                  @"HTMLURL":  @"html_url",                  @"reporterLogin":  @"user.login",                  @"assignee":  @"assignee",                  @"updatedAt":  @"updated_at"          };   }   Dominik Gruber @the_dom
  16. 16. +  (NSValueTransformer  *)URLJSONTransformer  {          return  [NSValueTransformer   valueTransformerForName:MTLURLValueTransformerName];   }   ! +  (NSValueTransformer  *)stateJSONTransformer  {          NSDictionary  *states  =  @{                  @"open":  @(GHIssueStateOpen),                  @"closed":  @(GHIssueStateClosed)          };   !        return  [MTLValueTransformer   reversibleTransformerWithForwardBlock:^(NSString  *str)  {                  return  states[str];          }  reverseBlock:^(NSNumber  *state)  {                  return  [states  allKeysForObject:state].lastObject;          }];   } Dominik Gruber @the_dom
  17. 17. + • • • Automatically implemented • • • • <NSCoding>! <NSCopying>! -­‐isEqual:! -­‐hash   [MTLJSONAdapter  JSONDictionaryFromModel:]   It’s possible to handle interface changes with Mantle Dominik Gruber @the_dom
  18. 18. • • • Bad documentation No persistence Some pitfalls Dominik Gruber @the_dom
  19. 19. Persistence // Persisting NSMutableData *data = [NSMutableData data]; NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; [self encodeWithCoder:archiver]; [archiver finishEncoding]; [data writeToFile:[self storagePath] atomically:YES]; Dominik Gruber @the_dom
  20. 20. Persistence // Loading id item = nil; NSString *path = [self storagePathForItemId:itemId]; if ([[NSFileManager defaultManager] fileExistsAtPath:path]) { NSData *data = [NSData dataWithContentsOfFile:path]; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; item = [[[self class] alloc] initWithCoder:unarchiver]; [unarchiver finishDecoding]; } Dominik Gruber @the_dom
  21. 21. How does it work? // NSKeyValueCoding if (![obj validateValue:&validatedValue forKey:key error:error]) { return NO; } ! if (value != validatedValue) { [obj setValue:validatedValue forKey:key]; } Dominik Gruber @the_dom
  22. 22. Q &A Dominik Gruber @the_dom

×