Skip to content

Commit

Permalink
* first iteration on JSONModel -> NSManagedObject
Browse files Browse the repository at this point in the history
  • Loading branch information
icanzilb committed Jan 22, 2014
1 parent 90cb0ec commit 28e3bb2
Show file tree
Hide file tree
Showing 13 changed files with 678 additions and 3 deletions.
23 changes: 23 additions & 0 deletions JSONModel/JSONModel/JSONModel+CoreData.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// JSONModel+CoreData.h
// JSONModelDemo_iOS
//
// Created by Marin Todorov on 22/1/14.
// Copyright (c) 2014 Underplot ltd. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "JSONModel.h"
#import <CoreData/CoreData.h>

@interface JSONModel(CoreData)

@end

@interface NSManagedObject(JSONModel)

+(instancetype)entityWithModel:(id<AbstractJSONModelProtocol>)model inContext:(NSManagedObjectContext*)context error:(NSError**)error;
+(instancetype)entityWithDictionary:(NSDictionary*)dictionary inContext:(NSManagedObjectContext*)context error:(NSError**)error;
-(BOOL)updateWithDictionary:(NSDictionary*)dictionary error:(NSError**)error;

@end
127 changes: 127 additions & 0 deletions JSONModel/JSONModel/JSONModel+CoreData.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//
// JSONModel+CoreData.m
// JSONModelDemo_iOS
//
// Created by Marin Todorov on 22/1/14.
// Copyright (c) 2014 Underplot ltd. All rights reserved.
//

#import "JSONModel+CoreData.h"
#import <objc/runtime.h>
#import <objc/message.h>

@implementation JSONModel(CoreData)

@end

@implementation NSManagedObject(JSONModel)

+(NSString*)entityName
{
return nil;
}

+(instancetype)entityWithModel:(id<AbstractJSONModelProtocol>)model inContext:(NSManagedObjectContext*)context error:(NSError**)error
{
return [self entityWithDictionary: [model toDictionary]
inContext: context
error:error];
}

+(instancetype)entityWithDictionary:(NSDictionary*)dictionary inContext:(NSManagedObjectContext*)context error:(NSError**)error
{
NSManagedObject* entity = [NSEntityDescription
insertNewObjectForEntityForName: self.entityName?self.entityName:NSStringFromClass([self class])
inManagedObjectContext:context];
if (entity) {
if (![entity updateWithDictionary: dictionary error: error]) {
return nil;
}
}
return entity;
}

-(BOOL)updateWithDictionary:(NSDictionary*)dictionary error:(NSError**)error
{
//introspect managed object
Class class = [self class];
NSMutableDictionary* moProperties = [@{} mutableCopy];

while (class != [NSManagedObject class]) {
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList(class, &propertyCount);

//loop over the class properties
for (unsigned int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
const char *propertyName = property_getName(property);

const char *attrs = property_getAttributes(property);
NSString* propertyAttributes = [NSString stringWithUTF8String:attrs];
NSScanner* scanner = [NSScanner scannerWithString: propertyAttributes];
NSString* propertyType = nil;
[scanner scanUpToString:@"T" intoString: nil];
[scanner scanString:@"T" intoString:nil];

//check if the property is an instance of a class
if ([scanner scanString:@"@\"" intoString: &propertyType]) {

[scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"]
intoString:&propertyType];
}

moProperties[[NSString stringWithUTF8String:propertyName]] = NSClassFromString(propertyType);
}

free(properties);
class = [class superclass];
}

//validate dictionary keys
NSArray* dictionaryKeys = [dictionary allKeys];
for (NSString* key in [moProperties allKeys]) {
if (![dictionaryKeys containsObject:key]) {
//unmatched key
if (error) {
*error = [JSONModelError errorInvalidDataWithTypeMismatch:[NSString stringWithFormat: @"Key \"%@\" not found in manged object's property list", key]];
}
return NO;
}
}

//copy values over
for (NSString* key in [moProperties allKeys]) {
id value = dictionary[key];
NSLog(@"class: %@", moProperties[key]);
//exception classes - for core data should be NSDate by default
if ([[moProperties[key]class] isEqual:[NSDate class]]) {
if ([self respondsToSelector:@selector(NSDateFromNSString:)]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//transform the value
value = [self performSelector:@selector(NSDateFromNSString:) withObject:dictionary[key]];
#pragma clang diagnostic pop
} else {
value = [self __NSDateFromNSString: dictionary[key]];
}
}

//copy the value to the managed object
[self setValue:value forKey:key];
}

return YES;
}


