2013-08-08 | Mantle (Cocoaheads Vienna)
-
Upload
dominik-gruber -
Category
Technology
-
view
217 -
download
3
description
Transcript of 2013-08-08 | Mantle (Cocoaheads Vienna)
Mantle
Dominik Gruber, @the_dom Cocoaheads Vienna – Aug. 8, 2013
https://github.com/github/Mantle
Agenda
• Context
• Core Data
• Alternatives to Core Data
• Mantle
Dominik Gruber @the_dom
Core Data
• Obvious choice
• Well-documented
• It works
• But...
• It’s slow
• No direct SQL queries
• A lot of boilerplate
Dominik Gruber @the_dom
Why is Core Data SO slow?
• No mass update/delete
• Synchronization between NSManagedObjectContext
• No transactions
Dominik Gruber @the_dom
Alternatives
• Raw SQLite
• libsqlite3.dylib
• FMDB
• NSCoder
• Mantle
Dominik Gruber @the_dom
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
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
-‐ (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
-‐ (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
-‐ (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
-‐ (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
-‐ (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
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
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
@implementation GHIssue !+ (NSDictionary *)JSONKeyPathsByPropertyKey { return @{ @"URL": @"url", @"HTMLURL": @"html_url", @"reporterLogin": @"user.login", @"assignee": @"assignee", @"updatedAt": @"updated_at" }; }
Dominik Gruber @the_dom
+ (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
+
• Automatically implemented
• <NSCoding>!
• <NSCopying>!
• -‐isEqual:!
• -‐hash
• [MTLJSONAdapter JSONDictionaryFromModel:]
• It’s possible to handle interface changes with Mantle
Dominik Gruber @the_dom
-
Dominik Gruber @the_dom
• Bad documentation
• No persistence
• Some pitfalls
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
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
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
Q & A
Dominik Gruber @the_dom