Started down the path of 'singleton'. (whew!!) Now, trying to retrace some steps AND take the core data stack OUT of the app delegate.
In a discussion of this, I saw the following code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions(NSDictionary *)launchOptions {
DataModel *dataModel = [[DataModel alloc] init];
self.rootViewController.dataModel = dataModel;
...
}
In DataModel.m is the core data stack, methods to initiate a web service and methods to save the returned data into core data. Connection is another class.
Launch, starting the connection, passing the managed object context to the root view controller and receiving the web data all work fine. Then, a posted notification (upon completion of data receipt) in Connection class notifies DataModel of data to process. The issue is DataModel has been deallocated. The app crashes.
After abandoning the (shhh) singleton class for DataModel, the question(s) then are: How can the DataModel be kept around to process the received web data, destined for core data? Or, would it be better to split the core data stack and processing the received data into 2 files?
It seems logical to have the core data stack and processing methods in one class. I want to build this app by passing the context from controller to controller.
I have a class, "SVODataHelper" that has this in it:
#pragma mark - Setup
- (id) init
{
self = [super init];
if (!self)
return nil;
_model = [NSManagedObjectModel mergedModelFromBundles:nil];
_coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_model];
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_context setPersistentStoreCoordinator:_coordinator];
return self;
}
- (void) loadStore
{
if (_store)
return;
NSError *error = nil;
_store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self storeURL] options:nil error:&error];
if (!_store)
{
NSLog(#"Failed to add store. Error: %#", error);
abort();
}
}
- (void) setupCoreData
{
[self loadStore];
}
Then, in AppDelegate, I use:
- (SVOData *)svoData
{
if (!_svoDataHelper)
{
static dispatch_once_t predicate;
dispatch_once (&predicate, ^{ _svoDataHelper = [[SVOData alloc] init]; }); // insures a single instance created to guarantee thread safety.
[_svoDataHelper setupCoreData];
}
return _svoDataHelper;
}
And finally, I access the helper class with:
svoDataHelper = [(AppDelegate *)[[UIApplication sharedApplication] delegate] svoData];
I don't know if it is good or bad but it works. I got the concept from some book I read ("Learning IOS Core Data?) and it looked pretty clean as it allowed me to keep the essential stuff in a separate class while avoiding having to pass a pointer to that class around.
Related
Inside my iOS application, I am using Core Data to do a fetch, and delete of a very large data set. This process takes approximately 5-10 seconds. What I would like to do is perform a rollback in case the user decides to turn the device off before the process has completed. However, the problem is to have the SAME instance of the NSManagedObjectContext to call the rollback function from the appropriate AppDelegate method. Within my application, I call my Core Data methods using a Singleton object like this:
static MySingleton *sharedSingleton = nil;
+ (MySingleton *) sharedInstance {
if (sharedSingleton == nil) {
sharedSingleton = [[super alloc] init];
}
return sharedSingleton;
}
In my application, I return an instance of an NSManagedObjectContext like this:
- (NSManagedObjectContext *) managedObjectContext{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
//Undo Support
NSUndoManager *anUndoManager = [[NSUndoManager alloc] init];
[self.managedObjectContext setUndoManager:anUndoManager];
}
return _managedObjectContext;
}
I then call it, and assign it to a reference like this:
NSManagedObjectContext *context = [[MySingleton sharedInstance] managedObjectContext];
How would I make this instance of the ManagedObjectContext available to me for use in the AppDelegate, so that I can call the rollback function?
First off, the better (safer) way to create a singleton is as in the example given here: Create singleton using GCD's dispatch_once in Objective C, namely:
+ (instancetype)sharedInstance
{
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Next, since you've created a managed object context that must hang around for a few seconds, you must have a strong reference to it somewhere, which you do.
If you're in the midst of debugging and are questioning some fundamental assumptions about your code, give the managed object context a name (or log the memory address of the MOC pointer), that you can inspect in the debugger later, to verify for yourself that indeed, you're dealing with the same one.
Note also that if you created a dedicated Managed Object Context just for this sort of importing, you wouldn't need to roll it back. You could just discard it.
In my apps, I usually have a parent (root) managed object context and a couple of child contexts; one child is for the main thread, another child is for import type operations.
As an alternative solution, instead of rollback the changes, you can create a multi context scenario where a child managed object context add all the data that you need and eventually when its done, you save the child context sending the new data to the main managed object. This way the main managed object context its not affected until the complete process is done.
This is a great article as reference Multi-Context CoreData.
Basically what you need to do is
// create main MOC
_mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainContext setPersistentStoreCoordinator:_persistentStoreCoordinator];
// create child MOC
_childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_childContext.parentContext = _mainContext;
Hope it helps.
I'm trying to separate my application work when there is a bigger work to do to optimize performance. My problem is about a NSManagedObjectContext used in another thread than the main one.
I'm calling:
[NSThread detachNewThreadSelector:#selector(test:) toTarget:self withObject:myObject];
On the test method there are some stuff to do and I have a problem here:
NSArray *fetchResults = [moc
executeFetchRequest:request
error:&error];
Here is my test method:
-(void) test:(MyObject *)myObject{
#autoreleasepool {
//Mycode
}
}
The second time I call the test method, my new thread is blocked when the executeFetchRequest is called.
This problem arrived when my test method is called more than one time in succession. I think the problem comes from the moc but I can't really understand why.
Edit:
With #Charlie's method it's almost working. Here is my code to save my NSManagedObjectContext (object created on my new thread).
- (void) saveContext:(NSManagedObjectContext *) moc{
NSError *error = nil;
if ([moc hasChanges] && ![moc save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
This method is called on the new thread. My problem now is that with this save, I have a deadlock and I don't really understand why. Without it's perfectly working.
Edit2
I'm working on this issue but I still can't fix it. I changed my code about the detachNewThreadSelector. Here is my new code:
NSManagedObjectContext* context = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.persistentStoreCoordinator = self.persistentStoreCoordinator;
context.undoManager = nil;
[context performBlock:^
{
CCImages* cachedImage;
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
childContext.parentContext = context;
cachedImage=[CCImages getCCImageForKey:path inManagedObjectContext:childContext];
UIImage *image = [self getImageFromCacheWithPath:path andCachedImage:cachedImage atDate:now];
if (image != nil){
if(![weakSelf.delegate respondsToSelector:#selector(CacheCacheDidLoadImageFromCache:)])
[weakSelf setDelegate:appDelegate.callbacksCollector];
//[weakSelf useCallbackCollectorForDelegate:weakSelf inMethod:#"initPaginatorForListMoments"];
[weakSelf.delegate CacheCacheDidLoadImageFromCache:image];
}
}
- (UIImage*) getImageFromCacheWithPath:(NSString*) path andCachedImage:(CCImages *) cachedImage atDate: (NSDate *) now{
NSURL* localURL=[NSURL URLWithString:cachedImage.path relativeToURL:[self imageCacheDirectory]];
UIImage * image;
//restore uiimage from local file system
if (localURL) {
image=[UIImage imageWithContentsOfFile:[localURL path]];
//update cache
[cachedImage setLastAccessedAt:now];
[self saveContext];
if(image)
return image;
}
return nil;
}
Just after that, I'm saving my contexts (manually for now)
[childContext performBlock:^{
NSError *error = nil;
if (![childContext save:&error]) {
DDLogError(#"Error during context saving when getting image from cache : %#",[error description]);
}
else{
[context performBlock:^{
NSError *error = nil;
if (![context save:&error]) {
DDLogError(#"Error during context saving when getting image from cache : %#",[error description]);
}
}];
}
}];
There is a strange problem. My call back method is called without any problem on my controller (which implements the CacheCacheDidLoadImageFromCache: method). On this method I attest the reception of the image (DDLogInfo) and say that I want my spinner to stop. It does not directly but only 15secondes after the callback method was called.
My main problem is that my context (I guess) is still loading my image from the cache while it was already found. I said 'already' because the callback method has been called and the image was present. There is no suspicious activity of the CPU or of the memory. Instruments didn't find any leak.
I'm pretty sure that I'm using wrongly the NSManagedObjectContext but I can't find where.
You are using the old concurrency model of thread confinement, and violating it's rules (as described in the Core Data Concurrency Guide, which has not been updated yet for queue confinement). Specifically, you are trying to use an NSManagedObjectContext or NSManagedObject between multiple threads.
This is bad.
Thread confinement should not be used for new code, only to maintain the compatibility of old code while it's being migrated to queue confinement. This does not seem to apply to you.
To use queue confinement to solve your problem, first you should create a context attached to your persistent store coordinator. This will serve as the parent for all other contexts:
+ (NSManagedObjectContent *) parentContextWithPersistentStoreCoordinator:(NSPersistentStoreCoordinator *)coordinator {
NSManagedObjectContext *result = nil;
result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[result setPersistentStoreCoordinator:coordinator];
return result;
}
Next, you want the ability to create child managed object contexts. You will use these to perform work on the data, wether reading or writing. An NSManagedObjectContext is a scratchpad of the work you are doing. You can think of it as a transaction. For example, if you're updating the store from a detail view controller you would create a new child context. Or if you were performing a multi-step import of a large data set, you would create a child for each step.
This will create a new child context from a parent:
+ (NSManagedObjectContext *) childContextWithParent:(NSManagedObjectContext *)parent {
NSManagedObjectContext *result = nil;
result = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[result setParent:parent];
return result;
}
Now you have a parent context, and you can create child contexts to perform work. To perform work on a context, you must wrap that work in performBlock: to execute it on the context's queue. I do not recommend using performBlockAndWait:. That is intended only for re-rentrant methods, and does not provide an autorelease pool or processing of user events (user events are what drives nearly all of Core Data, so they're important. performBlockAndWait: is an easy way to introduce bugs).
Instead of performBlockAndWait: for your example above, create a method that takes a block to process the results of your fetch. The fetch, and the block, will run from the context's queue - the threading is done for you by Core Data:
- (void) doThingWithFetchResults:(void (^)(NSArray *results, NSError *error))resultsHandler{
if (resultsHandler != nil){
[[self context] performBlock:^{
NSArray *fetchResults = [[self context] executeFetchRequest:request error:&error];
resultsHandler(fetchResults, error);
}];
}
}
Which you would call like this:
[self doThingsWithFetchResults:^(NSArray *something, NSError *error){
if ([something count] > 0){
// Do stuff with your array of managed objects
} else {
// Handle the error
}
}];
That said, always prefer using an NSFetchedResultsController over using executeFetch:. There seems to be a belief that NSFetchedResultsController is for powering table views or that it can only be used from the main thread or queue. This is not true. A fetched results controller can be used with a private queue context as shown above, it does not require a main queue context. The delegate callbacks the fetched results controller emits will come from whatever queue it's context is using, so UIKit calls need to be made on the main queue inside your delegate method implementations. The one issue with using a fetched results controller this way is that caching does not work due to a bug.
Again, always prefer the higher level NSFetchedResultsController to executeFetch:.
When you save a context using queue confinement you are only saving that context, and the save will push the changes in that context to it's parent. To save to the store you must recursively save all the way. This is easy to do. Save the current context, then call save on the parent as well. Doing this recursively will save all the way to the store - the context that has no parent context.
Example:
- (void) saveContextAllTheWayBaby:(NSManagedObjectContext *)context {
[context performBlock:^{
NSError *error = nil;
if (![context save:&error]){
// Handle the error appropriately.
} else {
[self saveContextAllTheWayBaby:[context parentContext]];
}
}];
}
You do not, and should not, use merge notifications and mergeChangesFromContextDidSaveNotification: with queue confinement. mergeChangesFromContextDidSaveNotification: is a mechanism for the thread confinement model that is replaced by the parent-child context model. Using it can cause a whole slew of problems.
Following the examples above you should be able to abandon thread confinement and all of the issues that come with it. The problems you are seeing with your current implementation are only the tip of the iceberg.
There are a number of Core Data sessions from the past several years of WWDC that may also be of help. The 2012 WWDC Session "Core Data Best Practices" should be of particular interest.
if you want to use managed object context in background thread, there are two approaches,
1 Create a new context set concurrency type to NSPrivateQueueConcurrencyType and set the parentContext to main thread context
2 Create a new context set concurrency type to NSPrivateQueueConcurrencyType and set persistentStoreCoordinator to main thread persistentStoreCoordinator
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
privateContext.persistentStoreCoordinator = mainManagedObjectContext.persistentStoreCoordinator;
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {
NSManagedObjectContext *moc = mainManagedObjectContext;
if (note.object != moc) {
[moc mergeChangesFromContextDidSaveNotification:note];
}
}];
// do work here
// remember managed object is not thread save, so you need to reload the object in private context
});
before exist the thread, make sure remove the observer, bad thing can happen if you don't
for more details read http://www.objc.io/issue-2/common-background-practices.html
I'm working on an app that using core data, and everything is working so far. But I haven't used an image in it yet. I would like to do so but I don't know where to start. I would like to have the user click a button that gives them the option of locations of where they can get images. Also, is it possible to have a user just enter the URL of an image and have it save to core data? Thats really it so if someone could point me in the right direction I would appreciate it. Thanks!
My code so far: https://github.com/jackintosh7/Core-Data
EDIT - I updated some of the references in the saveImgData:fromURL to match the parameters passed in the method. I used a variation of this method within an NSURL protocol to intercept requests and cache specific ones. Some of the entity parameters may not apply to your question. Just disregard those (such as encoding/lastModified/mimeType/response).
To save an image to CoreData give this a try.
First, download your image. A user can enter the url in a text entry field. (The example listed by andrewbuilder will work just fine)
NSURL *imgURL = [NSURL URLWithString:myImgURL];
NSData *imgData = [NSData dataWithContentsOfURL:imgURL];
//a better way would be to do this asynchronously. Google "Lazy Image Loading" and
//you should find a suitable example app from Apple
Setup your CoreData entity to store the image data. Here is a sample that stores more than just the image binary data.
Then save your image data directly to CoreData.
I have a CoreData manager class but you may have your CoreData stack in AppDelegate which is how Apple sets them up.
-(void)saveImgData:(NSData*)myImgData fromURL:(NSString*)urlStr{
//If you have a CoreData manager class do something like this
CoreDataManager *cdm = [CoreDataManager defaultManager];
//Use private queue concurrency type for background saving
NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
//Set up the parent context, in this case is the mainMOC
//If you are using basic CoreData it would be the managedObjectContext on your AppDelegate
ctx.parentContext = cdm.mainMOC;
[ctx performBlock:^{
//performBlock executes on a background thread
CachedURLResponse *cacheResponse = [NSEntityDescription insertNewObjectForEntityForName:#"CachedURLResponse" inManagedObjectContext:ctx];
cacheResponse.relativeURLHash = [NSNumber numberWithInteger:[urlStr hash]];
cacheResponse.data = [myImgData copy];
cacheResponse.response = [NSKeyedArchiver archivedDataWithRootObject:self.response];
cacheResponse.url = [urlStr copy];
cacheResponse.timestamp = [NSDate date];
cacheResponse.mimeType = [self.response.MIMEType copy];
cacheResponse.encoding = [self.response.textEncodingName copy];
if ([self.headers objectForKey:#"Last-Modified"])
cacheResponse.lastModified = [self.headers objectForKey:#"Last-Modified"];
NSError *__block error;
if (![ctx save:&error])
NSLog(#"Error, Cache not saved: %#", error.userInfo);
[cdm.mainMOC performBlockAndWait: ^{
[cdm saveContext];
}];
}];
}
My CoreDataManager.h looks like this:
and my CoreDataManager.m file looks like this:
#import "CoreDataManager.h"
#import <CoreData/CoreData.h>
#implementation CoreDataManager
#synthesize mainMOC = _mainMOC,
managedObjectModel = _mom,
persistentStoreCoordinator = _psc
;
+(CoreDataManager*)defaultManager
{
static CoreDataManager *_defaultMgr = nil;
static dispatch_once_t oncePred;
dispatch_once(&oncePred, ^{
_defaultMgr = [[CoreDataManager alloc] init];
});
return _defaultMgr;
}
-(id)init
{
if (self = [super init])
{
NSError *error = nil;
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"MyDatabase.sqlite"];
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MyDataModelName" withExtension:#"momd"];
_mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
_psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
_mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainMOC setPersistentStoreCoordinator:self.persistentStoreCoordinator];
}
return self;
}
- (void)saveContext
{
NSError *error = nil;
if (self.mainMOC != nil && ([self.mainMOC hasChanges] && ![self.mainMOC save:&error])) {
NSLog(#"CoreData save error: %#, %#", error, [error userInfo]);
}
}
#pragma mark - Core Data Stack
- (NSManagedObjectContext *)mainMOC
{
return _mainMOC;
}
- (NSManagedObjectModel *)managedObjectModel
{
return _mom;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
return _psc;
}
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
#end
Hope that gives you a good starting point. Google "CoreData Class Reference" for some really good reading on the topic.
I learnt a lot from Paul Hegarty's excellent iTunes U / Stanford Uni lectures... his latest is called "Developing iOS Apps for iPhone and iPad" A couple of his lectures specifically address the use of images in Core Data. For what you are wanting to do I recommend you watch lectures 10-13, but really it is worth watching the entire series.
Beyond that, spend some time to read through the Apple Documentation on Core Data, starting here. Samir writes a good response to this stack overflow question.
You have a few options in how you implement the code. While Core Data is capable of managing the saving of images into the structure of your persistent data store (e.g. sqlite), it is important to note that large image files will slow down the fetch process.
Core Data can manage how to store the image for you... by clicking on the Allows External Storage option in the Data Model Inspector... see attached image below.
If however, you are looking to point a user to a URL, it may be worth considering adding (another separate) attribute to your entity Item, for example an attribute named pictureURL.
Use a property in a UIViewController to manage the entity attribute...
#property (nonatomic, retain) NSString * pictureURL;
Then you can add a UITextField to a UIViewController to accept user input for this property pictureURL.
When you have a URL for an image, you can return the image with "getter" code similar to this (where the UIImage is a property of the UIViewController)...
- (UIImage *)image {
NSURL *imageURL = [NSURL URLWithString:self.pictureURL];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
return [UIImage imageWithData:imageData];
}
Hope that provides you with some direction.
According to NSManagedObjectContext Class Documentation...
- (NSArray *)executeFetchRequest:(NSFetchRequest *)request error:(NSError **)error
Return Value
An array of objects that meet the criteria specified by request fetched from the receiver and from the persistent stores associated with the receiver’s persistent store coordinator. If an error occurs, returns nil. If no objects match the criteria specified by request, returns an empty array.
I'm trying to create a unit test for the situation "if an error occurs, returns nil."
I would like to stay away from using OCMock (or subclassing NSManagedObjectContext to override the executeFetchRequest:error: method) because I figure there's an easy way to ensure failure of this method. So far my unit test reads...
- (void)testReportingCoreDataErrorToDelegate
{
NSManagedObjectContext *badContext = [[NSManagedObjectContext alloc] init];
[bcc setManagedObjectContext:badContext];
[bcc fetchFromCoreData];
STAssertTrue([mockDelegate didReceiveCoreDataError], #"This never asserts, it fails because the fetch request couldn't find an entity name - i.e. no managed object model");
}
Is there a simple way to trigger a fetch request returning nil?
I had the same conundrum. I like to keep unit test coverage at 100% whenever possible. There is no easy way to generate an organic error condition. In fact, I'm not sure the current implementation of the 4 store types that come with Core Data will ever trigger an error in response to executeFetchRequest:error. But as it could happen in the future, here is what I did:
I have one unit test case file that is dedicated to validating how my classes handle errors populated by executeFetchRequest:error. I define a subclass of NSIncrementalStore that always produces an error during requests in the implementation file. [NSManagedObjectContext executeFetchRequest:error] is processed by [NSPersistentStoreCoordinator executeRequest:withContext:error:] which processes [NSPersistentStore executeRequest:withContext:error:] on all stores. You may notice that the word "fetch" drops when you move to the coordinator - saves and fetch requests are handled by the same method executeRequest:withContext:error:. So I get coverage for testing against save errors and fetch requests by defining a NSPersistentStore that will always respond to saves and fetches with errors.
#define kErrorProneStore #"ErrorProneStore"
#interface ErrorProneStore : NSIncrementalStore
#end
#implementation ErrorProneStore
- (BOOL)loadMetadata:(NSError **)error
{
//Required - Apple's documentation claims you can omit setting this, but I had memory allocation issues without it.
NSDictionary * metaData = #{NSStoreTypeKey : kErrorProneStore, NSStoreUUIDKey : #""};
[self setMetadata:metaData];
return YES;
}
-(void)populateError:(NSError **)error
{
if (error != NULL)
{
*error = [[NSError alloc] initWithDomain:NSCocoaErrorDomain
code:NSPersistentStoreOperationError
userInfo:nil];
}
}
- (id)executeRequest:(NSPersistentStoreRequest *)request
withContext:(NSManagedObjectContext *)context
error:(NSError **)error
{
[self populateError:error];
return nil;
}
- (NSIncrementalStoreNode *)newValuesForObjectWithID:(NSManagedObjectID *)objectID
withContext:(NSManagedObjectContext *)context
error:(NSError **)error
{
[self populateError:error];
return nil;
}
- (id)newValueForRelationship:(NSRelationshipDescription *)relationship
forObjectWithID:(NSManagedObjectID *)objectID
withContext:(NSManagedObjectContext *)context
error:(NSError **)error
{
[self populateError:error];
return nil;
}
- (NSArray *)obtainPermanentIDsForObjects:(NSArray *)array
error:(NSError **)error
{
[self populateError:error];
return nil;
}
#end
Now you can construct the Core Data stack using the ErrorProneStore and be guaranteed your fetch requests will return nil and populate the error parameter.
- (void)testFetchRequestErrorHandling
{
NSManagedObjectModel * model = [NSManagedObjectModel mergedModelFromBundles:nil];
[NSPersistentStoreCoordinator registerStoreClass:[ErrorProneStore class]
forStoreType:kErrorProneStore];
NSPersistentStoreCoordinator * coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSManagedObjectContext * context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[context setPersistentStoreCoordinator:coordinator];
[coordinator addPersistentStoreWithType:kErrorProneStore
configuration:nil
URL:nil
options:nil
error:nil];
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:#"AValidEntity"];
NSError * error;
[context executeFetchRequest:request
error:&error];
STAssertNotNil(error, #"Error should always be nil");
}
In my opinion it is much easier to use OCMock.
- (void)testCountForEntityFetchError {
id mockContext =[OCMockObject partialMockForObject:self.context];
[[[mockContext stub] andCall:#selector(stubbedExecuteFetchRequest:error:) onObject:self] countForFetchRequest:OCMOCK_ANY error:[OCMArg setTo:nil]];
// Your code goes here
}
- (NSArray *)stubbedExecuteFetchRequest:(NSFetchRequest *)request error:(NSError **)error {
*error = [NSError errorWithDomain:#"CRTest" code:99 userInfo:nil];
return nil;
}
I am working an iPhone app and a Mac app that use Core Data.
I would like to have these 2 apps synchronise their databases via iCloud storage.
I have made adjustments to the implementations of the managedObjectContext & persistentStoreCoordinator & added mergeiCloudChanges - from the updated Recipes example code:
#pragma mark -
#pragma mark Core Data stack
// this takes the NSPersistentStoreDidImportUbiquitousContentChangesNotification
// and transforms the userInfo dictionary into something that
// -[NSManagedObjectContext mergeChangesFromContextDidSaveNotification:] can consume
// then it posts a custom notification to let detail views know they might want to refresh.
// The main list view doesn't need that custom notification because the NSFetchedResultsController is
// already listening directly to the NSManagedObjectContext
- (void)mergeiCloudChanges:(NSNotification*)note forContext:(NSManagedObjectContext*)moc {
NSLog(#"merging iCloud stuff");
[moc mergeChangesFromContextDidSaveNotification:note];
NSNotification* refreshNotification = [NSNotification notificationWithName:#"RefreshAllViews" object:self userInfo:[note userInfo]];
[[NSNotificationCenter defaultCenter] postNotification:refreshNotification];
}
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext
{
if (managedObjectContext != nil)
{
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
if (IOS_VERSION_GREATER_THAN_OR_EQUAL_TO(#"5.0")) {
NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc performBlockAndWait:^{
[moc setPersistentStoreCoordinator: coordinator];
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:coordinator];
}];
managedObjectContext = moc;
} else {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator:coordinator];
}
}
return managedObjectContext;
}
// NSNotifications are posted synchronously on the caller's thread
// make sure to vector this back to the thread we want, in this case
// the main thread for our views & controller
- (void)mergeChangesFrom_iCloud:(NSNotification *)notification {
NSManagedObjectContext* moc = [self managedObjectContext];
// this only works if you used NSMainQueueConcurrencyType
// otherwise use a dispatch_async back to the main thread yourself
[moc performBlock:^{
[self mergeiCloudChanges:notification forContext:moc];
}];
}
/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created by merging all of the models found in the application bundle.
*/
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];
return managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator__ != nil) {
return persistentStoreCoordinator__;
}
// assign the PSC to our app delegate ivar before adding the persistent store in the background
// this leverages a behavior in Core Data where you can create NSManagedObjectContext and fetch requests
// even if the PSC has no stores. Fetch requests return empty arrays until the persistent store is added
// so it's possible to bring up the UI and then fill in the results later
persistentStoreCoordinator__ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
// prep the store path and bundle stuff here since NSBundle isn't totally thread safe
NSPersistentStoreCoordinator* psc = persistentStoreCoordinator__;
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:#"MyApp.sqlite"];
// do this asynchronously since if this is the first time this particular device is syncing with preexisting
// iCloud content it may take a long long time to download
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
// this needs to match the entitlements and provisioning profile
NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:#"MyApp"];
cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
NSLog(#"cloudURL: %#", cloudURL);
// The API to turn on Core Data iCloud support here.
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:#"xxxxxxxx.com.me.MyApp",
#"MyApp",
cloudURL,
NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],
NSInferMappingModelAutomaticallyOption,
nil];
NSError *error = nil;
[psc lock];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible
* The schema for the persistent store is incompatible with current managed object model
Check the error message to determine what the actual problem was.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[psc unlock];
// tell the UI on the main thread we finally added the store and then
// post a custom notification to make your views do whatever they need to such as tell their
// NSFetchedResultsController to -performFetch again now there is a real store
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"asynchronously added persistent store!");
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefetchAllDatabaseData" object:self userInfo:nil];
});
});
return persistentStoreCoordinator__;
}
I can see files appear in my "/Users/me/Library/Mobile Documents" directory when i build/run myapp.
But I have no idea if it is syncing over to the iCloud storage - and obviously the data between the iphone and mac is not synced.
Are there other methods I need to implement to make the data move to the cloud?
And is there any way for me to view what documents are actually on the iCloud storage?
Here is a quick partial answer.
You can see what is stored in iCloud:
On the Mac:
System Preferences.app -> iCloud -> click on 'Manage...' you will then see a list of all apps that have documents stored Mac OS X or iOS.
On iOS:
Preferences -> iCloud -> Archive & Backup -> option below Space used you will then see a list of all apps that have documents stored Mac OS X or iOS.
As long as you are using NSFileManager's setUbiquitous: itemAtURL: destinationURL: error:the documents should be getting sent to iCloud for you and showing up on other devices.
Another partial answer. Take a look at : http://www.raywenderlich.com/6015/beginning-icloud-in-ios-5-tutorial-part-1 if you have not yet. I am working on the same thing as you namely, Mac App and iOS
app sharing data. Good Luck. Mark
I just learned about : http://mentalfaculty.tumblr.com/archive
Look for "Under The Sheets" CoreData and iCloud. Check it out!
OK my code look a bit different, I have it in a separate class to reuse it for all my projects. Nevertheless if iCloud is enabled (URLForUbiquityContainerIdentifier:nil return not nil) I setup my NSPersistentStoreCoordinator like this:
// ---- iCloud Setup
// fist container in entitlements
NSURL *iCloudDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
// if iCloud is enabled setup the store
if (iCloudDirectoryURL) {
__iCloudEnabled = true;
NSLog(#"iCloud:%#", [iCloudDirectoryURL absoluteString]);
// AppDelegate has to provide the contentnamekey
NSString *contentNameKey = [appDelegate dataStoreContentNameKey];
options = [NSDictionary dictionaryWithObjectsAndKeys:contentNameKey, NSPersistentStoreUbiquitousContentNameKey, iCloudDirectoryURL, NSPersistentStoreUbiquitousContentURLKey, nil];
}
I miss where you setup the NSPersistentStoreUbiquitousContentNameKey.
The second is clear i guess, this works only on the device and your App ID need iCloud enabled.