#pragma mark - string <-> date
-(NSDate*)__NSDateFromNSString:(NSString*)string
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
string = [string stringByReplacingOccurrencesOfString:@":" withString:@""]; // this is such an ugly code, is this the only way?
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HHmmssZZZZ"];

return [dateFormatter dateFromString: string];
}

@end
3 changes: 3 additions & 0 deletions JSONModel/JSONModelLib.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@
#import "NSArray+JSONModel.h"
#import "JSONModelArray.h"

#ifdef COREDATA_EXTERN
#import "JSONModel+CoreData.h"
#endif
2 changes: 1 addition & 1 deletion JSONModelDemoTests/UnitTests/TestModels/GitHubRepoModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
@property (assign, nonatomic) BOOL fork;
@property (assign, nonatomic) double size;
@property (assign, nonatomic) int followers;
@property (assign, nonatomic) NSString<Index>* name;
@property (strong, nonatomic) NSString<Index>* name;

@end
2 changes: 1 addition & 1 deletion JSONModelDemoTests/UnitTests/TestModels/ReposModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@

@interface ReposModel : JSONModel

@property (strong, nonatomic) NSArray<ConvertOnDemand, GitHubRepoModel>* repositories;
@property (strong, nonatomic) NSMutableArray<ConvertOnDemand, GitHubRepoModel>* repositories;

