I'm using iCloud in my app to load text files. When loading text files, this method is called by iCloud when I call _UIDocument openWithCompletionHandler:^(BOOL success) etc:
-(BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError {
NSLog(#"Library loadFromContents: state = %d", self.documentState);
if (!_books) {
_books = [[NSMutableArray alloc] init];
}
//if (self.documentState > 7) {
// NSLog(#"document is either savingError (4), EditingDisabled (8) or both (12)... will not load");
// return NO;
//}
self.books = [NSKeyedUnarchiver unarchiveObjectWithData:contents];
if ([_delegate respondsToSelector:#selector(libraryDocumentUpdated:)]) {
[_delegate libraryDocumentUpdated:self];
}
return YES;
}
Now the big problem is when documentState is 8 (UIDocumentStateEditingDisabled) or 12 (UIDocumentStateSavingError & UIDocumentStateEditingDisabled). This will usually lead to a crash of the app. I tried to return NO if the documentState is > 7, i.e. if it is either 8 or 12 but this results in not loading any contents at all.
I guess the problem is that the UIDocument won't load anything into self.books if it editing is disabled or if there was a saving error.
What would be a good practice to handle such errors? Also, why didn't Apple suggest in their sample code to check the documentState before loading data into UIDocument (iCloud Docs)? I guess that I'm doing something fundamentally wrong.
Do you have conflict management implemented ?
In those scenarios you should attempt a number of things then retry loading the file the first being to check if
[NSFileVersion unresolvedConflictVersionsOfItemAtURL:]
has any conflicts, resolve them, retry opening the file,
[NSFileManager evictUbiquitousItemAtURL:]
and
[NSFileManager startDownloadingUbiquitousItemAtURL:]
if still can not open it, retry again after it downloaded.
Related
my app has around 200 UITableView rows, when i use simulator on xcode to filter data through UISearchBar, it filters and shows result instantly however, when i run my app in my iphone (iphone4, iOS 5.1.1), it hangs for couple of seconds before showing any search result. I'm using this code to filter data...
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
[self.filteredData removeAllObjects];
if ([searchText length] > 0) {
self.isSearching = YES;
for (AClass *filteredData in self.allData) {
NSRange titleResultRange = [filteredData.name rangeOfString:self.searchBar.text options:NSCaseInsensitiveSearch];
if (titleResultRange.location != NSNotFound) {
[self.filteredData addObject:filteredData];
}
}
}
else self.isSearching = NO;
[self.tableView reloadData];}
I believe my code is okay since it's working perfectly fine on the simulator, is there anything i need to do to make it work faster on iphone?
btw, my iPhone is working perfectly fine, i use other apps, they work fine for me..
The reason your device is taking longer than the simulator is due to the amount of memory available. As a general rule, don't use the performance of your app in the simulator to judge your app's performance.
If you are filtering a very large data set in the way you describe, I would suggest using dispatch queues to perform your search instead of doing it all in the main queue. You can read about them here: http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
In case you don't want to read the entire documentation, here's an example of what this would look like with your code.
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
[self.filteredData removeAllObjects];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([searchText length] > 0) {
self.isSearching = YES;
for (AClass *filteredData in self.allData) {
NSRange titleResultRange = [filteredData.name rangeOfString:self.searchBar.text options:NSCaseInsensitiveSearch];
if (titleResultRange.location != NSNotFound) {
[self.filteredData addObject:filteredData];
}
}
}
else self.isSearching = NO;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
}
Please note that the example I am giving you is not thread safe... you will need to make sure that only one search is being performed at any given time or this code will crash, since the same array will be referenced across multiple queues. If you need more assistance, leave a comment and I'll try to get to it.
Background info
I've almost completed my app. Everything was working perfectly. Then the client asked for logging in the app (i.e. various points that had to record what was done, what responses were, etc...).
The app allows the user to create "messages" which are saved into core-data. The messages are then uploaded to the server individually. The message are created on the main thread and uploaded in an NSOperation subclass on a background thread.
It is the same template for the NSOperation subclass that I have used before and works. I'm doing all the best practise stuff for multi-threaded core-data.
All this side of the app works fine.
I added the logging part of the app. I've created a singleton called MyLogManager and a CoreData entity called LogEntry. The entity is very simple, it only has a date and text.
Code
The function inside the MyLogManager is...
- (void)newLogWithText:(NSString*)text
{
NSLog(#"Logging: %#", text);
NSManagedObjectContext *context = [self context];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"LogEntry" inManagedObjectContext:context];
LogEntry *logEntry = [[LogEntry alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
logEntry.text = text;
logEntry.date = [NSDate date];
[self saveContext:context];
}
which in turn runs...
- (NSManagedObjectContext*)context
{
AppDelegate *appDelegate = (ThapAppDelegate*)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[managedObjectContext setPersistentStoreCoordinator:appDelegate.persistentStoreCoordinator];
return managedObjectContext;
}
and
- (void)saveContext:(NSManagedObjectContext*)context
{
MyAppDelegate *appDelegate = (MyAppDelegate*)[[UIApplication sharedApplication] delegate];
[[NSNotificationCenter defaultCenter] addObserver:appDelegate
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:context];
NSError *error = nil;
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unhandled error %#, %#", error, [error userInfo]);
abort();
}
[[NSNotificationCenter defaultCenter] removeObserver:appDelegate name:NSManagedObjectContextDidSaveNotification object:context];
}
The NSOperation main thread (well parts of it)...
- (void)main
{
//create context and retrieve NSManagedObject using the NSManagedObjectID passed in as a parameter to operation
self.message.lastSendAttempt = [NSDate date];
[self startUpload];
[self completeOperation]; //This doesn't get run because the startUpload method never returns
}
- (void)startUpload
{
[[MyLogManager sharedInstance] logSendingMessageWithURLParameters:[self.event URLParameters]]; //this is a convenience method. It just appends some extra info on the string and runs newLogWithText.
//Do some uploading stuff here...
//The operation stops before actually doing the upload when logging to CoreData.
}
The problem
My NSOperation subclass that uploads the messages (on a background thread) calls this newLogWithText function but it also updates the message it is uploading. The NSOperation uses the same methods to get and save the core-data context. (i.e. it updates the last sent date and also updates if the send was successful).
This is the first time I've tried to deal with simultaneous writes and saves to core-data.
I don't get any errors and the app carries on "working". But the operation never completes. I've tried to debug it with breakpoints but when I use breakpoints it works. Without breakpoints the operation never finishes and the upload never happens. And then it just sits there blocking the queue it is on and no other messages can be sent.
In my appDelegate (I know this isn't the ideal place for it but it's the default for a new project and I haven't changed it) the mergeChanges method is just...
- (void)mergeChanges:(NSNotification *)notification
{
[self.managedObjectContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:NO];
}
I've tried throwing the 'newLogWithText' function off to another thread and even to the main thread with no luck.
I'm just about to try it now but change the "waitUntilDone" of the merge to YES. (Just noticed this). (This didn't work).
I'm 90% certain this is down to simultaneous writes to different contexts and the conflict resolution as it is the first time I've dealt with this. If I comment out the newLogWithText function then everything works as it should.
The only alternative at the moment is to scrap the LogEntry from core data and save the logs into an array inside NSUserDefaults but that doesn't feel right. Is there another way?
EDIT
I've changed it now so it users NSUserDefaults and it works without a problem. It just feels like a hacky solution.
I'm getting sporadic EXC_BAD_ACCESS crashes which I'm thinking has to do with multi-threading issues. (I tried profiling with Zombies, but the app doesn't crash when profiling). So I'm wondering if there is any sort of mechanism, for debugging purposes, to determine if an object is being simultaneously accessed by more than one thread? Maybe somehow print a log statement if that is the case?
A simple and dirty method of telling if you are the only one executing on a thread would rely on unguarded static variables:
-(void)concurrentMethod {
static NSThread *runningThread = nil;
NSThread *myThread = [NSThread currentThread];
if (runningThread != nil) {
NSLog(#"Thread %#: running concurrently with %#", runningThread, myThread);
}
runningThread = myThread;
... // Do the useful stuff here
if (runningThread != myThread) {
NSLog(#"Thread %#: pre-empted by %#", myThread, runningThread);
}
runningThread = nil;
}
I have an iPhone app that is using sqlite 3.6 (not with FMDB) to store and load data. I load the database when the app loads and uses the same database connection through the whole app.
In a background thread the app downloads some data from a webserver and writes to the database. At the same time the main thread also might need to write to the same database. This sometimes leads to EXC_BAD_ACCESS as both threads are trying to access the database.
What is the best and easiest way to be able to use the database from different threads?
This is an example that shows the problem:
sqlite3 *database;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"database.db"];
if (sqlite3_open([path UTF8String], &database) != SQLITE_OK) {
sqlite3_close(database);
return YES;
}
[NSThread detachNewThreadSelector:#selector(test) toTarget:self withObject:nil];
[self test];
return YES;
}
-(void)test {
for (int i = 0; i < 2000; i++) {
NSLog(#"%i",i);
sqlite3_exec([self getDb],"UPDATE mytable SET test=''", 0, 0, 0);
}
}
EDIT:
After willcodejavaforfood's answer below I've tried to change my code to use a separate database object (connection) for each separate thread and also added sqlite3_busy_timeout() so that sqlite will retry to write if the database is busy. Now I don't get EXC_BAD_ACCESS anymore but I've noticed that not all data get inserted. So this is not a stable solution either. It seems to be really hard to get sqlite working with threading..
My new solution with separate connections:
-(void)test {
sqlite3 *db = [self getNewDb];
for (int i = 0; i < 2000; i++) {
NSLog(#"%i",i);
sqlite3_exec(db,"UPDATE mytable SET test=''", 0, 0, 0);
}
}
- (sqlite3 *)getNewDb {
sqlite3 *newDb = nil;
if (sqlite3_open([[self getDbPath] UTF8String], &newDb) == SQLITE_OK) {
sqlite3_busy_timeout(newDb, 1000);
} else {
sqlite3_close(newDb);
}
return newDb;
}
I solved this problem by using one thread and an NSOperationQueue to insert the Data. I would give it some thought. I've never been able to get a stable System with mutliple threads, and most writes aren't that important that queuing really helps.
As per request, some more Infos:
I have a subclass of NSOperation that I instantiate with the model object I want to store.
These operations are than submitted to an extension of NSOperationsQueue that runs in a seperate thread. This custom Queue just adds a pointer to the database instance. When the operation is executed, it uses the [NSOperationsQueue currentQueue] property to access the queue and than the database. On purpose, i used non-concurrent operations (maxOperations was set to 1)
Hence, only one query (or update) is executed at a time consecutivly, completely in the background.
Obviously you need some kind of callback after you're finished.
It is possibly not the fast, but the most stable and cleanest solution i could find.
Docs:
http://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
http://icodeblog.com/2010/03/04/iphone-coding-turbo-charging-your-apps-with-nsoperation/
As you've noticed only one thread can access an sqlite database at a time. Options to prevent simultaneous access:
Create a new database connection in each thread and rely on file locking (costly).
Turn on sqlite3_config(SQLITE_CONFIG_SERIALIZED).
Use NSLock's.
Use GCD (Grand Central Dispatch) queue's.
The first three options may cause busy waiting (one thread waiting on another to release the lock) which is wasteful.
I use option 4 because it simplifies the task of creating new queries to run in the background and has no busy waiting. It also makes sure all queries execute in the order they were added (which my code tends to assume).
dispatch_queue_t _queue = dispatch_queue_create("com.mycompany.myqueue", DISPATCH_QUEUE_SERIAL);
// Run a query in the background.
dispatch_async(_queue, ^{
...some query
// Perhaps call a completion block on the main thread when done?
dispatch_async(dispatch_get_main_queue(), ^{
//completion(results, error);
});
});
// Run a query and wait for the result.
// This will block until all previous queries have finished.
// Note that you shouldn't do this in production code but it may
// be useful to retrofit old (blocking) code.
__block NSArray *results;
dispatch_sync(_queue, ^{
results = ...
});
...use the results
dispatch_release(_queue);
In a perfect world sqlite would let you perform simultaneous reads but only one write at a time (eg. like using dispatch_barrier_async() for writes and dispatch_async() for reads).
This is all explained in the Core Data Programming Guide in the section for Concurrency.
The pattern recommended for concurrent
programming with Core Data is thread
confinement.
You should give each thread its own
entirely private managed object
context and keep their associated
object graphs separated on a
per-thread basis.
There are two possible ways to adopt
the pattern:
Create a separate managed object
context for each thread and share a
single persistent store coordinator.
This is the typically-recommended
approach.
Create a separate managed object
context and persistent store
coordinator for each thread. This
approach provides for greater
concurrency at the expense of greater
complexity (particularly if you need
to communicate changes between
different contexts) and increased
memory usage.
I've tried these two solutions and they worked perfectly. You can either use critical sections or NSOperationQueue and I prefer the first one, here is the code for both of them:
define some class "DatabaseController" and add this code to its implementation:
static NSString * DatabaseLock = nil;
+ (void)initialize {
[super initialize];
DatabaseLock = [[NSString alloc] initWithString:#"Database-Lock"];
}
+ (NSString *)databaseLock {
return DatabaseLock;
}
- (void)writeToDatabase1 {
#synchronized ([DatabaseController databaseLock]) {
// Code that writes to an sqlite3 database goes here...
}
}
- (void)writeToDatabase2 {
#synchronized ([DatabaseController databaseLock]) {
// Code that writes to an sqlite3 database goes here...
}
}
OR to use the NSOperationQueue you can use:
static NSOperationQueue * DatabaseQueue = nil;
+ (void)initialize {
[super initialize];
DatabaseQueue = [[NSOperationQueue alloc] init];
[DatabaseQueue setMaxConcurrentOperationCount:1];
}
+ (NSOperationQueue *)databaseQueue {
return DatabaseQueue;
}
- (void)writeToDatabase {
NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(FUNCTION_THAT_WRITES_TO_DATABASE) object:nil];
[operation setQueuePriority:NSOperationQueuePriorityHigh];
[[DatabaseController databaseQueue] addOperations:[NSArray arrayWithObject:operation] waitUntilFinished:YES];
[operation release];
}
these two solutions block the current thread until the writing to database is finished which you may consider in most of the cases.
I need to update the tableview as soon as the content is pushed in core data database.
for this
AppDelegate.m contains following code
NSManagedObjectContext *moc = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"FeedItem" inManagedObjectContext:moc]];
//for loop
// push data in code data & then save context
[moc save:&error];
ZAssert(error == nil, #"Error saving context: %#", [error localizedDescription]);
//for loop ends
This code triggers following code from RootviewController.m
- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller
{
[[self tableView] beginUpdates];
}
But this updates the tableview only at the end of the for loop ,the table does not get updated after immediate push in db.
I tried following code but that didn't work
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// In the simplest, most efficient, case, reload the table view.
[self.tableView reloadData];
}
I have been stuck with this problem for several days.Please help.Thanks in advance for solution.
It sounds like you're running the first bit of code in the main thread. As this thread also controls the UI updating, nothing will happen until your loop is finished. There are three ways to solve this.
1) Update coredata from a second thread and pass a message into the main thread to tell it to update the UI. This is quite tricky to do - have a look at the core data multithreading docs for some scary warnings!
2) Don't do your updates in a for loop, do them as part of a timer instead. this will give the ui time to update between each insert i.e.
...
self.stuffToUpdate = [NSMutableArray arrayWithObjects:...];
[NSTimer scheduledTimerWithInterval:0.1 target:self selector:#selector(doStuff:) userInfo:nil repeats:YES];
...
- (void) doStuff:(NSTimer *)timer {
// If there's noting to update, bail
if (0 == stuffToUpdate.count) {
[timer invalidate];
return;
}
// Get the next object to update
id object = [stuffToUpdate objectAtIndex:stuffToUpdate.count-1];
[stuffToUpdate removeLastObject];
// Do stuff to object
[object doStuff];
// Save
NSError &error = nil;
[moc save:&error];
// Tell the table to update
[self.tableView reloadData];
}
However, a continually updating table like that might give wierd user interface issues - you'd have to try it in user tests to see if it works Ok. Also, this will slow the total time taken to update coredata but that might not be a problem if you're updating the UI all the time.
3) Change your app so that is displays a 'please wait' message with an activity indicator while coredata is populated! This is much easier to do but if your updates take a long time, this might put users off.