rapid persistent store for database from UIManagedDocument - core-data

I created a database through a UIManagedDocument. If I add records slowly, everything works. If I rapidly (3-4 seconds between records), the records are out of order when fetched and may not survive across the app stopping/starting.
The UIManagedDocument is created in viewDidAppear and modified in "useDocument" (code provided below) which checks for non-existant database and creates it if necessary or just opens it.
Again, I think my issue is that the core data is not immediately stored to the SQLite.
-(void)setExampleDatabase:(UIManagedDocument *)exampleDatabase {
if ( _exampleDatabase != exampleDatabase ) {
_exampleDatabase = exampleDatabase;
NSMutableDictionary *pragmaOptions = [NSMutableDictionary dictionary];
[pragmaOptions setObject:#"FULL" forKey:#"synchronous"];
[pragmaOptions setObject:#"1" forKey:#"fullfsync"];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
pragmaOptions, NSSQLitePragmasOption,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES, NSInferMappingModelAutomaticallyOption, nil];
exampleDatabase.persistentStoreOptions = options;
//
// Does the file exist?? Is not, create it.
if ( ! [[NSFileManager defaultManager] fileExistsAtPath: [self.exampleDatabase.fileURL path]] ) {
[self.exampleDatabase saveToURL:self.exampleDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
//
// OK, the creation has been done, enable the add button so records can be created
self.addButton.enabled = YES;
}];
//
// The file exists, but it's close, so open it.
} else if ( self.exampleDatabase.documentState == UIDocumentStateClosed ) {
[self.exampleDatabase openWithCompletionHandler:^(BOOL success) {
if ( success ) {
//
// It's now successfully opened, enable the add button, load the data from
// the core data database and cause the objects to be displayed in the table view
// "getCounts" is immediately below this method
self.addButton.enabled = YES;
[self getCounts:self.exampleDatabase.managedObjectContext];
[self.tableView reloadData];
} else {
NSLog(#"Error opening a closed database");
}
} ];
} else if ( self.exampleDatabase.documentState == UIDocumentStateNormal ) {
//
// OK, the database is opened. Enable the add button, load the data from
// the core data database and cause the objects to be displayed in the table view
// "getCounts" is immediately below this method
self.addButton.enabled = YES;
[self getCounts:self.exampleDatabase.managedObjectContext];
[self.tableView reloadData];
} else {
NSLog(#"Something is wrong in useDocument - it exists in an unknown state");
exit(1);
}
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
if ( ! self.exampleDatabase ) {
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"DefaultDb"];
self.exampleDatabase = [[UIManagedDocument alloc] initWithFileURL:url];
}
}

Related

Save data with Magical Record inside a block

I am retrieving some data from an API resource and I want to store the result inside my City entity using Magical Record and when the process finish, reload a tableView in my ViewController with the results.
All is fine but when I start the app for the first time,dowload process is started and the data is saved in core data.
but the table view in my ViewControllers is empty.
If I launch the app after the first time
the tableView refresh correctly.
I don't know if the problem is in threads... Can anybody help me?
ViewController :
Here I start the request. When block is called, I store cities array and reload tableView
- (void)getCitiesFromDataStore {
[[APIManager sharedManager] getCitiesWithCompletion:^(NSArray *cities) {
_dataSourceArray = cities;
[self.citiesTableView reloadData];
} failure:^(NSError *error) {
NSLog(#"%#",error.localizedDescription);
}];
}
APIMAnager
- (void)getCitiesWithCompletion:(void (^)(NSArray *))succesBlock
failure:(void (^)(NSError *))errorBlock
{
NSArray *cachedCities = [City findAllCities];
if ([cachedCities count] == 0) {
[self GET:#"cities" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *results = responseObject[#"cities"];
[City MR_importFromArray:results];
[[NSManagedObjectContext MR_defaultContext] MR_saveToPersistentStoreAndWait];
NSArray *cities = [City findAllCities];
succesBlock(cities);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
errorBlock(error);
}];
return;
}
// Si ya hay ciudades almacenadas en CoreData, devuelvo el
// succesblock con las ciudades de CoreData
succesBlock(cachedCities);
}
I have a Category also to manage actions with the City entity
City+DBOperations
+ (NSArray *)findAllCities
{
NSArray *cities = [City MR_findAll];
return cities;
}
I know you said you resolved it, but for others who might be coming here another thing you could try is wrapping the import in a saveWithBlock:completion: and do your find in the completion block.
Also make sure you know which context each method is using. It is often helpful to be explicit about that.
Therefore you could change it to (this is untested, but should give you the concept):
[self GET:#"cities" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *results = responseObject[#"cities"];
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
[City MR_importFromArray:results inContext:localContext];
} completion:^(BOOL contextDidSave, NSError *error) {
NSArray *cities = [User MR_findAllInContext:[NSManagedObjectContext MR_defaultContext]];
succesBlock(cities);
}];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
errorBlock(error);
}];