@end
59 changes: 59 additions & 0 deletions JSONModelDemo_iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
697852FF17D93547006BFCD0 /* nestedDataWithTypeMismatchOnImagesObject.json in Resources */ = {isa = PBXBuildFile; fileRef = 697852FE17D93546006BFCD0 /* nestedDataWithTypeMismatchOnImagesObject.json */; };
9C0D0240166E6BBF001EA645 /* KivaViewControllerNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C0D023E166E6BBF001EA645 /* KivaViewControllerNetworking.m */; };
9C0D0241166E6BBF001EA645 /* KivaViewControllerNetworking.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9C0D023F166E6BBF001EA645 /* KivaViewControllerNetworking.xib */; };
9C55AF0718903127004EBD8A /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C55AF0618903127004EBD8A /* CoreData.framework */; };
9C55AF0C189031A8004EBD8A /* CoreDataViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C55AF0A189031A8004EBD8A /* CoreDataViewController.m */; };
9C55AF0D189031A8004EBD8A /* CoreDataViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9C55AF0B189031A8004EBD8A /* CoreDataViewController.xib */; };
9C55AF0E18903300004EBD8A /* ReposModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CFDD0C5176E977C007B7DFA /* ReposModel.m */; };
9C55AF0F189033AE004EBD8A /* GitHubRepoModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 9CFDD0B3176E977C007B7DFA /* GitHubRepoModel.m */; };
9C55AF1218903448004EBD8A /* github.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 9C55AF1018903448004EBD8A /* github.xcdatamodeld */; };
9C55AF1818903ADA004EBD8A /* JSONModel+CoreData.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C55AF1718903ADA004EBD8A /* JSONModel+CoreData.m */; };
9C55AF1918903ADA004EBD8A /* JSONModel+CoreData.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C55AF1718903ADA004EBD8A /* JSONModel+CoreData.m */; };
9C55AF1C1890494E004EBD8A /* GitHubRepoEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C55AF1B1890494E004EBD8A /* GitHubRepoEntity.m */; };
9C55AF1D1890494E004EBD8A /* GitHubRepoEntity.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C55AF1B1890494E004EBD8A /* GitHubRepoEntity.m */; };
9C66DFA8168CEF420015CCDF /* ArrayTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C66DF60168CEF420015CCDF /* ArrayTests.m */; };
9C66DFA9168CEF420015CCDF /* BuiltInConversionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C66DF62168CEF420015CCDF /* BuiltInConversionsTests.m */; };
9C66DFAB168CEF420015CCDF /* CustomPropsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C66DF66168CEF420015CCDF /* CustomPropsTests.m */; };
Expand Down Expand Up @@ -152,6 +162,15 @@
9C0D023D166E6BBF001EA645 /* KivaViewControllerNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KivaViewControllerNetworking.h; sourceTree = "<group>"; };
9C0D023E166E6BBF001EA645 /* KivaViewControllerNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KivaViewControllerNetworking.m; sourceTree = "<group>"; };
9C0D023F166E6BBF001EA645 /* KivaViewControllerNetworking.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = KivaViewControllerNetworking.xib; sourceTree = "<group>"; };
9C55AF0618903127004EBD8A /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
9C55AF09189031A8004EBD8A /* CoreDataViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CoreDataViewController.h; sourceTree = "<group>"; };
9C55AF0A189031A8004EBD8A /* CoreDataViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreDataViewController.m; sourceTree = "<group>"; };
9C55AF0B189031A8004EBD8A /* CoreDataViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CoreDataViewController.xib; sourceTree = "<group>"; };
9C55AF1118903448004EBD8A /* github.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = github.xcdatamodel; sourceTree = "<group>"; };
9C55AF1618903ADA004EBD8A /* JSONModel+CoreData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSONModel+CoreData.h"; sourceTree = "<group>"; };
9C55AF1718903ADA004EBD8A /* JSONModel+CoreData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSONModel+CoreData.m"; sourceTree = "<group>"; };
9C55AF1A1890494E004EBD8A /* GitHubRepoEntity.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GitHubRepoEntity.h; sourceTree = "<group>"; };
9C55AF1B1890494E004EBD8A /* GitHubRepoEntity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GitHubRepoEntity.m; sourceTree = "<group>"; };
9C66DF5F168CEF420015CCDF /* ArrayTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArrayTests.h; sourceTree = "<group>"; };
9C66DF60168CEF420015CCDF /* ArrayTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArrayTests.m; sourceTree = "<group>"; };
9C66DF61168CEF420015CCDF /* BuiltInConversionsTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BuiltInConversionsTests.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -325,6 +344,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9C55AF0718903127004EBD8A /* CoreData.framework in Frameworks */,
9CA6B10216FCAAEE00B3E78E /* SystemConfiguration.framework in Frameworks */,
9CBBBEDD166B6CF0008B4326 /* UIKit.framework in Frameworks */,
9CBBBEDF166B6CF0008B4326 /* Foundation.framework in Frameworks */,
Expand Down Expand Up @@ -366,6 +386,19 @@
name = "KivaJSONDemo+networking";
sourceTree = "<group>";
};
9C55AF0818903168004EBD8A /* CoreDataDemo */ = {
isa = PBXGroup;
children = (
9C55AF09189031A8004EBD8A /* CoreDataViewController.h */,
9C55AF0A189031A8004EBD8A /* CoreDataViewController.m */,
9C55AF0B189031A8004EBD8A /* CoreDataViewController.xib */,
9C55AF1018903448004EBD8A /* github.xcdatamodeld */,
9C55AF1A1890494E004EBD8A /* GitHubRepoEntity.h */,
9C55AF1B1890494E004EBD8A /* GitHubRepoEntity.m */,
);
name = CoreDataDemo;
sourceTree = "<group>";
};
9C66DF5E168CEF420015CCDF /* UnitTests */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -458,6 +491,8 @@
9C66E011168CF0AA0015CCDF /* JSONModelClassProperty.m */,
9C66E012168CF0AA0015CCDF /* JSONModelError.h */,
9C66E013168CF0AA0015CCDF /* JSONModelError.m */,
9C55AF1618903ADA004EBD8A /* JSONModel+CoreData.h */,
9C55AF1718903ADA004EBD8A /* JSONModel+CoreData.m */,
);
path = JSONModel;
sourceTree = "<group>";
Expand Down Expand Up @@ -518,6 +553,7 @@
9CBBBEDB166B6CF0008B4326 /* Frameworks */ = {
isa = PBXGroup;
children = (
9C55AF0618903127004EBD8A /* CoreData.framework */,
9CA6B0FF16FCA5B400B3E78E /* SystemConfiguration.framework */,
9CBBBEDC166B6CF0008B4326 /* UIKit.framework */,
9CBBBEDE166B6CF0008B4326 /* Foundation.framework */,
Expand All @@ -530,6 +566,7 @@
9CBBBEE2166B6CF0008B4326 /* JSONModelDemo_iOS */ = {
isa = PBXGroup;
children = (
9C55AF0818903168004EBD8A /* CoreDataDemo */,
9CBBBFAD166BB87B008B4326 /* DemoApp */,
9CBBBF87166BAC31008B4326 /* libs */,
9CBBBF9D166BB27E008B4326 /* YouTubeDemo */,
Expand Down Expand Up @@ -818,6 +855,7 @@
9CBBBEF1166B6CF0008B4326 /* Default@2x.png in Resources */,
9CBBBEF3166B6CF0008B4326 /* Default-568h@2x.png in Resources */,
9CBBBEFC166B6CF0008B4326 /* MasterViewController.xib in Resources */,
9C55AF0D189031A8004EBD8A /* CoreDataViewController.xib in Resources */,
9CBBBF78166BA80F008B4326 /* KivaViewController.xib in Resources */,
9CBBBF8F166BAC54008B4326 /* btnCheck.png in Resources */,
9CBBBF90166BAC54008B4326 /* btnCheck@2x.png in Resources */,
Expand Down Expand Up @@ -886,6 +924,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9C55AF0C189031A8004EBD8A /* CoreDataViewController.m in Sources */,
9CBBBEE9166B6CF0008B4326 /* main.m in Sources */,
9CBBBEED166B6CF0008B4326 /* AppDelegate.m in Sources */,
9CBBBEF6166B6CF0008B4326 /* MasterViewController.m in Sources */,
Expand All @@ -895,15 +934,20 @@
9CBBBF82166BA86A008B4326 /* KivaFeed.m in Sources */,
9CBBBF91166BAC54008B4326 /* HUD.m in Sources */,
9CBBBF92166BAC54008B4326 /* MBProgressHUD.m in Sources */,
9C55AF1818903ADA004EBD8A /* JSONModel+CoreData.m in Sources */,
9CBBBF97166BAED2008B4326 /* GitHubViewController.m in Sources */,
9CBBBF9C166BAEF5008B4326 /* GitHubUserModel.m in Sources */,
9CBBBFA1166BB29B008B4326 /* YouTubeViewController.m in Sources */,
9CBBBFA5166BB2AD008B4326 /* VideoModel.m in Sources */,
9C55AF1218903448004EBD8A /* github.xcdatamodeld in Sources */,
9CBBBFA9166BB3E1008B4326 /* VideoTitle.m in Sources */,
9CBBBFAC166BB3EC008B4326 /* VideoLink.m in Sources */,
9C55AF0F189033AE004EBD8A /* GitHubRepoModel.m in Sources */,
9CBBBFB1166BBB05008B4326 /* MyDataModel.m in Sources */,
9CBBBFB5166BBB21008B4326 /* StorageViewController.m in Sources */,
9C55AF1C1890494E004EBD8A /* GitHubRepoEntity.m in Sources */,
9C0D0240166E6BBF001EA645 /* KivaViewControllerNetworking.m in Sources */,
9C55AF0E18903300004EBD8A /* ReposModel.m in Sources */,
9C66E024168CF0AA0015CCDF /* JSONModel.m in Sources */,
9C66E026168CF0AA0015CCDF /* JSONModelArray.m in Sources */,
9C66E028168CF0AA0015CCDF /* JSONModelClassProperty.m in Sources */,
Expand Down Expand Up @@ -942,6 +986,7 @@
9C66E02B168CF0AA0015CCDF /* JSONModelError.m in Sources */,
9C66E02D168CF0AA0015CCDF /* NSArray+JSONModel.m in Sources */,
9C66E02F168CF0AA0015CCDF /* JSONAPI.m in Sources */,
9C55AF1D1890494E004EBD8A /* GitHubRepoEntity.m in Sources */,
9C66E031168CF0AA0015CCDF /* JSONHTTPClient.m in Sources */,
9C66E033168CF0AA0015CCDF /* JSONModel+networking.m in Sources */,
9C66E035168CF0AA0015CCDF /* JSONKeyMapper.m in Sources */,
Expand All @@ -961,6 +1006,7 @@
9CFDD0D0176E977C007B7DFA /* GitHubRepoModel.m in Sources */,
9CFDD0D1176E977C007B7DFA /* GitHubRepoModelForUSMapper.m in Sources */,
9CFDD0D2176E977C007B7DFA /* ImageModel.m in Sources */,
9C55AF1918903ADA004EBD8A /* JSONModel+CoreData.m in Sources */,
9CFDD0D3176E977C007B7DFA /* JSONTypesModel.m in Sources */,
9CFDD0D4176E977C007B7DFA /* NestedModel.m in Sources */,
9CFDD0D5176E977C007B7DFA /* OptionalPropModel.m in Sources */,
Expand Down Expand Up @@ -1150,6 +1196,19 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */

/* Begin XCVersionGroup section */
9C55AF1018903448004EBD8A /* github.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
9C55AF1118903448004EBD8A /* github.xcdatamodel */,
);
currentVersion = 9C55AF1118903448004EBD8A /* github.xcdatamodel */;
path = github.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
};
rootObject = 9CBBBECF166B6CEF008B4326 /* Project object */;
}
14 changes: 14 additions & 0 deletions JSONModelDemo_iOS/CoreDataViewController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// CoreDataViewController.h
// JSONModelDemo_iOS
//
// Created by Marin Todorov on 22/1/14.
// Copyright (c) 2014 Underplot ltd. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

@interface CoreDataViewController : UIViewController

@end
Loading

0 comments on commit 28e3bb2

Please sign in to comment.