forked from magicalpanda/MagicalRecord
/
NSManagedObject+MagicalDataImport.m
344 lines (285 loc) · 13.1 KB
/
NSManagedObject+MagicalDataImport.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
//
// NSManagedObject+JSONHelpers.m
//
// Created by Saul Mora on 6/28/11.
// Copyright 2011 Magical Panda Software LLC. All rights reserved.
//
#import "CoreData+MagicalRecord.h"
#import "NSObject+MagicalDataImport.h"
#import <objc/runtime.h>
void MR_swapMethodsFromClass(Class c, SEL orig, SEL new);
NSString * const kMagicalRecordImportCustomDateFormatKey = @"dateFormat";
NSString * const kMagicalRecordImportDefaultDateFormatString = @"yyyy-MM-dd'T'HH:mm:ssz";
NSString * const kMagicalRecordImportAttributeKeyMapKey = @"mappedKeyName";
NSString * const kMagicalRecordImportAttributeValueClassNameKey = @"attributeValueClassName";
NSString * const kMagicalRecordImportRelationshipMapKey = @"mappedKeyName";
NSString * const kMagicalRecordImportRelationshipLinkedByKey = @"relatedByAttribute";
NSString * const kMagicalRecordImportRelationshipTypeKey = @"type"; //this needs to be revisited
NSString * const kMagicalRecordImportAttributeUseDefaultValueWhenNotPresent = @"useDefaultValueWhenNotPresent";
@interface NSObject (MagicalRecord_DataImportControls)
- (id) MR_valueForUndefinedKey:(NSString *)key;
@end
@implementation NSManagedObject (MagicalRecord_DataImport)
- (BOOL) MR_importValue:(id)value forKey:(NSString *)key
{
NSString *selectorString = [NSString stringWithFormat:@"import%@:", [key MR_capitalizedFirstCharacterString]];
SEL selector = NSSelectorFromString(selectorString);
if ([self respondsToSelector:selector])
{
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
[invocation setTarget:self];
[invocation setSelector:selector];
[invocation setArgument:&value atIndex:2];
[invocation invoke];
// import callbacks must return a BOOL indicating whether they have
// handled the import themselves (YES) or whether MR still needs to handle it (NO).
// the default is YES for backwards compatibility.
BOOL returnValue = YES;
[invocation getReturnValue:&returnValue];
return returnValue;
}
return NO;
}
- (void) MR_setAttributes:(NSDictionary *)attributes forKeysWithObject:(id)objectData
{
for (NSString *attributeName in attributes)
{
NSAttributeDescription *attributeInfo = [attributes valueForKey:attributeName];
NSString *lookupKeyPath = [objectData MR_lookupKeyForAttribute:attributeInfo];
if (lookupKeyPath)
{
id value = [attributeInfo MR_valueForKeyPath:lookupKeyPath fromObjectData:objectData];
if (![self MR_importValue:value forKey:attributeName])
{
[self setValue:value forKey:attributeName];
}
}
else
{
if ([[[attributeInfo userInfo] objectForKey:kMagicalRecordImportAttributeUseDefaultValueWhenNotPresent] boolValue])
{
id value = [attributeInfo defaultValue];
if (![self MR_importValue:value forKey:attributeName])
{
[self setValue:value forKey:attributeName];
}
}
}
}
}
- (NSManagedObject *) MR_findObjectForRelationship:(NSRelationshipDescription *)relationshipInfo withData:(id)singleRelatedObjectData
{
NSEntityDescription *destinationEntity = [relationshipInfo destinationEntity];
NSManagedObject *objectForRelationship = nil;
id relatedValue;
// if its a primitive class, than handle singleRelatedObjectData as the key for relationship
if ([singleRelatedObjectData isKindOfClass:[NSString class]] ||
[singleRelatedObjectData isKindOfClass:[NSNumber class]])
{
relatedValue = singleRelatedObjectData;
}
else if ([singleRelatedObjectData isKindOfClass:[NSDictionary class]])
{
relatedValue = [singleRelatedObjectData MR_relatedValueForRelationship:relationshipInfo];
}
else
{
relatedValue = singleRelatedObjectData;
}
if (relatedValue)
{
NSManagedObjectContext *context = [self managedObjectContext];
Class managedObjectClass = NSClassFromString([destinationEntity managedObjectClassName]);
NSString *primaryKey = [relationshipInfo MR_primaryKey];
objectForRelationship = [managedObjectClass MR_findFirstByAttribute:primaryKey
withValue:relatedValue
inContext:context];
}
return objectForRelationship;
}
- (void) MR_addObject:(NSManagedObject *)relatedObject forRelationship:(NSRelationshipDescription *)relationshipInfo
{
NSAssert2(relatedObject != nil, @"Cannot add nil to %@ for attribute %@", NSStringFromClass([self class]), [relationshipInfo name]);
NSAssert2([[relatedObject entity] isKindOfEntity:[relationshipInfo destinationEntity]], @"related object entity %@ not same as destination entity %@", [relatedObject entity], [relationshipInfo destinationEntity]);
//add related object to set
NSString *addRelationMessageFormat = @"set%@:";
id relationshipSource = self;
if ([relationshipInfo isToMany])
{
addRelationMessageFormat = @"add%@Object:";
if ([relationshipInfo respondsToSelector:@selector(isOrdered)] && [relationshipInfo isOrdered])
{
//Need to get the ordered set
NSString *selectorName = [[relationshipInfo name] stringByAppendingString:@"Set"];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
relationshipSource = [self performSelector:NSSelectorFromString(selectorName)];
#pragma clang diagnostic pop
addRelationMessageFormat = @"addObject:";
}
}
NSString *addRelatedObjectToSetMessage = [NSString stringWithFormat:addRelationMessageFormat, attributeNameFromString([relationshipInfo name])];
SEL selector = NSSelectorFromString(addRelatedObjectToSetMessage);
@try
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[relationshipSource performSelector:selector withObject:relatedObject];
#pragma clang diagnostic pop
}
@catch (NSException *exception)
{
MRLog(@"Adding object for relationship failed: %@\n", relationshipInfo);
MRLog(@"relatedObject.entity %@", [relatedObject entity]);
MRLog(@"relationshipInfo.destinationEntity %@", [relationshipInfo destinationEntity]);
MRLog(@"Add Relationship Selector: %@", addRelatedObjectToSetMessage);
MRLog(@"perform selector error: %@", exception);
}
}
- (void) MR_setRelationships:(NSDictionary *)relationships forKeysWithObject:(id)relationshipData withBlock:(void(^)(NSRelationshipDescription *,id))setRelationshipBlock
{
for (NSString *relationshipName in relationships)
{
if ([self MR_importValue:relationshipData forKey:relationshipName])
{
continue;
}
NSRelationshipDescription *relationshipInfo = [relationships valueForKey:relationshipName];
NSString *lookupKey = [[relationshipInfo userInfo] valueForKey:kMagicalRecordImportRelationshipMapKey] ?: relationshipName;
id relatedObjectData = [relationshipData valueForKeyPath:lookupKey];
if (relatedObjectData == nil || [relatedObjectData isEqual:[NSNull null]])
{
continue;
}
SEL shouldImportSelector = NSSelectorFromString([NSString stringWithFormat:@"shouldImport%@:", [relationshipName MR_capitalizedFirstCharacterString]]);
BOOL implementsShouldImport = (BOOL)[self respondsToSelector:shouldImportSelector];
void (^establishRelationship)(NSRelationshipDescription *, id) = ^(NSRelationshipDescription *blockInfo, id blockData)
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if (!(implementsShouldImport && !(BOOL)[self performSelector:shouldImportSelector withObject:relatedObjectData]))
{
setRelationshipBlock(blockInfo, blockData);
}
#pragma clang diagnostic pop
};
if ([relationshipInfo isToMany] && [relatedObjectData isKindOfClass:[NSArray class]])
{
for (id singleRelatedObjectData in relatedObjectData)
{
establishRelationship(relationshipInfo, singleRelatedObjectData);
}
}
else
{
establishRelationship(relationshipInfo, relatedObjectData);
}
}
}
- (BOOL) MR_preImport:(id)objectData;
{
if ([self respondsToSelector:@selector(shouldImport:)])
{
BOOL shouldImport = (BOOL)[self shouldImport:objectData];
if (!shouldImport)
{
return NO;
}
}
if ([self respondsToSelector:@selector(willImport:)])
{
[self willImport:objectData];
}
MR_swapMethodsFromClass([objectData class], @selector(valueForUndefinedKey:), @selector(MR_valueForUndefinedKey:));
return YES;
}
- (BOOL) MR_postImport:(id)objectData;
{
MR_swapMethodsFromClass([objectData class], @selector(valueForUndefinedKey:), @selector(MR_valueForUndefinedKey:));
if ([self respondsToSelector:@selector(didImport:)])
{
[self performSelector:@selector(didImport:) withObject:objectData];
}
return YES;
}
- (BOOL) MR_performDataImportFromObject:(id)objectData relationshipBlock:(void(^)(NSRelationshipDescription*, id))relationshipBlock;
{
BOOL didStartimporting = [self MR_preImport:objectData];
if (!didStartimporting) return NO;
NSDictionary *attributes = [[self entity] attributesByName];
[self MR_setAttributes:attributes forKeysWithObject:objectData];
NSDictionary *relationships = [[self entity] relationshipsByName];
[self MR_setRelationships:relationships forKeysWithObject:objectData withBlock:relationshipBlock];
return [self MR_postImport:objectData];
}
- (BOOL) MR_importValuesForKeysWithObject:(id)objectData
{
__weak typeof(self) weakself = self;
return [self MR_performDataImportFromObject:objectData
relationshipBlock:^(NSRelationshipDescription *relationshipInfo, id localObjectData) {
NSManagedObject *relatedObject = [weakself MR_findObjectForRelationship:relationshipInfo withData:localObjectData];
if (relatedObject == nil)
{
NSEntityDescription *entityDescription = [relationshipInfo destinationEntity];
relatedObject = [entityDescription MR_createInstanceInContext:[weakself managedObjectContext]];
}
[relatedObject MR_importValuesForKeysWithObject:localObjectData];
if ((localObjectData) && (![localObjectData isKindOfClass:[NSDictionary class]]))
{
NSString * relatedByAttribute = [[relationshipInfo userInfo] objectForKey:kMagicalRecordImportRelationshipLinkedByKey] ?: primaryKeyNameFromString([[relationshipInfo destinationEntity] name]);
if (relatedByAttribute)
{
if (![weakself MR_importValue:localObjectData forKey:relatedByAttribute])
{
[relatedObject setValue:localObjectData forKey:relatedByAttribute];
}
}
}
[weakself MR_addObject:relatedObject forRelationship:relationshipInfo];
}];
}
+ (id) MR_importFromObject:(id)objectData inContext:(NSManagedObjectContext *)context;
{
NSAttributeDescription *primaryAttribute = [[self MR_entityDescription] MR_primaryAttributeToRelateBy];
id value = [objectData MR_valueForAttribute:primaryAttribute];
NSManagedObject *managedObject = [self MR_findFirstByAttribute:[primaryAttribute name] withValue:value inContext:context];
if (managedObject == nil)
{
managedObject = [self MR_createInContext:context];
}
[managedObject MR_importValuesForKeysWithObject:objectData];
return managedObject;
}
+ (id) MR_importFromObject:(id)objectData
{
return [self MR_importFromObject:objectData inContext:[NSManagedObjectContext MR_defaultContext]];
}
+ (NSArray *) MR_importFromArray:(NSArray *)listOfObjectData
{
return [self MR_importFromArray:listOfObjectData inContext:[NSManagedObjectContext MR_defaultContext]];
}
+ (NSArray *) MR_importFromArray:(NSArray *)listOfObjectData inContext:(NSManagedObjectContext *)context
{
NSMutableArray *dataObjects = [NSMutableArray array];
[listOfObjectData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
NSDictionary *objectData = (NSDictionary *)obj;
NSManagedObject *dataObject = [self MR_importFromObject:objectData inContext:context];
[dataObjects addObject:dataObject];
}];
return dataObjects;
}
@end
void MR_swapMethodsFromClass(Class c, SEL orig, SEL new)
{
Method origMethod = class_getInstanceMethod(c, orig);
Method newMethod = class_getInstanceMethod(c, new);
if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)))
{
class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
}
else
{
method_exchangeImplementations(origMethod, newMethod);
}
}