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.
Related
Im developing an IOS 5 app which takes a feed from a url and displays the posts in a tableview. I have a View controller that loads the table cells with the posts in the feed. This all works perfectly.
However, i wanted to use the SVProgressHUD to show whilst the feed is being loaded in a separate thread.
So in my -(void)viewDidLoad method I have the following:
- (void)viewDidLoad
{
[super viewDidLoad];
[SVProgressHUD showInView:self.view status:#"loading.." networkIndicator:YES];
dispatch_async(kBgQueue, ^{NSData* data = [NSData dataWithContentsOfURL: latestFeedURL];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];});
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(foregroundRefresh:) name:UIApplicationWillEnterForegroundNotification object:nil];
self.pull = [[PullToRefreshView alloc] initWithScrollView:(UIScrollView *) self.feedTableView];
[self.pull setDelegate:self];
[self.feedTableView addSubview:self.pull];
self.title = #"Latest";
}
- (void)fetchedData:(NSData *)responseData {
//parse out the json data
NSError* error;
NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
NSMutableArray* latestFeed = [json objectForKey:#"posts"]; //2
self.feedUpLoads = latestFeed;
NSLog(#"objects: %#", latestFeed); //3
[self.feedTableView reloadData];
[SVProgressHUD dismiss];
}
This all works fine, im getting the data which is loaded in a background thread and my table is displaying the posts with all the detail required. The problem I have is that the SVProgressHUD is not showing at all. Even if I put the [SVProgressHUD showInView line in the fetchData method, it's still not showing. (by the way i know the SVProgressHUD code works because I can actually make it show forexample in the viewWillAppear method.
Im guessing that it's not working because at the point when I'm calling it the view does not yet fully exist? But if that's the case where should I call it so that it shows whilst the feed is being called and where should I remove it?
Any help appreciated! thanks in advance!!
For anyone else having a similar problem, this can also happen because you have a long loop or a piece of code that takes a long time to execute. If this happens, your progress bar wont be shown until after the loop, which kind of defeats the purpose.
To solve this issue you need to you this:
(void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
Basically your code would look something like this:
- (IBAction)submitPost:(id)sender {
//now we show the loading bar and submit the comment
[SVProgressHUD showWithStatus:#"Submitting post" maskType:SVProgressHUDMaskTypeGradient];
SEL aSelector = #selector(submitDataOfPost);
[self performSelectorInBackground:aSelector withObject:sender];
}
This will basically load the progress bar, and in a background thread, the method you want to execute will be called. This makes sure that the UI is updated (shows the progress hud) at the same time that your code is executed.
I am new iPhone Developer. I am upgrading existing iPhone App. I am using Core Data Model to save data.
In App, there is a 15 square boxes to add images. I am calling a Detached Thread to make a separate process. In this process, I am saving image into two size. I have added observer with image object and remove observer at last.
I am using this method to add Observer:-
[projectImage addObserver:self forKeyPath:#"fileName" options:NSKeyValueObservingOptionNew context:nil];
And this method for making separate Thread:-
[NSThread detachNewThreadSelector:#selector(addImage:) toTarget:self withObject:[dic retain]];
here AddImage is Method like:-
- (void) addImage:(NSDictionary *) dic {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = [dic objectForKey:#"image"];
projectImage = nil;
projectImage = [dic objectForKey:#"managedObject"];
[projectImage importImageData:image];
[projectImage removeObserver:self forKeyPath:#"fileName"];
[pool drain];
}
And dic is Dictionary
My problem is :
It is Crashing after taking 4-5 images by Camera or Phone library.
If any can guide me to get rid to this problem.
Thanks in Advance
You are leaking memory, and probably because of this your app will crash. I think the app runs out of memory and gets killed.
remove the [dic retain] from
[NSThread detachNewThreadSelector:#selector(addImage:) toTarget:self withObject:[dic retain]];
the object is retained by the method call. See the discussion of detachNewThreadSelector:toTarget:withObject:.
The objects aTarget and anArgument are retained during the execution of the detached thread, then released. The detached thread is exited (using the exit class method) as soon as aTarget has completed executing the aSelector method.
your call should be
[NSThread detachNewThreadSelector:#selector(addImage:) toTarget:self withObject:dic];
Why is it that everytime I call save on my NSManagedObjectContext:
-(NSManagedObjectContext*)managedObjectContext {
NSMutableDictionary* threadDictionary = [[NSThread currentThread] threadDictionary];
NSManagedObjectContext* backgroundThreadContext = [threadDictionary objectForKey:RKManagedObjectStoreThreadDictionaryContextKey];
if (!backgroundThreadContext) {
backgroundThreadContext = [self newManagedObjectContext];
[threadDictionary setObject:backgroundThreadContext forKey:RKManagedObjectStoreThreadDictionaryContextKey];
[backgroundThreadContext release];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundThreadContext];
}
return backgroundThreadContext;
}
- (NSError*)save {
NSManagedObjectContext* moc = [self managedObjectContext];
NSError *error = nil;
#try {
if (![moc save:&error]) { //breakpoint in here
//some code
}
the app seems to be waiting forever and never got back to resume it's execution? Here's what I mean in a video. Can this be possibly caused because something is wrong with the entity/relationship model?
Here's a screenshot of the leaks instruments, I don't see any leaks, but it seems that the app is allocating something that builds up:
Have you tried ditching your multi-threading code to see if it works? My guess would be that you're mixing up threads here and accessing/saving the MOC from different threads. Managing threads manually is a PITA, you should try switching to Grand Central Dispatch.
I would also give your main MOC its own accessor so you can make sure it isn't called from background threads, and have some: - (NSManagedObjectContext*)newBackgroundMOC; and - (void)saveBackgroundMOC:(NSManagedObjectContext*)context; methods to quickly create and save MOCs from background queues/threads:
dispatch_async(my_queue, ^{
NSManagedObjectContext *context = [self newBackgroundMOC]; // create context, setup didSave notification to merge with main MOC, etc
// modify context
[self saveBackgroundMOC:context]; // main MOC gets updated
});
Migrating to GCD is a bit of work, but in the long run you'll see it's much more pleasant to work with. It goes without saying that it's also the most modern and recommended by Apple way to deal with threads.
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.
I would like to see if I can make a "search as you type" implementation, against a web service, that is optimized enough for it to run on an iPhone.
The idea is that the user starts typing a word; "Foo", after each new letter I wait XXX ms. to see if they type another letter, if they don't, I call the web service using the word as a parameter.
The web service call and the subsequent parsing of the result I would like to move to a different thread.
I have written a simple SearchWebService class, it has only one public method:
- (void) searchFor:(NSString*) str;
This method tests if a search is already in progress (the user has had a XXX ms. delay in their typing) and subsequently stops that search and starts a new one. When a result is ready a delegate method is called:
- (NSArray*) resultsReady;
I can't figure out how to get this functionality 'threaded'.
If I keep spawning new threads each time a user has a XXX ms. delay in the typing I end up in a bad spot with many threads, especially because I don't need any other search, but the last one.
Instead of spawning threads continuously, I have tried keeping one thread running in the background all the time by:
- (void) keepRunning {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
SearchWebService *searchObj = [[SearchWebService alloc] init];
[[NSRunLoop currentRunLoop] run]; //keeps it alive
[searchObj release];
[pool release];
}
But I can't figure out how to access the "searchFor" method in the "searchObj" object, so the above code works and keeps running. I just can't message the searchObj or retrieve the resultReady objects?
Hope someone could point me in the right direction, threading is giving me grief:)
Thank you.
Ok, I spend the last 8 hours reading up on every example out there.
I came to realize that I would have to do some "Proof of Concept" code to see if there even would be a speed problem with building a new thread for "each" keystroke.
It turns out that using NSOperation and NSOperationQueue is more than adequate, both in terms of speed and especially in terms of simplicity and abstraction.
Is called after each keystroke:
- (void) searchFieldChanged:(UITextField*) textField {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSString *searchString = textField.text;
if ([searchString length] > 0) {
[self performSelector:#selector(doSearch:) withObject:textField.text afterDelay:0.8f];
}
}
This is mainly to stop the code form initiating a search for keystrokes that are less than 800 ms. apart.
(I would have that a lot lower if it where not for the small touch keyboard).
If it is allowed to time out, it is time to search.
- (void) doSearch:(NSString*) searchString {
[queue cancelAllOperations];
ISSearchOperation *searchOperation = [[ISSearchOperation alloc] initWithSearchTerm:searchString];
[queue addOperation:searchOperation];
[searchOperation release];
}
Cancel all operations that is currently in the queue. This is called every time a new search is
started, it makes sure that the search operation already in progress gets closed down in an orderly fashion, it also makes sure that only 1 thread is ever in a "not-cancelled" state.
The implementation for the ISSearchOperation is really simple:
#implementation ISSearchOperation
- (void) dealloc {
[searchTerm release];
[JSONresult release];
[parsedResult release];
[super dealloc];
}
- (id) initWithSearchTerm:(NSString*) searchString {
if (self = [super init]) {
[self setSearchTerm:searchString];
}
return self;
}
- (void) main {
if ([self isCancelled]) return;
[self setJSONresult:/*do webservice call synchronously*/];
if ([self isCancelled]) return;
[self setParsedResult:/*parse JSON result*/];
if ([self isCancelled]) return;
[self performSelectorOnMainThread:#selector(searchDataReady:) withObject:self.parsedResult waitUntilDone:YES];
}
#end
There are two major steps, the downloading of the data from the web service and the parsing.
After each I check to see if the search has been canceled by [NSOperationQueue cancelAllOperations] if it has, then we return and the object is nicely cleaned up in the dealloc method.
I will probably have to build in some sort of time out for both the web service and the parsing, to prevent the queue from choking on a KIA object.
But for now this is actually lightning fast, in my test I am searching an 16.000 entries dictionary and having Xcode NSLog it to the screen (slows things down nicely), each 800 ms. I issue a new search string via a timer and thereby canceling the old before it has finished its NSLog results to screen loop.
NSOperationQueue handles this with no glitches and never more that a few ms. of two threads being executed. The UI is completely unaffected by the above tasks running in the background.