UImanagedDocument's persistentStore iCloud backup

Im trying to create backups for core data UIManagedDocument, and store them in iCloud. Its been > 2 months since im trying to do this and i dont understand how to do that. there is no info in the internet at all...
im trying to create local backups at least and it doesnt work either
this is the code :
-(void)testCopyStoreToDocuments
{
AppDelegate* appDelegate=(AppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate.userDataDocument closeWithCompletionHandler:^(BOOL closed)
{
if(closed)
{
#autoreleasepool {
NSFileCoordinator *fc = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSString *sourceFile = [[[[LoadingManager localDocumentURL]URLByAppendingPathComponent:#"StoreContent"]path] stringByAppendingPathComponent:#"persistentStore"];
NSURL *sourceURL = [NSURL fileURLWithPath:sourceFile];
[fc coordinateReadingItemAtURL:sourceURL options:NSFileCoordinatorReadingWithoutChanges error:nil byAccessor:^(NSURL *sourceURLtoUse) {
NSError *error = nil;
NSFileManager *fm = [[NSFileManager alloc] init];
NSString *destinationFile = [[[LoadingManager localDocumentsDirectoryURL]path] stringByAppendingPathComponent:#"persistentStore"];
//simply copy the file over
BOOL copySuccess = [fm copyItemAtPath:[sourceURLtoUse path]
toPath:destinationFile
error:&error];
if (copySuccess) {
NSLog(#" copied file successfully");
} else {
NSLog(#"Error copying item at path: %#\nTo path: %#\nError: %#", sourceFile, destinationFile, error);
}
}];
fc = nil;
}
}
}];
}
-(void)testReplaceStore
{
AppDelegate* appDelegate=(AppDelegate*)[UIApplication sharedApplication].delegate;
[appDelegate.userDataDocument closeWithCompletionHandler:^(BOOL closed)
{
if(closed)
{
NSFileCoordinator *fc = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
[fc coordinateWritingItemAtURL:[[[LoadingManager localDocumentURL]URLByAppendingPathComponent:#"StoreContent"]URLByAppendingPathComponent:#"persistentStore"] options:NSFileCoordinatorWritingForDeleting error:nil byAccessor:^(NSURL *sourceURLtoUse){
NSLog(#"%#",sourceURLtoUse);
NSError * error = nil;
NSLog(#"replacment: %hhd",[[NSFileManager defaultManager]replaceItemAtURL:sourceURLtoUse withItemAtURL:[[LoadingManager localDocumentsDirectoryURL]URLByAppendingPathComponent:#"persistentStore"] backupItemName:#"backUp" options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:nil error:&error]);
NSLog(#"%#",error);
}];
NSLog(#"stores: %#",appDelegate.userDataDocument.managedObjectContext.persistentStoreCoordinator.persistentStores);
[appDelegate.userDataDocument saveToURL:appDelegate.userDataDocument.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL saved)
{
if(saved)
{
[appDelegate.userDataDocument openWithCompletionHandler:^(BOOL opened)
{
if(opened)
{
NSLog(#"opened");
}
}];
}
else
{
NSLog(#"failed to save");
NSLog(#"stores: %#",appDelegate.userDataDocument.managedObjectContext.persistentStoreCoordinator.persistentStores);
}
}];
}
}];
}
It Nslogs an error when replacing is called :
replacment: 0
Error Domain=NSCocoaErrorDomain Code=512 "The operation couldn’t be completed. (Cocoa error 512.)" UserInfo=0x15d91540 {NSFileNewItemLocationKey=file:///var/mobile/Applications/85974C93-75FD-406A-B1BF-EDE7DFC25FE2/Documents/persistentStore, NSFileOriginalItemLocationKey=file:///var/mobile/Applications/85974C93-75FD-406A-B1BF-EDE7DFC25FE2/Documents/Data%20Document/StoreContent/persistentStore, NSUnderlyingError=0x15db0080 "The operation couldn’t be completed. (Cocoa error 260.)",
I just tested this code and it works fine. Here is a sample app http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/
You will get that error if the target file already exists.
/** Copies file to iCloud container removing the target file if it already exists
#param docURL URL of UIManagedDocument whose store file is to be copied (its Core Data Store must not be shared in iCloud!
*/
- (void)copyFileToICloud:(NSURL*)docURL {
#autoreleasepool {
NSFileCoordinator *fc = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSURL *sourceURL = [[docURL URLByAppendingPathComponent:#"StoreContent"] URLByAppendingPathComponent:#"persistentStore"];
// Local directory - test
//NSString *destinationFile = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:#"persistentStore_backup"];
NSURL *destinationURL = [[[[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil] URLByAppendingPathComponent:#"Documents"] URLByAppendingPathComponent:#"persistentStore_backup"];
FLOG(#" source file is %#", sourceURL);
FLOG(#" target file is %#", destinationURL);
NSError *cError;
[fc coordinateWritingItemAtURL:destinationURL options:NSFileCoordinatorWritingForReplacing error:&cError byAccessor:^(NSURL *newURL) {
NSError *error = nil;
NSFileManager *fm = [[NSFileManager alloc] init];
// Delete it if it already exists
if ([fm fileExistsAtPath:[newURL path]]) {
FLOG(#" target file exists");
if (![fm removeItemAtURL:newURL error:&error]) {
FLOG(#" error unable to remove target file");
NSLog(#"Error removing item Error: %#, %#", error, error.userInfo);
return;
} else {
FLOG(#" target file removed");
}
}
//simply copy the file over
BOOL copySuccess = [fm copyItemAtPath:[sourceURL path]
toPath:[newURL path]
error:&error];
if (copySuccess) {
NSLog(#" copied file successfully");
} else {
NSLog(#"Error copying items Error: %#, %#", error, error.userInfo);
}
}];
if (cError) {
FLOG(#" error is %#", cError);
}
fc = nil;
}
}
EDIT: A few additional things to remember:
If you are using WAL mode (or the default Core Data mode for iOS7, OS X 10.9) then you have to copy the ~wal and ~shm files as well - better copy the entire StoreContent directory.
To copy the files from iCloud you will have to do a metadata query to find the files and then check the download status, I think you will have to trigger the download and check the files have downloaded before you can copy them.

FBLoginView: login isn't performed

I'm adding FBLoginView to my ViewController < FBLoginViewDelegate >:
FBLoginView *loginview = [[FBLoginView alloc] init];
loginview.frame = CGRectOffset(loginview.frame, 5, 5);
loginview.delegate = self;
[self.view addSubview:loginview];
[loginview sizeToFit];
All the necessary fields in plist (FacebookAppID, FacebookDisplayName, URL Schemes) are all set according to the tutorial. The facebook app is also configured according to the tutorial (bundle ID is set, Facebook login is enabled).
But the login still isn't performed. When I press on "log in", I get redirected to the browser with facebook login, but when it's finished, I'm not logged in the app (loginViewFetchedUserInfo:user: isn't called, "log in" hasn't changed to "log out").
What can be the problem?
Everything worked after I implemented the following in the AppDelegate.m (taken from one of the official examples):
- (void)sessionStateChanged:(FBSession *)session
state:(FBSessionState) state
error:(NSError *)error
{
switch (state) {
case FBSessionStateOpen:
if (!error) {
// We have a valid session
//NSLog(#"User session found");
[FBRequestConnection
startForMeWithCompletionHandler:^(FBRequestConnection *connection,
NSDictionary<FBGraphUser> *user,
NSError *error) {
if (!error) {
self.loggedInUserID = user.id;
self.loggedInSession = FBSession.activeSession;
}
}];
}
break;
case FBSessionStateClosed:
case FBSessionStateClosedLoginFailed:
[FBSession.activeSession closeAndClearTokenInformation];
break;
default:
break;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:FBSessionStateChangedNotification
object:session];
if (error) {
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
}
/*
* Opens a Facebook session and optionally shows the login UX.
*/
- (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI {
return [FBSession openActiveSessionWithReadPermissions:nil
allowLoginUI:allowLoginUI
completionHandler:^(FBSession *session,
FBSessionState state,
NSError *error) {
[self sessionStateChanged:session
state:state
error:error];
}];
}
/*
*
*/
- (void) closeSession {
[FBSession.activeSession closeAndClearTokenInformation];
}
/*
* If we have a valid session at the time of openURL call, we handle
* Facebook transitions by passing the url argument to handleOpenURL
*/
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// attempt to extract a token from the url
return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
}
You need to add the following to the app delegate
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
// Call FBAppCall's handleOpenURL:sourceApplication to handle Facebook app responses
BOOL wasHandled = [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
// You can add your app-specific url handling code here if needed
return wasHandled;
}
You may need to implement the
(BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url method
Be sure to call:
return [FBSession.activeSession handleOpenURL:url];
When applicable.
#Sergey: Are you want to open FBDialog on your native app or in browser? If you to want open in your native app then use "FBSessionLoginBehaviorForcingWebView". Here is my code that I am using:
NSArray *permission = [NSArray arrayWithObjects:kFBEmailPermission,kFBUserPhotosPermission, nil];
FBSession *session = [[FBSession alloc] initWithPermissions:permission];
[FBSession setActiveSession: [[FBSession alloc] initWithPermissions:permission] ];
[[FBSession activeSession] openWithBehavior:FBSessionLoginBehaviorForcingWebView completionHandler:^(FBSession *session, FBSessionState status, NSError *error) {
switch (status) {
case FBSessionStateOpen:
[self yourmethod];
break;
case FBSessionStateClosedLoginFailed: {
// prefer to keep decls near to their use
// unpack the error code and reason in order to compute cancel bool
NSString *errorCode = [[error userInfo] objectForKey:FBErrorLoginFailedOriginalErrorCode];
NSString *errorReason = [[error userInfo] objectForKey:FBErrorLoginFailedReason];
BOOL userDidCancel = !errorCode && (!errorReason || [errorReason isEqualToString:FBErrorLoginFailedReasonInlineCancelledValue]);
if(error.code == 2) {
UIAlertView *errorMessage = [[UIAlertView alloc] initWithTitle:#"FBAlertTitle"
message:#"FBAuthenticationErrorMessage"
delegate:nil
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[errorMessage performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
errorMessage = nil;
}
}
break;
// presently extension, log-out and invalidation are being implemented in the Facebook class
default:
break; // so we do nothing in response to those state transitions
}
}];
permission = nil;
or you want to open in browser then use following :
In your .h file
#import <FacebookSDK/FacebookSDK.h> and add FBLoginViewDelegate delegate
In you .m file
FBLoginView *loginview = [[FBLoginView alloc] init];
loginview.frame = CGRectOffset(loginview.frame, 5, 5);
loginview.delegate = self;
[self.view addSubview:loginview];
[loginview sizeToFit];
// use following delegate methods
- (void)loginViewShowingLoggedInUser:(FBLoginView *)loginView {
// first get the buttons set for login mode
}
- (void)loginViewFetchedUserInfo:(FBLoginView *)loginView
user:(id<FBGraphUser>)user {
// here we use helper properties of FBGraphUser to dot-through to first_name and
// id properties of the json response from the server; alternatively we could use
// NSDictionary methods such as objectForKey to get values from the my json object
NSLog(#"userprofile:%#",user);
}
- (void)loginViewShowingLoggedOutUser:(FBLoginView *)loginView {
//BOOL canShareAnyhow = [FBNativeDialogs canPresentShareDialogWithSession:nil];
}
- (void)loginView:(FBLoginView *)loginView handleError:(NSError *)error {
// see https://developers.facebook.com/docs/reference/api/errors/ for general guidance on error handling for Facebook API
// our policy here is to let the login view handle errors, but to log the results
NSLog(#"FBLoginView encountered an error=%#", error);
}

What is the best way to remove logs file Core Data creates, when removing a UIManagedDocument from iCloud?

I would have thought NSFileManagers method of removeItemAtURL:error: would remove the Core Data log files created when using UIManagedDocuments with iCloud.
What is the best way to make sure all of these log files are removed?
I have used...
- (void)deleteRemnantsOfOldDatabaseDocumentAndItsTransactionLogsWithCompletionHandler:(completion_success_t)completionBlock
{
__weak CloudController *weakSelf = self;
NSURL *databaseStoreFolder = self.iCloudDatabaseStoreFolderURL;
NSURL *transactionLogFolder = self.transactionLogFilesFolderURL;
[self deleteFileAtURL:databaseStoreFolder withCompletionBlock:^(BOOL docSuccess) {
[weakSelf deleteFileAtURL:transactionLogFolder withCompletionBlock:^(BOOL logSuccess) {
completionBlock(docSuccess && logSuccess);
}];
}];
}
In conjunction with...
- (void)deleteFileAtURL:(NSURL *)fileURL withCompletionBlock:(completion_success_t)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileCoordinator *fileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
NSError *coordinatorError = nil;
__block BOOL success = NO;
[fileCoordinator coordinateWritingItemAtURL:fileURL
options:NSFileCoordinatorWritingForDeleting
error:&coordinatorError
byAccessor:^(NSURL *writingURL) {
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSError *removalError = nil;
if ([fileManager fileExistsAtPath:[writingURL path]]) {
if (![fileManager removeItemAtURL:writingURL error:&removalError]) {
NSLog(#"deleteFileAtURL: removal error: %#", removalError);
} else {
success = YES;
}
}
}];
if (coordinatorError) {
NSLog(#"deleteFileAtURL: coordinator error: %#", coordinatorError);
}
completionBlock(success);
});
}
Note: this was used for a single document toolbox style app, and was intended more for clearing out the iCloud container before creating a brand new document, in an 'apparently' empty iCloud store for the first time. But I'm sure it can be adapted without too much work.
Oops, the above won't make sense/work without:
typedef void (^completion_success_t)(BOOL success);
You can debug the contents of your iCloud container and verify things have been removed by using a method like (which to be honest I've probably lifted from somewhere else and modified):
- (void)logDirectoryHierarchyContentsForURL:(NSURL *)url
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtURL:url
includingPropertiesForKeys:#[NSURLNameKey, NSURLContentModificationDateKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:nil];
NSMutableArray *results = [NSMutableArray array];
for (NSURL *itemURL in directoryEnumerator) {
NSString *fileName;
[itemURL getResourceValue:&fileName forKey:NSURLNameKey error:NULL];
NSDate *modificationDate;
[itemURL getResourceValue:&modificationDate forKey:NSURLContentModificationDateKey error:NULL];
[results addObject:[NSString stringWithFormat:#"%# (%#)", itemURL, modificationDate]];
}
NSLog(#"Directory contents: %#", results);
}
And it's also worth logging onto developer.icloud.com and examining what is actually in the iCloud store. There is sometimes a difference between what is retained in the device ubiquity container, and what is actually in the iCloud server folder structure. Between all of these you can get quite a good idea of what's going on.

Using Core Data in IOS6 (delete/edit)

I am saving the latest internet request of my tableviewdata in an (core data) entity, but have problems with error exceptions about "faults".
I have two methods 'loadData' which gets the latest 'ordersitems' that will be loaded in my tableview AND 'loadThumbnails' which will try to cache the thumbnail into the core data entity.
The problem occurs when the managedobject gets deleted and the thumbnail method still tries to access it. Though i made a variable stopThumbnails to stop the loadThumbnails method, the problem keeps occurring.
What is the proper iOS 6 way to lazyload the images and save them to coredata but check if the object has not been deleted? i found this Core Data multi thread application which was useful but my newbie understanding of core data is still limited and i have problems writing code. I read the apple docs about http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/coredata/Articles/cdConcurrency.html but it was hard to understand completely.
I want at least my http request to load asychronous (but preferably as much as possible) i came up with the following:
-(void)viewdidload
{
NSFetchRequest *fetchReq = [NSFetchRequest fetchRequestWithEntityName:#"OrderItems"];
fetchReq.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]];
self.data = [self.managedObjectContext executeFetchRequest:fetchReq error:nil];
MYFILTER = #"filter=companyX";
[self loadData];
}
-(void)loadData
{
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//json request from url
NSDictionary *reqData = myOrderJSONRequest(MYFILTER);
dispatch_async( dispatch_get_main_queue(), ^{
if(reqData!=NULL && reqData!=nil)
{
//request successful so delete all items from entity before inserting new ones
stopThumbnails = YES;
for(int i=self.data.count-1;i>=0;i--)
{
[self.managedObjectContext deleteObject:[self.data objectAtIndex:i]];
}
[self.managedObjectContext save:nil];
if(reqData.count>0)
{
//insert latest updates
for (NSDictionary *row in reqData){
OrderItem *item = [NSEntityDescription insertNewObjectForEntityForName:#"OrderItem" inManagedObjectContext:self.managedObjectContext];
item.order_id = [NSNumber numberWithInt:[[row objectForKey:#"order_id"] intValue]];
item.description = [row objectForKey:#"description"];
item.thumbnail_url = [row objectForKey:#"thumbnail_url"];
}
[self.managedObjectContext save:nil];
}
NSFetchRequest *fetchReq = [NSFetchRequest fetchRequestWithEntityName:#"OrderItems"];
fetchReq.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES]];
self.data = [self.managedObjectContext executeFetchRequest:fetchReq error:nil];
[TableView reloadData];
//LOAD THUMBNAILS ASYNCHRONOUS
stopThumbnails = NO;
[self loadThumbnails];
}
else{
//NO INTERNET
}
});
});
}
-(void)loadThumbnails
{
if(!loadingThumbnails)
{
loadingThumbnails = YES;
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i=0;i<self.data.count; i++) {
if(!stopThumbnails)
{
OrderItem *item = [self.data objectAtIndex:i];
if(item.thumbnail==NULL)
{
//ASYNCHRONOUS IMAGE REQUEST
NSURL *image_url = [NSURL URLWithString:item.thumbnail_url];
NSData *image_data = [NSData dataWithContentsOfURL:image_url];
dispatch_async( dispatch_get_main_queue(), ^{
if(image_data!=nil && image_data!=NULL && !stopThumbnails)
{
//IMAGE REQUEST SUCCESSFUL
item.thumbnail = image_data;
[self.managedObjectContext save:nil];
//RELOAD AFFECTED TABLEVIEWCELL
NSIndexPath* rowToReload = [NSIndexPath indexPathForRow:i inSection:0];
NSArray* rowsToReload = [NSArray arrayWithObjects:rowToReload, nil];
[TableView reloadRowsAtIndexPaths:rowsToReload withRowAnimation:UITableViewRowAnimationFade];
}
else
{
loadingThumbnails = NO;
return;
}
});
}
if(stopThumbnails)
{
dispatch_async( dispatch_get_main_queue(), ^{
loadingThumbnails = NO;
return;
});
}
}
else{
dispatch_async( dispatch_get_main_queue(), ^{
loadingThumbnails = NO;
return;
});
}
}
dispatch_async( dispatch_get_main_queue(), ^{
loadingThumbnails = NO;
return;
});
});
}
}
Any help is of course greatly appreciated :)
Well i dont know if this is the right approach but it works, so i'll mark this as an answer.
To do everything on the background i used a second nsmanagedobjectcontext (MOC) and then merge the changes to the main MOC. the dispatch queue works great although i had to use the NSManagedObjectContextDidSaveNotification in order to merge the changes of the two contexts.
since IOS 5 its possible to use blocks instead that do the merging for you. So i decided to use this instead of the dispatch way (this way i didnt have to use notofications).
Also using blocks i got the same problem (faults) when an object got selected on a background queue while is was deleted on a different queue. So i decided instead of deleting it right away, insert a NSDate 'deleted' property for the OrderItem. then have a timer with a delete check to see if there are objects that have been deleted longer than 10 minutes ago. This way i am sure no thumbnail was still downloading. It works. Though i would still like to know id this is the right approach :)

Resources