2013-08-08 | mantle (cocoaheads vienna)

23
Mantle Dominik Gruber, @the_dom Cocoaheads Vienna – Aug. 8, 2013 https://github.com/github/Mantle

Upload: dominik-gruber

Post on 18-Jan-2015

219 views

Category:

Technology


3 download

DESCRIPTION

Introduction to Mantle, a Model framework for Cocoa and Cocoa Touch. Talk given at Cocoaheads Vienna on Aug. 8, 2013

TRANSCRIPT

Page 1: 2013-08-08 | Mantle (Cocoaheads Vienna)

Mantle

Dominik Gruber, @the_dom Cocoaheads Vienna – Aug. 8, 2013

https://github.com/github/Mantle

Page 2: 2013-08-08 | Mantle (Cocoaheads Vienna)

Agenda

• Context

• Core Data

• Alternatives to Core Data

• Mantle

Dominik Gruber @the_dom

Page 3: 2013-08-08 | Mantle (Cocoaheads Vienna)
Page 4: 2013-08-08 | Mantle (Cocoaheads Vienna)

Core Data

• Obvious choice

• Well-documented

• It works

• But...

• It’s slow

• No direct SQL queries

• A lot of boilerplate

Dominik Gruber @the_dom

Page 5: 2013-08-08 | Mantle (Cocoaheads Vienna)

Why is Core Data SO slow?

• No mass update/delete

• Synchronization between NSManagedObjectContext  

• No transactions

Dominik Gruber @the_dom

Page 6: 2013-08-08 | Mantle (Cocoaheads Vienna)

Alternatives

• Raw SQLite

• libsqlite3.dylib

• FMDB

• NSCoder

• Mantle

Dominik Gruber @the_dom

Page 7: 2013-08-08 | Mantle (Cocoaheads Vienna)

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

Page 8: 2013-08-08 | Mantle (Cocoaheads Vienna)

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

Page 9: 2013-08-08 | Mantle (Cocoaheads Vienna)

-­‐  (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

Page 10: 2013-08-08 | Mantle (Cocoaheads Vienna)

-­‐  (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

Page 11: 2013-08-08 | Mantle (Cocoaheads Vienna)

-­‐  (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

Page 12: 2013-08-08 | Mantle (Cocoaheads Vienna)

-­‐  (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

Page 13: 2013-08-08 | Mantle (Cocoaheads Vienna)

-­‐  (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

Page 14: 2013-08-08 | Mantle (Cocoaheads Vienna)

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

Page 15: 2013-08-08 | Mantle (Cocoaheads Vienna)

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

Page 16: 2013-08-08 | Mantle (Cocoaheads Vienna)

@implementation  GHIssue  !+  (NSDictionary  *)JSONKeyPathsByPropertyKey  {          return  @{                  @"URL":  @"url",                  @"HTMLURL":  @"html_url",                  @"reporterLogin":  @"user.login",                  @"assignee":  @"assignee",                  @"updatedAt":  @"updated_at"          };  }  

Dominik Gruber @the_dom

Page 17: 2013-08-08 | Mantle (Cocoaheads Vienna)

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

Page 18: 2013-08-08 | Mantle (Cocoaheads Vienna)

+

• Automatically implemented

• <NSCoding>!

• <NSCopying>!

• -­‐isEqual:!

• -­‐hash  

• [MTLJSONAdapter  JSONDictionaryFromModel:]  

• It’s possible to handle interface changes with Mantle

Dominik Gruber @the_dom

Page 19: 2013-08-08 | Mantle (Cocoaheads Vienna)

-

Dominik Gruber @the_dom

• Bad documentation

• No persistence

• Some pitfalls

Page 20: 2013-08-08 | Mantle (Cocoaheads Vienna)

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

Page 21: 2013-08-08 | Mantle (Cocoaheads Vienna)

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

Page 22: 2013-08-08 | Mantle (Cocoaheads Vienna)

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

Page 23: 2013-08-08 | Mantle (Cocoaheads Vienna)

Q & A

Dominik Gruber @the_dom