UITableView performance issue - iphone

I have a UITableView which gets data from a server and updates in every 1 second(using performSelectorOnMainThread). Since this blocks main thread sometimes its not easy to scroll the table and its painfull for the user. Also i cant reduce my refresh interval also.
What are the possible solutions for this problem?

I would only refresh the visible cells as the data changes, and the others as they appear so it will be less consuming than updating the hole UITablaView
you can get the visible cells using ( from UITableView):
- (NSArray *)visibleCells
and you can update the remaining cells as they appear using UITableViewDelegate Protocol
– tableView:willDisplayCell:forRowAtIndexPath:
and i think this should make it a bit faster.

Hold your data in a mutable array or similar structure then asyncronously update that array with an NSURLConnection. You can call reloadData on the tableview to redraw the table when the NSURLConnection is done.
You would probably just call the NSUrlConnection from an NSTimer at whatever interval you prefer.

instead of calling performSelectorOnMainThread function of NSThread call
detachNewThreadSelector function. In this way your thread will not block the main thread
[NSThread detachNewThreadSelector:#selector(aMethod:) toTarget:[MyObject class] withObject:nil];
in toTarget: method you can write self instead of [MyObject class]
also in Implementation selector write #synchronize(self) for eg.
-(void)aMethod
{
#synchronize(self) {
//write your whole code here
}
}
I have done the same thing in my application its work perfectly

Use GCD with queues (serial or global queues). This is the apple recommended way now.
dispatch_async(dispatch_get_main_queue(), ^{
//do UI updates
});

Related

Multithreaded TableViewController

