Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
AtomicStoreSubclass.m
/* |
File: AtomicStoreSubclass.m |
Abstract: Sample implementation of a custom CoreData store using |
the NSAtomicStore API. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple |
Computer, Inc. ("Apple") in consideration of your agreement to the |
following terms, and your use, installation, modification or |
redistribution of this Apple software constitutes acceptance of these |
terms. If you do not agree with these terms, please do not use, |
install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and |
subject to these terms, Apple grants you a personal, non-exclusive |
license, under Apple's copyrights in this original Apple software (the |
"Apple Software"), to use, reproduce, modify and redistribute the Apple |
Software, with or without modifications, in source and/or binary forms; |
provided that if you redistribute the Apple Software in its entirety and |
without modifications, you must retain this notice and the following |
text and disclaimers in all such redistributions of the Apple Software. |
Neither the name, trademarks, service marks or logos of Apple Computer, |
Inc. may be used to endorse or promote products derived from the Apple |
Software without specific prior written permission from Apple. Except |
as expressly stated in this notice, no other rights or licenses, express |
or implied, are granted by Apple herein, including but not limited to |
any patent rights that may be infringed by your derivative works or by |
other works in which the Apple Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE |
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION |
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS |
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND |
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL |
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, |
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED |
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), |
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE |
POSSIBILITY OF SUCH DAMAGE. |
Copyright © 2007 Apple, Inc., All Rights Reserved |
*/ |
#import <CoreData/NSPersistentStore.h> |
#import <CoreData/NSAtomicStore.h> |
#import "AtomicStoreSubclass.h" |
#import "AtomicStoreCacheNodeSubclass.h" |
NSString *MyAtomicStoreSubclassDomain = @"MyAtomicStoreSubclass"; |
@implementation AtomicStoreSubclass |
#pragma - implementation details |
/* |
* createStubAndGenerateMetadata |
Called from initWithPersistentStoreCoordinator:configurationName:URL:options: |
Creates a stub file to reserve the store's future location on disk; |
return NO (don't initialize) if we can't create the file |
*/ |
- (BOOL)createStubAndGenerateMetadata { |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
NSURL *fileURL = [self URL]; |
NSString *filePath = [fileURL path]; |
BOOL success = [fileManager createFileAtPath:filePath contents:nil attributes:nil]; |
if (!success) { |
return NO; |
} |
[self setMetadata: [NSDictionary dictionaryWithObject: [NSNumber numberWithInteger: 0] forKey: @"lastReferenceObject"]]; |
removeStub = YES; |
return YES; |
} |
/* |
* readFile |
Called from initWithPersistentStoreCoordinator:configurationName:URL:options: |
Reads the file and returns NO (don't complete initialization) if the store isn't approximately |
what we expect it to be (an archive containing the high level format we expect) |
*/ |
- (BOOL)readFile { |
NSData *storeContents = nil; |
@try { |
storeContents = [NSKeyedUnarchiver unarchiveObjectWithData: [NSData dataWithContentsOfURL:[self URL]]]; |
} @catch (NSException *ex) { |
// bad archive |
return NO; |
} |
if (nil == storeContents) { |
return NO; |
} else { |
if (![storeContents isKindOfClass: [NSDictionary class]]) { |
// didn't get what we were expecting |
return NO; |
} |
NSDictionary *storeMetadata = [storeContents valueForKey:@"Metadata"]; |
NSArray *storedObjects = [storeContents valueForKey:@ "Objects"]; |
if (nil == storeMetadata || nil == storedObjects) { |
// not my store |
return NO; |
} else { |
[self setMetadata: storeMetadata]; |
temp_objects = [storedObjects retain]; |
} |
} |
return YES; |
} |
/* |
* processObjects: |
Go through the external representation array and create cache nodes for the |
represented objects |
*/ |
- (BOOL)processObjects:(NSError **)error { |
if (nil == temp_objects) { |
// This is a new store |
return YES; |
} |
NSMutableSet *cacheNodes = [NSMutableSet set]; |
NSDictionary *entities = [[[self persistentStoreCoordinator] managedObjectModel] entitiesByName]; |
for (NSDictionary *externalRepresentation in temp_objects) { |
NSNumber *idReferenceData = [externalRepresentation valueForKey: @"idReferenceData"]; |
NSString *entityName = [externalRepresentation valueForKey:@"entityName"]; |
if (nil == idReferenceData) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:51 userInfo:nil]; |
return NO; |
} |
if (nil == entityName) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:52 userInfo:nil]; |
return NO; |
} |
NSEntityDescription *entity = [entities valueForKey:entityName]; |
if (nil == entity) { |
// should only end up here if the metadata lied about what was in the store |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:53 userInfo:nil]; |
return NO; |
} |
NSManagedObjectID *objectID = [self objectIDForEntity: entity referenceObject:idReferenceData]; |
AtomicStoreCacheNodeSubclass *newNode = [[AtomicStoreCacheNodeSubclass alloc] initWithObjectID:objectID]; |
[newNode setPropertyCacheData: [externalRepresentation valueForKey:@"propertyData"]]; |
[cacheNodes addObject: newNode]; |
} |
[self addCacheNodes: cacheNodes]; |
NSError *localError = nil; |
for (AtomicStoreCacheNodeSubclass *node in cacheNodes) { |
[node resolvePropertyValues: &localError]; |
if (nil != localError) { |
*error = localError; |
return NO; |
} |
} |
[temp_objects release]; |
temp_objects = nil; |
return YES; |
} |
/* Gather up all the external representations of the objects in the cache nodes in |
* preparation for saving |
*/ |
- (NSSet *)getExternalRepresentations { |
NSSet *cacheNodes = [self cacheNodes]; |
NSMutableSet *externalRepresentations = [NSMutableSet set]; |
for (AtomicStoreCacheNodeSubclass *node in cacheNodes) { |
NSMutableDictionary *externalRepresentation = [NSMutableDictionary dictionary]; |
[externalRepresentation setValue: [[node entity] name] forKey: @"entityName"]; |
[externalRepresentation setValue: [self referenceObjectForObjectID: [node objectID]] forKey:@"idReferenceData"]; |
[externalRepresentation setValue: [self getPropertyCacheDataForKVCCompliantObject:node] forKey: @"propertyData"]; |
[externalRepresentations addObject: externalRepresentation]; |
} |
return externalRepresentations; |
} |
/* |
* getPropertyCacheDataForKVCCompliantObject: |
Get the external representation of an object from either a managedObject or a cacheNode. |
This takes advantage of the fact that managed objects and cache nodes are very similar: |
both must respond to valueForKey:attributeName with a valid attribute value, and |
both must respond to valueForKey:relationshipName with an object (or set of objects, |
in the case of a toMany relationship) which do likewise |
*/ |
- (NSDictionary *)getPropertyCacheDataForKVCCompliantObject:(id)somethingRespondingToValueForKey { |
NSMutableDictionary *newValues = [NSMutableDictionary dictionary]; |
NSDictionary *attributeDescriptions = [[somethingRespondingToValueForKey entity] attributesByName]; |
for (NSString *attributeName in attributeDescriptions) { |
NSAttributeDescription *attributeDescription = [attributeDescriptions valueForKey: attributeName]; |
id attributeValue = [somethingRespondingToValueForKey valueForKey: attributeName]; |
if (NSTransformableAttributeType == [attributeDescription attributeType]) { |
NSString *transformerName = [attributeDescription valueTransformerName]; |
attributeValue = (nil == transformerName) ? [[NSValueTransformer valueTransformerForName: NSKeyedUnarchiveFromDataTransformerName] reverseTransformedValue: attributeValue] : [[NSValueTransformer valueTransformerForName: transformerName] transformedValue: attributeValue]; |
} |
[newValues setValue: attributeValue forKey: attributeName]; |
} |
NSDictionary *relationshipDescriptions = [[somethingRespondingToValueForKey entity] relationshipsByName]; |
for (NSString *relationshipName in relationshipDescriptions) { |
NSRelationshipDescription *relationshipDescription = [relationshipDescriptions valueForKey: relationshipName]; |
NSMutableSet *destinationInfos = [NSMutableSet set]; |
if (![relationshipDescription isToMany]) { |
id destinationObject = [somethingRespondingToValueForKey valueForKey: relationshipName]; |
if (nil != destinationObject) { |
[destinationInfos addObject: [NSArray arrayWithObjects: [[destinationObject entity] name], [self referenceObjectForObjectID: [destinationObject objectID]], nil]]; |
} |
} else { |
for (NSManagedObject *destinationObject in [somethingRespondingToValueForKey valueForKey: relationshipName]) { |
[destinationInfos addObject: [NSArray arrayWithObjects: [[destinationObject entity] name], [self referenceObjectForObjectID: [destinationObject objectID]], nil]]; |
} |
} |
[newValues setValue: destinationInfos forKey: relationshipName]; |
} |
return newValues; |
} |
/* |
* initWithPersistentStoreCoordinator:configurationName:URL:options: |
Do necessary initialization; return nil up front if for some reason the URL |
or its contents are known bad |
*/ |
- (id)initWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator configurationName:(NSString *)configurationName URL:(NSURL*)url options:(NSDictionary *)options { |
if (nil == url || ![url isFileURL]) { |
[self release]; |
return nil; |
} |
if (nil != (self = [super initWithPersistentStoreCoordinator:coordinator configurationName:configurationName URL:url options:options])) { |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
NSString *filePath = [url path]; |
BOOL isDirectory; |
if (![fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) { |
// no file, create one |
if (![self createStubAndGenerateMetadata]) { |
// can only get here if we can't create the stub |
[self release]; |
return nil; |
} |
} else if (isDirectory) { |
// directories aren't my type of store |
[self release]; |
return nil; |
} else { |
if (![self readFile]) { |
// can only get here if we can't read the file, so it isn't a store, or we can't read it, or something |
[self release]; |
return nil; |
} |
} |
} |
return self; |
} |
/* |
* dealloc |
release our variables |
*/ |
- (void)dealloc { |
[temp_objects release], temp_objects = nil; |
[store_identifier release], store_identifier = nil; |
[super dealloc]; |
} |
#pragma - NSPersistentStore required overrides |
/* |
* metadataForPersistentStoreWithURL: |
Try to load the store to get metadata - not very efficient, but simple |
*/ |
+ (NSDictionary *)metadataForPersistentStoreWithURL:(NSURL*)url error:(NSError **)error { |
NSData *storeContents = nil; |
@try { |
storeContents = [NSKeyedUnarchiver unarchiveObjectWithData: [NSData dataWithContentsOfURL:url]]; |
} @catch (NSException *ex) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:1 userInfo:nil]; |
return NO; |
} |
if (nil == storeContents) { |
if (nil != error) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:2 userInfo:nil]; |
} |
return nil; |
} else { |
if (! [storeContents isKindOfClass: [NSDictionary class]]) { |
if (nil != error) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:3 userInfo:nil]; |
} |
return nil; |
} |
NSDictionary *storeMetadata = [storeContents valueForKey:@"Metadata"]; |
NSDictionary *storedObjects = [storeContents valueForKey:@ "Objects"]; |
if (nil == storeMetadata || nil == storedObjects) { |
if (nil != error) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:4 userInfo:nil]; |
} |
return nil; |
} else { |
return storeMetadata; |
} |
} |
} |
/* |
* setMetadata:forPersistentStoreWithURL:error: |
Try to set metadata |
*/ |
+ (BOOL)setMetadata:(NSDictionary *)metadata forPersistentStoreWithURL:(NSURL*)url error:(NSError **)error { |
NSData *storeContents = nil; |
@try { |
storeContents = [NSKeyedUnarchiver unarchiveObjectWithData: [NSData dataWithContentsOfURL:url]]; |
} @catch (NSException *ex) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:11 userInfo:nil]; |
return NO; |
} |
if (nil == storeContents) { |
if (nil != error) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:12 userInfo:nil]; |
} |
return NO; |
} else { |
if (! [storeContents isKindOfClass: [NSDictionary class]]) { |
if (nil != error) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:13 userInfo:nil]; |
} |
return NO; |
} |
NSDictionary *storeMetadata = [storeContents valueForKey:@"Metadata"]; |
NSDictionary *storedObjects = [storeContents valueForKey:@ "Objects"]; |
if (nil == storeMetadata || nil == storedObjects) { |
if (nil != error) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:14 userInfo:nil]; |
} |
return NO; |
} else { |
NSString *storeIdentifier = [storeMetadata valueForKey: NSStoreUUIDKey]; |
NSString *storeType = [storeMetadata valueForKey: NSStoreTypeKey]; |
if (nil == storeIdentifier || nil == storeType) { |
if (nil != error) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:15 userInfo:nil]; |
} |
return NO; |
} |
NSMutableDictionary *newStoreContents = [NSMutableDictionary dictionary]; |
NSMutableDictionary *newMetadata = [metadata mutableCopy]; |
if ([newMetadata objectForKey:@"NSStoreModelVersionHashesVersion"] == nil) { |
[newMetadata setObject:[storeMetadata objectForKey:@"NSStoreModelVersionHashesVersion"] forKey: @"NSStoreModelVersionHashesVersion"]; |
} |
if ([newMetadata objectForKey:NSStoreModelVersionHashesKey] == nil) { |
[newMetadata setObject:[storeMetadata objectForKey:NSStoreModelVersionHashesKey] forKey: NSStoreModelVersionHashesKey]; |
} |
if ([newMetadata objectForKey:NSStoreModelVersionIdentifiersKey] == nil) { |
[newMetadata setObject:[storeMetadata objectForKey:NSStoreModelVersionIdentifiersKey] forKey: NSStoreModelVersionIdentifiersKey]; |
} |
if ([newMetadata objectForKey:NSStoreTypeKey] == nil) { |
[newMetadata setObject:[storeMetadata objectForKey:NSStoreTypeKey] forKey: NSStoreTypeKey]; |
} |
if ([newMetadata objectForKey:NSStoreUUIDKey] == nil) { |
[newMetadata setObject:[storeMetadata objectForKey:NSStoreUUIDKey] forKey: NSStoreUUIDKey]; |
} |
[newStoreContents setValue: newMetadata forKey: @"Metadata"]; |
[newMetadata release]; |
[newStoreContents setValue: storedObjects forKey: @"Objects"]; |
NSData *newData = [NSKeyedArchiver archivedDataWithRootObject:newStoreContents]; |
[newData writeToURL:url atomically:YES]; |
} |
} |
return YES; |
} |
/* |
* type: |
Return a type for this store |
*/ |
- (NSString *)type { |
return @"MyAtomicStoreSubclass"; |
} |
/* |
* identifier: |
Return the identifier for this store |
*/ |
- (NSString *)identifier { |
return store_identifier; |
} |
/* |
* setIdentifier: |
Set the identifier for this store |
*/ |
- (void)setIdentifier:(NSString *)identifier { |
if (identifier != store_identifier) { |
[identifier retain]; |
[store_identifier release]; |
store_identifier = identifier; |
} |
} |
#pragma - NSAtomicStore required overrides |
/* |
* load: |
Load the objects; the metadata was loaded earlier |
*/ |
- (BOOL)load:(NSError **)error { |
if (nil != temp_objects) { |
NSError *localError = nil; |
BOOL success = [self processObjects:&localError]; |
if (success) { |
return YES; |
} |
if (nil != error) { |
*error = localError; |
} |
} else { |
return YES; |
} |
return NO; |
} |
/* |
save: |
use standard archiving to save the metadata and the external representations |
of the objects to disk |
*/ |
- (BOOL)save:(NSError **)error { |
if ([self isReadOnly]) { |
if (nil != error) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:31 userInfo:nil]; |
} |
} |
NSMutableDictionary *dictionaryToSave = [NSMutableDictionary dictionary]; |
[dictionaryToSave setValue: [self metadata] forKey:@"Metadata"]; |
[dictionaryToSave setValue: [self getExternalRepresentations] forKey:@"Objects"]; |
NSData *encodedStore = [NSKeyedArchiver archivedDataWithRootObject:dictionaryToSave]; |
if (!encodedStore) { |
if (nil != error) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:32 userInfo:nil]; |
return NO; |
} |
} |
BOOL success = [encodedStore writeToURL:[self URL] atomically:YES]; |
if (!success) { |
*error = [NSError errorWithDomain:MyAtomicStoreSubclassDomain code:33 userInfo:nil]; |
} |
removeStub = NO; |
return success; |
} |
/* |
* newReferenceObjectForManagedObject: |
Reference objectIDs are constructed using a monotonically increasing number |
across all object types; this ensures uniqueness within inheritance hierarchies |
*/ |
- (id)newReferenceObjectForManagedObject:(NSManagedObject*)managedObject { |
NSDictionary *metadata = [self metadata]; |
long nextKey = 0; |
NSNumber *lastReferenceObject = [metadata valueForKey:@"lastReferenceObject"]; |
if (nil == lastReferenceObject) { |
nextKey = 1; |
} else { |
nextKey = [lastReferenceObject longValue]; |
nextKey++; |
} |
NSNumber *newReferenceObject = [NSNumber numberWithLong: nextKey]; |
NSMutableDictionary *newMetadata = [[self metadata] mutableCopy]; |
[newMetadata setValue: newReferenceObject forKey: @"lastReferenceObject"]; |
[self setMetadata: newMetadata]; |
[newMetadata release]; |
return [newReferenceObject retain]; |
} |
/* |
* newCacheNodeForManagedObject: |
Create and populate a new cache node by reusing the mechanism that unpacks the |
external representation of the object |
*/ |
- (NSAtomicStoreCacheNode *)newCacheNodeForManagedObject:(NSManagedObject *)managedObject { |
AtomicStoreCacheNodeSubclass *newNode = [[AtomicStoreCacheNodeSubclass alloc] initWithObjectID: [managedObject objectID]]; |
[newNode setPropertyCacheData: [self getPropertyCacheDataForKVCCompliantObject: managedObject]]; |
return newNode; |
} |
/* |
* updateCacheNode: |
Update the cache node by reusing the mechanism that unpacks the |
external representation of the object |
*/ |
- (void)updateCacheNode:(NSAtomicStoreCacheNode *)node fromManagedObject:(NSManagedObject *)managedObject { |
[(AtomicStoreCacheNodeSubclass *)node setPropertyCache: nil]; |
[(AtomicStoreCacheNodeSubclass *)node setPropertyCacheData: [self getPropertyCacheDataForKVCCompliantObject: managedObject]]; |
} |
#pragma - graph management overrides |
/* |
* willRemoveCacheNodes: |
The nodes are being removed, so break any retain cycles that may have been created |
between them by removing the property cache |
*/ |
- (void)willRemoveCacheNodes:(NSSet *)cacheNodes { |
for (NSAtomicStoreCacheNode *node in cacheNodes) { |
[node setPropertyCache: nil]; |
} |
} |
/* |
* willRemoveFromPersistentStoreCoordinator: |
Only called when a store is being removed from the coordinator |
do memory cleanup by removing all property caches |
and the retain cycles they contain |
*/ |
- (void)willRemoveFromPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator { |
for (NSAtomicStoreCacheNode *node in [self cacheNodes]) { |
[node setPropertyCache: nil]; |
} |
if (removeStub) { |
[[NSFileManager defaultManager] removeFileAtPath: [[self URL] path] handler:nil]; |
} |
[super willRemoveFromPersistentStoreCoordinator:coordinator]; |
} |
@end |
Copyright © 2007 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2007-06-08