/
RKInMemoryEntityCache.m
255 lines (213 loc) · 12.1 KB
/
RKInMemoryEntityCache.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
//
// RKInMemoryEntityCache.m
// RestKit
//
// Created by Jeff Arena on 1/24/12.
// Copyright (c) 2012 RestKit. All rights reserved.
//
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#endif
#import "RKInMemoryEntityCache.h"
#import "NSManagedObject+ActiveRecord.h"
#import "../ObjectMapping/RKObjectPropertyInspector.h"
#import "RKObjectPropertyInspector+CoreData.h"
#import "RKLog.h"
// Set Logging Component
#undef RKLogComponent
#define RKLogComponent lcl_cRestKitCoreData
@implementation RKInMemoryEntityCache
@synthesize entityCache = _entityCache;
- (id)init {
self = [super init];
if (self) {
_entityCache = [[NSMutableDictionary alloc] init];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
#endif
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_entityCache release];
[super dealloc];
}
- (void)didReceiveMemoryWarning {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextObjectsDidChangeNotification
object:nil];
[_entityCache removeAllObjects];
}
- (NSMutableDictionary *)cachedObjectsForEntity:(NSEntityDescription *)entity
withMapping:(RKManagedObjectMapping *)mapping
inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(entity, @"Cannot retrieve cached objects without an entity");
NSAssert(mapping, @"Cannot retrieve cached objects without a mapping");
NSAssert(managedObjectContext, @"Cannot retrieve cached objects without a managedObjectContext");
NSMutableDictionary *cachedObjectsForEntity = [_entityCache objectForKey:entity.name];
if (cachedObjectsForEntity == nil) {
[self cacheObjectsForEntity:entity withMapping:mapping inContext:managedObjectContext];
cachedObjectsForEntity = [_entityCache objectForKey:entity.name];
}
return cachedObjectsForEntity;
}
- (NSManagedObject *)cachedObjectForEntity:(NSEntityDescription *)entity
withMapping:(RKManagedObjectMapping *)mapping
andPrimaryKeyValue:(id)primaryKeyValue
inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(entity, @"Cannot retrieve a cached object without an entity");
NSAssert(mapping, @"Cannot retrieve a cached object without a mapping");
NSAssert(primaryKeyValue, @"Cannot retrieve a cached object without a primaryKeyValue");
NSAssert(managedObjectContext, @"Cannot retrieve a cached object without a managedObjectContext");
NSMutableDictionary *cachedObjectsForEntity = [self cachedObjectsForEntity:entity
withMapping:mapping
inContext:managedObjectContext];
// NOTE: We coerce the primary key into a string (if possible) for convenience. Generally
// primary keys are expressed either as a number of a string, so this lets us support either case interchangeably
id lookupValue = [primaryKeyValue respondsToSelector:@selector(stringValue)] ? [primaryKeyValue stringValue] : primaryKeyValue;
NSManagedObjectID *objectID = [cachedObjectsForEntity objectForKey:lookupValue];
NSManagedObject *object = nil;
if (objectID) {
object = [self objectWithID:objectID inContext:managedObjectContext];
}
return object;
}
- (void)cacheObjectsForEntity:(NSEntityDescription *)entity
withMapping:(RKManagedObjectMapping *)mapping
inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(entity, @"Cannot cache objects without an entity");
NSAssert(mapping, @"Cannot cache objects without a mapping");
NSAssert(managedObjectContext, @"Cannot cache objects without a managedObjectContext");
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSArray *objectIds = [NSManagedObject executeFetchRequest:fetchRequest inContext:managedObjectContext];
[fetchRequest release];
RKLogInfo(@"Caching all %ld %@ objectsIDs to thread local storage", (long) [objectIds count], entity.name);
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
if ([objectIds count] > 0) {
BOOL coerceToString = [self shouldCoerceAttributeToString:mapping.primaryKeyAttribute forEntity:entity];
for (NSManagedObjectID* theObjectID in objectIds) {
NSManagedObject* theObject = [self objectWithID:theObjectID inContext:managedObjectContext];
id attributeValue = [theObject valueForKey:mapping.primaryKeyAttribute];
// Coerce to a string if possible
attributeValue = coerceToString ? [attributeValue stringValue] : attributeValue;
if (attributeValue) {
[dictionary setObject:theObjectID forKey:attributeValue];
}
}
}
[_entityCache setObject:dictionary forKey:entity.name];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(objectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:managedObjectContext];
}
- (void)cacheObject:(NSManagedObject *)managedObject withMapping:(RKManagedObjectMapping *)mapping inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(managedObject, @"Cannot cache an object without a managedObject");
NSAssert(mapping, @"Cannot cache an object without a mapping");
NSAssert(managedObjectContext, @"Cannot cache an object without a managedObjectContext");
NSManagedObjectID *objectID = [managedObject objectID];
if (objectID) {
NSEntityDescription *entity = managedObject.entity;
BOOL coerceToString = [self shouldCoerceAttributeToString:mapping.primaryKeyAttribute forEntity:entity];
id attributeValue = [managedObject valueForKey:mapping.primaryKeyAttribute];
// Coerce to a string if possible
attributeValue = coerceToString ? [attributeValue stringValue] : attributeValue;
if (attributeValue) {
NSMutableDictionary *cachedObjectsForEntity = [self cachedObjectsForEntity:entity
withMapping:mapping
inContext:managedObjectContext];
[cachedObjectsForEntity setObject:objectID forKey:attributeValue];
}
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(objectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:managedObjectContext];
}
- (void)cacheObject:(NSEntityDescription *)entity withMapping:(RKManagedObjectMapping *)mapping andPrimaryKeyValue:(id)primaryKeyValue inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(entity, @"Cannot cache an object without an entity");
NSAssert(mapping, @"Cannot cache an object without a mapping");
NSAssert(managedObjectContext, @"Cannot cache an object without a managedObjectContext");
// NOTE: We coerce the primary key into a string (if possible) for convenience. Generally
// primary keys are expressed either as a number or a string, so this lets us support either case interchangeably
id lookupValue = [primaryKeyValue respondsToSelector:@selector(stringValue)] ? [primaryKeyValue stringValue] : primaryKeyValue;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setFetchLimit:1];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"%@ = %@", mapping.primaryKeyAttribute, lookupValue]];
[fetchRequest setResultType:NSManagedObjectIDResultType];
NSArray *objectIds = [NSManagedObject executeFetchRequest:fetchRequest inContext:managedObjectContext];
[fetchRequest release];
NSManagedObjectID *objectID = nil;
if ([objectIds count] > 0) {
objectID = [objectIds objectAtIndex:0];
if (objectID && lookupValue) {
NSMutableDictionary *cachedObjectsForEntity = [self cachedObjectsForEntity:entity
withMapping:mapping
inContext:managedObjectContext];
[cachedObjectsForEntity setObject:objectID forKey:lookupValue];
}
}
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(objectsDidChange:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:managedObjectContext];
}
- (void)expireCacheEntryForObject:(NSManagedObject *)managedObject withMapping:(RKManagedObjectMapping *)mapping inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(managedObject, @"Cannot expire cache entry for an object without a managedObject");
NSAssert(mapping, @"Cannot expire cache entry for an object without a mapping");
NSAssert(managedObjectContext, @"Cannot expire cache entry for an object without a managedObjectContext");
NSEntityDescription *entity = managedObject.entity;
BOOL coerceToString = [self shouldCoerceAttributeToString:mapping.primaryKeyAttribute forEntity:entity];
id attributeValue = [managedObject valueForKey:mapping.primaryKeyAttribute];
// Coerce to a string if possible
attributeValue = coerceToString ? [attributeValue stringValue] : attributeValue;
if (attributeValue) {
NSMutableDictionary *cachedObjectsForEntity = [self cachedObjectsForEntity:entity
withMapping:mapping
inContext:managedObjectContext];
[cachedObjectsForEntity removeObjectForKey:attributeValue];
if ([cachedObjectsForEntity count] == 0) {
[self expireCacheEntryForEntity:entity];
}
}
}
- (void)expireCacheEntryForEntity:(NSEntityDescription *)entity {
NSAssert(entity, @"Cannot expire cache entry for an entity without an entity");
RKLogTrace(@"About to expire cache for entity name=%@", entity.name);
[_entityCache removeObjectForKey:entity.name];
}
#pragma mark Helper Methods
- (BOOL)shouldCoerceAttributeToString:(NSString *)attribute forEntity:(NSEntityDescription *)entity {
Class attributeType = [[RKObjectPropertyInspector sharedInspector] typeForProperty:attribute ofEntity:entity];
return [attributeType instancesRespondToSelector:@selector(stringValue)];
}
- (NSManagedObject *)objectWithID:(NSManagedObjectID *)objectID inContext:(NSManagedObjectContext *)managedObjectContext {
NSAssert(objectID, @"Cannot fetch a managedObject with a nil objectID");
NSAssert(managedObjectContext, @"Cannot fetch a managedObject with a nil managedObjectContext");
return [managedObjectContext objectWithID:objectID];
}
#pragma mark Notifications
- (void)objectsDidChange:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
NSSet *insertedObjects = [userInfo objectForKey:NSInsertedObjectsKey];
NSSet *deletedObjects = [userInfo objectForKey:NSDeletedObjectsKey];
RKLogTrace(@"insertedObjects=%@, deletedObjects=%@", insertedObjects, deletedObjects);
NSMutableSet *entitiesToExpire = [NSMutableSet set];
for (NSManagedObject *object in insertedObjects) {
[entitiesToExpire addObject:object.entity];
}
for (NSManagedObject *object in deletedObjects) {
[entitiesToExpire addObject:object.entity];
}
for (NSEntityDescription *entity in entitiesToExpire) {
[self expireCacheEntryForEntity:entity];
}
}
@end