I recently got a crash in UITableViewController's tableView:cellForRowAtIndexPath:
-[__NSArrayM objectAtIndex:]: index 10 beyond bounds for empty array
for the code:
Message* res = [messages objectAtIndex:indexPath.row]
Where messages is an NSMutableArray.
The crash happens because in the tableView:cellForRowAtIndexPath: method I try to get a message from an array of messages, but that array has probably been changed by an other thread. The refreshing of my messages array is done on another thread, which is why I think this happened.
What would be the best way to handle this situation considering that I need to keep using multiple threads? Is there a way to cancel the drawing of a cell and force the redraw of the entire list of messages? I've tried to do a simple if check:
if([messages size] < indexPath.row) {
return nil;
}
That results with the method throwing an assertion exception and crashing the app.
The goal is to gracefully reload the whole view when this happens.
When dealing with multithreading , one must be really careful to switch between background thread and main thread and also at the time of UI Updation . As you are trying to update your UI before thread is completed which result in crash. Let me give you a brief example , firstly we will call a method in background thread
[self performSelectorInBackgroungThread:#selector(fetchDataFromServer)
withObject:nil
onCompletion:nil];
Looking on to the other methods
-(void)fetchDataFromServer{
//Some something
[self performSelectorOnMainthread:#selector(backgroundThreadCompleted)]
withObject:nil
waitUntilDone:nil];
}
Soon after the work in the background thread has been completed I call a method in the Main Thread and perform all the work related to UI Updation in that method only
-(void)backgroundThreadCompleted
{
//UI Updation
}
If you follow the same process your application is not going to crash. In your case you are trying to access an empty array(an object at any index that doesn't exist).As a precautionary step iplement a conditional check that if the [array count]!=0 then only try to access the array else return; . When the values for array changes reload your tableview using [UITableView reloadData].
When ever you made changes in your message array you have to reload the tableView.
by using this you message will always be updated on your tableVlew and your crashing problem will resolved

Is it possible to make a method operate sequentially?

I have only included the code relevant to this label within the program.
In my viewDidLoad method I have
[startLabel setHidden:NO];
startLabel.text = #"Touch to Begin";
In the touchesBegan method I then have
startLabel.text = #"Loading . .";
[self fillArrays];
Then in the fill Arrays method I fill the arrays and then hide the label -
self.myArray = [NSArray arrayWithObjects:
[UIImage imageNamed:#"Frame 1.png"], . .etc etc etc . . . nil]];
[startLabel setHidden:YES];
However, the text is not updated before the Array is loaded. Resulting in the "Loading . . " text never appearing. As it seems to be implemented after the Array is filled.
At the same time the setHidden bool is set to YES, thus one never sees the label.
I wish for the startLabel to update before the method begins to fill the Array as this takes some time. i.e. for the method to Operate sequentially.
Is this possible?
Thank You
You don't need multithreading, that's overcomplicating things. The problem is that until your code returns, UIKit isn't going to update the user interface, so you're scheduling the user interface update, loading the arrays, then returning control to UIKit, which then performs the user interface update. What you need to do is schedule the user interface update, return control to UIKit and then load the arrays in the next iteration of the run loop. To do that, you can use performSelector:withObject:afterDelay: with a zero delay, which executes the method call in the next run loop iteration. This should do the trick:
startLabel.text = #"Loading . .";
[self performSelector:#selector(fillArrays) withObject:nil afterDelay:0];
You need to load the arrays on a different thread to that of your GUI updates.
See this article: http://evilrockhopper.com/2010/01/iphone-development-keeping-the-ui-responsive-and-a-background-thread-pattern/
The function you want is performSelectorInBackground
That way your UI updates and your Background work can be done at the same time.
You can run another function which updates the label on the main thread as well in a similar way:
performSelectorOnMainThread
That way you load arrays in the background, and you update your UI on the main thread (which is good because I Think the UIKit is not thread safe.

Freezes (not crashes) with GCD, blocks and Core Data

I have recently rewritten my Core Data driven database controller to use Grand Central Dispatch to manage fetching and importing in the background. Controller can operate on 2 NSManagedContext's:
NSManagedObjectContext *mainMoc instance variable for main thread. this contexts is used only by quick access for UI by main thread or by dipatch_get_main_queue() global queue.
NSManagedObjectContext *bgMoc for background tasks (importing and fetching data for NSFetchedresultsController for tables). This background tasks are fired ONLY by user defined queue: dispatch_queue_t bgQueue (instance variable in database controller object).
Fetching data for tables is done in background to not block user UI when bigger or more complicated predicates are performed.
Example fetching code for NSFetchedResultsController in my table view controllers:
-(void)fetchData{
dispatch_async([CDdb db].bgQueue, ^{
NSError *error = nil;
[[self.fetchedResultsController fetchRequest] setPredicate:self.predicate];
if (self.fetchedResultsController && ![self.fetchedResultsController performFetch:&error]) {
NSSLog(#"Unresolved error in fetchData %#", error);
}
if (!initial_fetch_attampted)initial_fetch_attampted = YES;
fetching = NO;
dispatch_async(dispatch_get_main_queue(), ^{
[self.table reloadData];
[self.table scrollRectToVisible:CGRectMake(0, 0, 100, 20) animated:YES];
});
});
} // end of fetchData function
bgMoc merges with mainMoc on save using NSManagedObjectContextDidSaveNotification:
- (void)bgMocDidSave:(NSNotification *)saveNotification {
// CDdb - bgMoc didsave - merging changes with main mainMoc
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainMoc mergeChangesFromContextDidSaveNotification:saveNotification];
// Extra notification for some other, potentially interested clients
[[NSNotificationCenter defaultCenter] postNotificationName:DATABASE_SAVED_WITH_CHANGES object:saveNotification];
});
}
- (void)mainMocDidSave:(NSNotification *)saveNotification {
// CDdb - main mainMoc didSave - merging changes with bgMoc
dispatch_async(self.bgQueue, ^{
[self.bgMoc mergeChangesFromContextDidSaveNotification:saveNotification];
});
}
NSfetchedResultsController delegate has only one method implemented (for simplicity):
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
dispatch_async(dispatch_get_main_queue(), ^{
[self fetchData];
});
}
This way I am trying to follow Apple recommendation for Core Data: 1 NSManagedObjectContext per thread. I know this pattern is not completely clean for at last 2 reasons:
bgQueue not necessarily fires the same thread after suspension but since it is serial, it should not matter much (there is never 2 threads trying access bgMoc NSManagedObjectContext dedicated to it).
Sometimes table view data source methods will ask NSFetchedResultsController for info from bgMoc (since fetch is done on bgQueue) like sections count, fetched objects in section count, etc....
Event with this flaws this approach works pretty well of the 95% of application running time until ...
AND HERE GOES MY QUESTION:
Sometimes, very randomly application freezes but not crashes. It does not response on any touch and the only way to get it back to live is to restart it completely (switching back to and from background does not help).
No exception is thrown and nothing is printed to the console (I have Breakpoints set for all exception in Xcode).
I have tried to debug it using Instruments (time profiles especially) to see if there is something hard going on on main thread but nothing is showing up.
I am aware that GCD and Core Data are the main suspects here, but I have no idea how to track / debug this.
Let me point out, that this also happens when I dispatch all the tasks to the queues asynchronously only (using dispatch_async everywhere). This makes me think it is not just standard deadlock.
Is there any possibility or hints of how could I get more info what is going on? Some extra debug flags, Instruments magical tricks or build setting etc...
Any suggestions on what could be the cause are very much appreciated as well as (or) pointers to how to implement background fetching for NSFetchedResultsController and background importing in better way.
My first and very bad mistake was to fetch data for NSFetchedResultsController in the background queue.
It turned out after testing, I was way too sensitive about fetching times. I unnecessary did put fetchData execution to back thread making core data related code too complex when the longest fetch time I could generate took literally split of a second. This introduced way too much complexity and uncertainty for very small performance gain (if any).
I resigned form that by moving fetchData execution and all NSFetchedResultsControllerDelegate method to the main thread (simplified the code by removing GCD code).
When this was done I no longer needed mainMocDidSave: and unregistered from listening to the NSManagedObjectContextDidSaveNotification for main thread context.
I could also removed and unregistered DATABASE_SAVED_WITH_CHANGES notification posting.
This greatly simplified 'merging' mechanism as from this time on only background thread context merges its changes with main thread context (when saved). Let's call it one directional change notifications.
NSFetchedResultsControllerDelegate methods will be fired automatically as they pickup main thread context changes after merge.
Another important thing is to change dispatch_async to dispatch_sync in:
- (void)bgMocDidSave:(NSNotification *)saveNotification {
// CDdb - bgMoc didsave - merging changes with main mainMoc
// Previously was: dispatch_async
// dispatch_sync in this place may prevent from overlapping merging in some cases (many blocks in background queue)
dispatch_sync(dispatch_get_main_queue(), ^{
[self.mainMoc mergeChangesFromContextDidSaveNotification:saveNotification];
// !!! Extra notification NO needed anymore
});
}
Rule of thumb: SIMPLIFY and MINIMIZE amount of threads and NSManagedContexts.
I experienced that having 2 contexts is enough even for very big apps:
importContext operating in dedicated to GCD queue (MUST be serial queue). Just remember to save it at the end of the queue block's code.
mainConstext to operate on main UI thread (I call it READ context ;-) to be use when pulling data for the UI (presentation).
The DATABASE_SAVED_WITH_CHANGES notification looks a bit suspicious: Let's say bgMoc saves. Then bgMocDidSave: fires and merges the changes with the mainMoc which is fine. Then you fire a notification which in the end (I assume mainMocDidSave: fires when DATABASE_SAVED_WITH_CHANGES is raised) merges the changes back in bgMoc (which is where is originated from!). This does not sound like the right approach to me.
Also you might want to check in bgMocDidSave: that the notification originates from the bgMoc. If the mainMoc saves then changes are that bgMocDidSave: also fires.

Threading, priorities, and abandonment

I have recently become thread curious on iOS. Please point me in the direction you would take, to achieve (if possible) the following on modern iOS devices... thank you!
The user is typing in text, say a word every few seconds.
From time to time I want to launch DifficultProcess to do some semantic processing. In short, I guess I need to be able to do four things:
launch DifficultProcess from main
if DifficultProcess completes, get a message back from it to the same main
abandon, get rid of, DifficultProcess if I want to, from main
and finally the priority question: DifficultProcess must have much lower priority than main or user input, I want DifficultProcess to have really really looow priority; is that even possible?
What, essentially, are the calls one uses for A, B, C in modern (2011) (late January) iOS? I don't care about Dad's methods! And is "D" even possible in any way?
I guess those are the four ideas!
So in particular I want to send a message to, in other words call a routine in, the running background process (in that way, one could kill off the running background process if desired, or perhaps change it's mode of operation etc).
(For anyone born before 1997, you will recognise that as a typical "speculative processing" paradigm.)
Thanks for pointers for anyone who can be bothered on this!
I would recommend using NSOperation and NSOperationQueue to manage background activity that you need to be able to cancel arbitrarily.
NSOperation's -cancel and NSOperationQueue's -cancelAllOperations are the methods to look at.
To get messages back from the background to the main thread, the dispatch_async-to-main-thread-queue technique is fine. You can combine this with a delegate protocol for your NSOperation to codify the messages you want to send back.
E.g.
#protocol MyOperationDelegate
- (void) operationStarted:(MyOperation *)operation;
- (void) makingProgressOnItem:(id)anItem otherInterestingItem:(NSDictionary *)otherItem remainingCount:(NSUInteger)count;
- (void) operationWillFinish:(MyOperation *)operation;
#end
#interface MyOperation
id <MyOperationDelegate> delegate;
#end
#implementation MyOperation
...
- (void) cancel
{
[super cancel];
// Tell the delegate we're about to finish (due to cancellation).
dispatch_sync (dispatch_get_main_queue(), ^{
[self.delegate operationWillFinish:self];
});
}
- (void) main
{
// Check for cancellation
if (self.isCancelled) return;
// Starting
dispatch_sync (dispatch_get_main_queue(), ^{
[self.delegate operationStarted:self];
});
if (self.isCancelled) return; // Another cancel check
// Send async progress messages periodically while doing some work
while (workNotDone)
{
// Do some work ...
dispatch_async (dispatch_get_main_queue(), ^{
[self.delegate makingProgressOnItem:foo otherInterestingItem:bar remainingCount:baz];
});
if (self.isCancelled) return;
}
// About to finish
if (!self.isCancelled) {
dispatch_sync (dispatch_get_main_queue(), ^{
[self.delegate operationWillFinish:self];
});
}
}
#end
KVO is no good for interthread communication; the observation is received on the thread that originates the key value change. So, if your background thread changes a value, your background thread is going to receive the KVO about it. Probably not what you want.
Grandpa's -performSelectorOnMainThread:withObject:waitUntilDone: continues to be a fine way to get messages back to the main thread. The limitation is that your messages can only access one object-based argument. The dispatch_async to the main thread doesn't have this limitation.
If you want to fire off an asynchronous (or synchronous) NSNotification's from a background thread to the main thread, you need to use -performSelectorOnMainThread.
NSNotification *note = [NSNotification notificationWithName:FinishedABunchOfWorkNotification object:self userInfo:nil];
[[NSNotificationCenter defaultCenter] performSelectorOnMainThread:#selector(postNotification:) withObject:note waitUntilDone:YES];
I would suggest using dispatch_async to the global low priority queue (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)).
Cancellation is trickier though. There's no good general mechanism for canceling background work that I'm aware of aside from "chunking" it and checking a flag each chunk
To get messages back just dispatch_async back to the main queue. If you squint just right you can think of dispatch_async as "send message" in an actor model.
(edit) if you need serialization of stuff in the background, make a private queue and set its target to the global low priority one, iirc.
At the risk of quoting Dad's method (it has been around since iPhone version 2) I use
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
It's easy and foolproof as long as you remember that you must create a new autorelease pool in the method you pass as selector, and drain it at the end of the method. Apart from that do whatever you like - EXCEPT touch UIKit. It isn't thread-safe so any UI changes must be done through
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
or KVO triggers. Key Value Observing would be a good way for your background thread to communicate to your main thread that the work is done.
- (void)myBackgroundThreadMethod {
NSAutoreleasePool *threadPool = [[NSAutoreleasePool alloc] init];
// my time-consuming processing here
[threadPool drain];
}
For more precise control of threads you need to look at NSThread. Threading Programming Guide lays it all out in detail - if you create a thread through NSThread then you have control over when the thread is started. The document does recommend leaving the thread alone and just letting it terminate - but shows how you can terminate it. One way is - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait
NSThread docs also say "leave priority alone". You can set thread priority with
+ (BOOL)setThreadPriority:(double)priority
but I've never known it to be necessary, the scheduler is smart enough to maintain UI responsiveness.

Objective-C : Start an object on a background thread -- interact as usual?

I would like to have an object be callable from the main thread
MyObj* backgroundObject = [[MyObj alloc] initInBackground];
BOOL result = [backgroundObject computeResult];
But have all the methods of backgroundObject compute in another thread.
And also have backgroundObj be able to send messages to it's delegate. How can I do such a thing? Is it possible?
As others have pointed out, an NSObject doesn't exist on any one thread, a thread only comes into play when you start executing its methods.
My suggestion would be to not use manual threads for every time that a method is called on the object, but instead use NSOperations and an NSOperationQueue. Have an NSOperationQueue as an instance variable of the object, and have calls to the various methods on the object create NSOperations which are inserted into the queue. The NSOperationQueue will process these operations on a background thread, avoiding all of the manual thread management you would need to have for multiple accesses to methods.
If you make this NSOperationQueue have a maximum concurrency count of 1, you can also avoid locking shared resources within the object between the various operations that will be performed on a background thread (of course you'll still need to lock instance variables that can be accessed from the outside world).
For callbacks to delegates or other objects, I'd recommend using -performSelectorOnMainThread:withObject:waitUntilDone so that you don't have to think about making those delegate methods threadsafe.
See the Concurrency Programming Guide for more.
Sure, you can use NSThread for that, and have backgroundObject use performSelectorOnMainThread: to contact the delegate.
NSThread documentation
performSelectorOnMainThread:withObject:waitUntilDone:
Objects do not exist in threads AFAIK. The function you send to an object will always be performed on the thread you sent it from (unless you use NSThread or performSelectorOnMainThread or something along those lines).
What you need is a NSOperation and a block/delegate parameter to notify the caller of completion.
Look at the documentation of NSOperation and NSOperationQueues
You should use GCD:
/*
* I didn't initalised your object in background, because
* because in the most cases you need your object to stick around
* and only perfom the time consimung calculations in background
*/
MyObj* backgroundObject = [[MyObj alloc] initInBackground];
...
- (void)startBackgroundTask {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//in a perfectly async non blocking block :-)
BOOL result = [backgroundObject computeResult];
dispatch_async(dispatch_get_main_queue(), ^{
//post your result, or do something else with your result
[[NSNotificationCenter defaultCenter] postNotificationName:"backgroundComputeResultDone" object:[NSNumber numberWithBool:result]];
if (result) {
//do some stuff
}
});
});
}
Maybe you can take a look at these two videos from apple here:
WWDC 2011 - Session 210 - Mastering Grand Central Dispatch
WWDC 2011 - Session 308 - Blocks and Grand Central Dispatch in Practice