CloudKit CoreAnimation error - cloudkit

We are beginning to work with CloudKit and have been working with Firebase for a while.
The issue we are having is a CoreAnimation warning when we are not using CoreAnimation in our code.
CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.
This occurs whenever we read/add/update/delete any record from CloudKit.
For example, here's our initial loading of Contacts into an NSTableView
-(void)loadContacts {
CKQuery *query = [[CKQuery alloc] initWithRecordType:#"Contact" predicate:[NSPredicate predicateWithFormat:#"TRUEPREDICATE"]];
self.arrayToDisplay = [NSMutableArray new];
[self.publicDatabase performQuery:query inZoneWithID:nil completionHandler:^(NSArray *results, NSError *error) {
if ( error ) {
NSLog(#"error loading array: %#", error.localizedDescription);
} else {
self.arrayToDisplay.array = results; //arrayToDisplay is an array of CKRecord objects
[self.myTableView reloadData];
}
}];
}
self.publicDatabase is an initialized CKDatabase.
Note that all the functions work, read/add/update/delete, but that message appears after each call.
We have set the CA_DEBUG to log the backtraces and it appears it's got something to do with redrawing the NSTableView before the records are fetched?

This seems to fix the issue as it puts the tableView reload back onto the main thread. Just replace the [self.tableView reloadData] in the block with the following.
[self.tableView
performSelectorOnMainThread:#selector(reloadData)
withObject:nil
waitUntilDone:NO
];

performSelectorOnMainThread: will work just fine as you posted in your answer. However sometimes you want to do more than just execute a single method. In that case I recommend wrapping multiple lines of code into a GCD dispatch statement. Here's how it looks:
__weak UITableView *weakTableView; // use a weak reference to prevent retain cycle
dispatch_queue_t q_main = dispatch_get_main_queue();
dispatch_async(q_main, ^() {
[weakTableView reloadData];
// do other stuff too if you want
});

Related

How to request JSON data in the viewDidAppear method or viewDidLoad?

If I call the [self requestData]; from viewDidLoad my table populates itself with data just fine. If I move [self requestData]; to the viewDidAppear method the table remains empty.
Also, I'm not entirely sure if [self.mainTableView reloadData]; is working. I'm trying to move the data request and handling to the viewDidAppear method because I saw that pattern in a code example and thought it might speed up my app launch somewhat. At the moment there's quite a lag from the app launch Default.png to the rootViewController.
thanks for any help with this.
-(void)viewDidAppear:(BOOL)animated
{
[self requestData];
}
-(void)requestData {
[HUD showUIBlockingIndicatorWithText:#"Fetching JSON"];
NSError *requestError = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL
URLWithString:kURL]];
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&requestError];
NSError *jsonParsingError = nil;
if (requestError)
{
NSLog(#"sync. request failed with error: %#", requestError);
}
else
{
// handle data
publicData = [NSJSONSerialization JSONObjectWithData:response
options:0
error:&jsonParsingError];
publicDataArray = [publicData objectForKey:#"data"];
}
/*
for(publicDataDict in publicDataArray) {
NSLog(#"data output is %#",[publicDataDict objectForKey:#"title"]);
}
*/
[self.mainTableView reloadData];
NSLog(#"reload table cat id %#", categoryString);
[HUD hideUIBlockingIndicator];
}
You're trying to optimise the wrong thing.
The reason you have a lag is that your url request is synchronous, so it is blocking the main thread while waiting for the data to be received. Using an asynchronous URL request will give you far better performance benefits that moving the loading call as you are trying to do.
viewDidLoad is the right place to make your JSON request because It is called once, and will keep data into memory.
ViewDidAppear is called each time view appear, it is not really made to retain object
The "lag" is the fact that your UI is not being responsive because the JSON post request is on the main thread.
to speed up launching try to load data asynchronously, like this:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self requestData];
}];
you shoud call it inside viewDidLoad
viewDidLoad is called after the views have been created, but before they're drawn.
viewDidAppear is called after the views have been drawn. It's too late to set data here that affects the display. You could possibly load the data in viewWillAppear, but be aware that it may be called multiple times (e.g. switching between apps).
That's why setting data that affects the display works in viewDidLoad, but not viewDidAppear. viewDidLoad is called only once in a view controller's life cycle.
However, consider the user experience if your user's Internet connection is slow or the server is slow to respond. Your application will seem to "freeze" for awhile.
Instead of a synchronous request, consider using an asynchronous request. You could display a "loading" message, spinner, or something until the request returns, then update the UI directly.
Apple strongly recommends always making asynchronous requests when run from the main UI thread.

Speed up search using dispatch_async?

I'm trying to speed up my app search , it get lags when there is a lot of data.
so i'm trying to split search Predicate on UI by using dispatch_async not dispatch_sync cause no different if I use it.
The problem is when i use dispatch_async, the app crash sometimes because [__NSArrayI objectAtIndex:]: index "17" beyond bounds.
I now this happened because lets say the first one still work and reload the tableView and continue search will change the array size depend on result so in this case "CRASH" :(
this is my code:
dispatch_async(myQueue, ^{
searchArray = [PublicMeathods searchInArray:searchText array:allData];
} );
if(currentViewStyle==listViewStyle){
[mytable reloadData];
}
and i've tried this :
dispatch_async(myQueue, ^{
NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
dispatch_sync(dispatch_get_main_queue(), ^{
searchArray = tmpArray;
[mytable reloadData];
});
});
but in this case the lags still there.
Update -1- :
The search Predicate takes just 2ms :) after hard work :)
but the keyboard still lags when the user searches, so the only thing I do after get result is reload table "change in UI" this what I think make it lags,
So what I search for split this two operation "typing on keyboard & refresh UI".
Update -2- :
#matehat https://stackoverflow.com/a/16879900/1658442
and
#TomSwift https://stackoverflow.com/a/16866049/1658442
answers work like a charm :)
If searchArray is the array that is used as table view data source then this array must
only be accessed and modified on the main thread.
Therefore, on the background thread, you should filter into a separate temporary array first. Then you assign the temporary array to searchArray on the main thread:
dispatch_async(myQueue, ^{
NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
dispatch_sync(dispatch_get_main_queue(), ^{
searchArray = tmpArray;
[mytable reloadData];
});
});
Update: Using a temporary array should solve the crash problem, and using a background thread helps to keep the UI responsive during the search. But as it turned out in the discussion, a major reason for the slow search might be the complicated search logic.
It might help to store additional "normalized" data (e.g. all converted to lower-case, phone numbers converted to a standard form, etc ...) so that the actual search can be done with
faster case-insensitive comparisons.
One solution might be to voluntarily induce a delay between searches to let the user type and let the search be performed asynchronously. Here's how:
First make sure your queue is created like this :
dispatch_queue_t myQueue = dispatch_queue_create("com.queue.my", DISPATCH_QUEUE_CONCURRENT);
Have this ivar defined in your class (and set it to FALSE upon initialization):
BOOL _scheduledSearch;
Write down this macro at the top of your file (or anywhere really, just make sure its visible)
#define SEARCH_DELAY_IN_MS 100
And instead of your second snippet, call this method:
[self scheduleSearch];
Whose implementation is:
- (void) scheduleSearch {
if (_scheduledSearch) return;
_scheduledSearch = YES;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)((double)SEARCH_DELAY_IN_MS * NSEC_PER_MSEC));
dispatch_after(popTime, myQueue, ^(void){
_scheduledSearch = NO;
NSString *searchText = [self textToSearchFor];
NSArray *tmpArray = [PublicMeathods searchInArray:searchText array:allData];
dispatch_async(dispatch_get_main_queue(), ^{
searchArray = tmpArray;
[mytable reloadData];
});
if (![[self textToSearchFor] isEqualToString:searchText])
[self scheduleSearch];
});
}
[self textToSearchFor] is where you should get the actual search text from.
Here's what it does :
The first time a request comes in, it sets the _scheduledSearch ivar to TRUE and tells GCD to schedule a search in 100 ms
Meanwhile any new search requests is not taken care of, because a search is going to happen anyway in a few ms
When the scheduled search happens, the _scheduledSearch ivar is reset to FALSE, so the next request is handled.
You can play with different values for SEARCH_DELAY_IN_MS to make it suit your needs. This solution should completely decouple keyboard events with workload generated from the search.
First, a couple notes on the code you presented:
1) It looks as if you're likely queuing up multiple searches as the user types, and these all have to run to completion before the relevant one (the most recent one) updates the display with the desired result set.
2) The second snippet you show is the correct pattern in terms of thread safety. The first snippet updates the UI before the search completes. Likely your crash happens with the first snippet because the background thread is updating the searchArray when the main thread is reading from it, meaning that your datasource (backed by searchArray) is in an inconsistent state.
You don't say if you're using a UISearchDisplayController or not, and it really doesn't matter. But if you are, one common issue is not implementing - (BOOL) searchDisplayController: (UISearchDisplayController *) controller shouldReloadTableForSearchString: (NSString *) filter and returning NO. By implementing this method and returning NO you are turning off the default behavior of reloading the tableView with each change to the search term. Instead you have opportunity to kick off your asynchronous search for the new term, and update the UI ([tableview reloadData]) only once you have new results.
Regardless of whether you're using UISearchDisplayController you need to take a few things into consideration when implementing your asynchronous search:
1) Ideally you can interrupt a search-in-progress and cancel it if the search is no longer useful (e.g. the search term changed). Your 'searchInArray' method doesn't appear to support this. But it's easy to do if your just scanning an array.
1a) If you can't cancel your search, you still need a way at the end of the search to see if your results are relevant or not. If not, then don't update the UI.
2) The search should run on a background thread as to not bog down the main thread and UI.
3) Once the search completes it needs to update the UI (and the UI's datasource) on the main thread.
I put together sample project (here, on Github) that performs a pretty inefficient search against a large list of words. The UI remains responsive as the user types in their term, and the spawned searches cancel themselves as they become irrelevant. The meat of the sample is this code:
- (BOOL) searchDisplayController: (UISearchDisplayController *) controller
shouldReloadTableForSearchString: (NSString *) filter
{
// we'll key off the _currentFilter to know if the search should proceed
#synchronized (self)
{
_currentFilter = [filter copy];
}
dispatch_async( _workQueue, ^{
NSDate* start = [NSDate date];
// quit before we even begin?
if ( ![self isCurrentFilter: filter] )
return;
// we're going to search, so show the indicator (may already be showing)
[_activityIndicatorView performSelectorOnMainThread: #selector( startAnimating )
withObject: nil
waitUntilDone: NO];
NSMutableArray* filteredWords = [NSMutableArray arrayWithCapacity: _allWords.count];
// only using a NSPredicate here because of the SO question...
NSPredicate* p = [NSPredicate predicateWithFormat: #"SELF CONTAINS[cd] %#", filter];
// this is a slow search... scan every word using the predicate!
[_allWords enumerateObjectsUsingBlock: ^(id obj, NSUInteger idx, BOOL *stop) {
// check if we need to bail every so often:
if ( idx % 100 == 0 )
{
*stop = ![self isCurrentFilter: filter];
if (*stop)
{
NSTimeInterval ti = [start timeIntervalSinceNow];
NSLog( #"interrupted search after %.4lf seconds", -ti);
return;
}
}
// check for a match
if ( [p evaluateWithObject: obj] )
{
[filteredWords addObject: obj];
}
}];
// all done - if we're still current then update the UI
if ( [self isCurrentFilter: filter] )
{
NSTimeInterval ti = [start timeIntervalSinceNow];
NSLog( #"completed search in %.4lf seconds.", -ti);
dispatch_sync( dispatch_get_main_queue(), ^{
_filteredWords = filteredWords;
[controller.searchResultsTableView reloadData];
[_activityIndicatorView stopAnimating];
});
}
});
return FALSE;
}
- (BOOL) isCurrentFilter: (NSString*) filter
{
#synchronized (self)
{
// are we current at this point?
BOOL current = [_currentFilter isEqualToString: filter];
return current;
}
}
i believe your crash is indeed solved by the embedding of the display of the UI element for which searchArray is the backing element in a call to GrandCentralDispatch inside of the other call (as you show in your updated original post). that is the only way to make sure you are not causing the elements of the array to change behind the scenes while the display of the items associated with it is taking place.
however, i believe if you are seeing lag, it is not so much caused by the processing of the array at 2ms or the reload that takes 30ms, but rather by the time it takes GCD to get to the internal dispatch_sync call on the main queue.
if, by this point, you have managed to get the processing of your array down to only 2ms in the worst case (or even if you've managed to get it down to less than 30ms, which is about the time it takes to process a frame in the main run loop at 30 fps), then you should consider abandoning GCD altogether in your effort to process this array. taking 2ms on the main queue to process your array is not going to cause any buggy behavior.
you may have lag elsewhere (i.e. if you are incrementing search results by trying to go out to the net to get the results, you may want to do the call and then process the response on your separate dispatch queue), but for the times you are talking about, this bit of processing doesn't need to be split out onto separate queues. for any hard-core processing that takes over 30ms, you should consider GCD.
I suspect your problem is that allData is shared between the main queue and the background queue. If you make a change in allData on the main queue, that may shorten allData in the background queue, causing an index that used to be valid to become invalid.
It's also possible that the problem is not allData itself, but some array within the objects in allData. Try setting a breakpoint on exceptions (in Xcode, open the Breakpoints source list, click the plus button at the bottom, and choose "Add Exception Breakpoint...") so you can see exactly where the error occurs.
In either case, you have two possible solutions:
Copy the offending object before using it in the search. This protects the background queue from changes in the main queue, but depending on what you need to copy, it may be difficult to get the changes back into the UI—you might have to match the copies back to their originals.
Use a lock (like #synchronized) or a per-object queue to ensure only one queue is using the object at a time. NSManagedObjectContext uses the latter approach for its -performBlock: and -performBlockAndWait: methods. It may be a little tricky to do this without blocking the main queue, though.
Try to modify your functions the next way:
function prototype;
- (void)searchInArray:searchText array:allData complete: (void (^)(NSArray *arr)) complete;
function itself
- (void)searchInArray:searchText array:allData complete: (void (^)(NSArray *arr)) complete {
NSArray * array = [NSArray new];
// function code
complete(array)//alarming that we have done our stuff
}
and when you are calling this function
dispatch_queue_t searchQueue = dispatch_queue_create("com.search",NULL);
dispatch_async(searchQueue,^{
[PublicMeathods searchInArray:searchText array:allData complete:^(NSArray *arr) {
searchArray = arr;
dispatch_async(dispatch_get_main_queue(), ^{
[myTable reloadData];
});
}];
});
Hope it will help you)
I found a simple solution with the same spirit of the solution presented by Matehad (wait some time and perform a search only if the user doesn't input anything else). Here it is:
Declare 2 global counters and a global string:
int keyboardInterruptionCounter1 = 0, int keyboardInterruptionCounter2 = 0 and NSString *searchTextGlobal
On the searchBar function do this:
-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText{
keyboardInterruptionCounter1++;
searchTextGlobal = searchText;//from local variable to global variable
NSTimeInterval waitingTimeInSec = 1;//waiting time according to typing speed.
//waits for the waiting time
[NSTimer scheduledTimerWithTimeInterval:waitingTimeInSec target:self selector:#selector(timerSearchBar:) userInfo:nil repeats:NO];
}
-(void)timerSearchBar:(NSTimer *)timer{
keyboardInterruptionCounter2++;
// enters only if nothing else has been typed.
if (keyboardInterruptionCounter2 == keyboardInterruptionCounter1) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
//do the search with searchTextGlobal string
dispatch_async(dispatch_get_main_queue(), ^{
//update UI
});
});
}
}
Explanation: The search is performed only if both counters are the same, this only happens if the user has typed and waited .52 sec without typing anything else. Instead, if the users types fast enough, then no query is done. The solution can be done with or without threading.
Martin R has posted a correct answer. The only thing to point out that instead of
dispatch_sync(dispatch_get_main_queue()
it should be
dispatch_async(dispatch_get_main_queue()
The complete code in Swift would be:
let remindersFetcherQueue = dispatch_queue_create("com.gmail.hillprincesoftware.remindersplus", DISPATCH_QUEUE_CONCURRENT)
dispatch_sync(remindersFetcherQueue) {
println("Start background queue")
estore.fetchRemindersMatchingPredicate(remindersPredicate) {
reminders in
// var list = ... Do something here with the fetched reminders.
dispatch_async(dispatch_get_main_queue()) {
self.list = list // Assign to a class property
self.sendChangedNotification() // This should send a notification which calls a function to ultimately call setupUI() in your view controller to do all the UI displaying and tableView.reloadData().
}
}
}

Multiple Threads Using GCD while Fetching Photos

I have to fetch contacts from the Address Book and show photo beside each if found in a UITableView.
I fetch all contacts using ABContactsHelper library and then asynchronously fetch photos for visible rows in the UITableView using GCD blocks.
I referred to an Apple Sample code which waits for the UITableView to finish scrolling, get Visible NSIndexPaths & created threads to fetch photos. My problem so far is two fold.
First, if user scrolls, stops, scrolls & stops and does it quite a few times, too many threads are generated for fetching photos which slows down the app.
Secondly, when the thread returns to set photo in cache as well as the UITableViewCell however, the reference to UIImageView is now being reused for another record in UITableViewCell, hence the photo is placed on wrong record which eventually gets replace by correct one, when thread for that particular record returns.
Here is the code I is used both in cellForRowAtIndexPath as well as when UITableView stops scrolling.
- (void)loadImagesLazilyForIndexPath:(NSIndexPath *)indexPath photo:(UIImageView *)photo contact:(ContactModel *)contact
{
if (!self.tableView.isDragging && !self.tableView.isDecelerating) {
UIImage *thePhoto = [self.imagesForContacts objectForKey:indexPath];
if (!thePhoto) {
// NSLog(#"Photo Not Found - Now Fetching %#", indexPath);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
#autoreleasepool {
UIImage *image = [[JMContactsManager sharedContactsManager] photoForContact:contact];
if (!image)
image = self.noProfilePhoto;
[self.imagesForContacts setObject:image forKey:indexPath];
dispatch_async(dispatch_get_main_queue(), ^{
// NSLog(#"Photo Fetched %#", indexPath);
#autoreleasepool {
NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
BOOL visible = [visiblePaths indexOfObjectPassingTest:^BOOL(NSIndexPath * ip, NSUInteger idx, BOOL *stop) {
if (ip.row == indexPath.row && ip.section == indexPath.section) {
*stop = YES;
return 1;
}
return 0;
}];
if (visible)
photo.image = [self.imagesForContacts objectForKey:indexPath];
[[NSURLCache sharedURLCache] removeAllCachedResponses];
}
});
}
});
} else {
// NSLog(#"Photo Was Found %#", indexPath);
#autoreleasepool {
photo.image = [self.imagesForContacts objectForKey:indexPath];
}
}
}
}
For this kind of functionality I would go with an NSOperation and an NSOperationQueue, they are build on top of GCD, but it gives you the opportunity to cancel operations. You could check which operation aren't visible anymore and cancel them. In thi s way you can control reference "away".
I see also another issue that could lead into a "problem" it seems that you are caching images in an NSMutableDictionary, aren't you? Or are you using an NSCache? If it is an NScache is fine, but most of mutable object aren't thread safe "naturally"
Boost up the priority of the queue :-)
As mentioned by #Andrea, you should be using an NSOperationQueue, which gives you the ability to cancel queued tasks.
Indexing your image cache by indexPath into your table is not robust as an index path for a given element could change (although maybe not in your specific case). You might consider indexing your image cache by ABRecord.uniqueId instead.
In any case it will not solve the problem of your images being set twice or more for the same cell. This happens because UITableView does not assign a view for each item but manages a pool of UITableCellViews, which it re-uses each time. What you could do is something along the following lines:
// Assuming your "ContactCellView" inherits from UITableCellView and has a contact property
// declared as follows: #property (retain) ABRecord *contact.
- (void) setContact:(ABRecord*)contact
{
_contact = contact;
__block UIImage *thePhoto = [self.imagesForContacts objectForKey:contact.uniqueId];
if (thePhoto == nil) {
_loadImageOp = [NSBlockOperation blockOperationWithBlock:^(void) {
// Keep a local reference to the contact because it might change on us at any time.
ABRecord *fetchContact = contact;
// Fetch the photo as you normally would
thePhoto = [[JMContactsManager sharedContactsManager] photoForContact:fetchContact];
if (thePhoto == nil)
thePhoto = self.noProfilePhoto;
// Only assign the photo if the contact has not changed in the mean time.
if (fetchContact == _contact)
_contactPhotoView.image = thePhoto;
}];
} else {
_contactPhotoView.image = thePhoto;
}
}

Getting EXC_BAD_ACCESS when using dispatch_async with Core Data

I have images in coredata which I'm trying to load lazily for a table view. Each cell uses an observer for the related core data entity to update the image when one becomes available. The relevant code in the entity is as follows:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// The heavy lifting seems to be during firing of the fault and accessing data,
// so i'm trying to do that in the background thread.
UIImage *i = [UIImage imageWithData:self.imageEntity.data];
// I now need to notify observers that the image is ready on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self willChangeValueForKey:#"image"];
image = i;
[self didChangeValueForKey:#"image"];
});
});
The project uses ARC, I'm not getting any compiler errors or warnings, and when I run it kind of works until I scroll fast, and then I get a EXC_BAD_ACCESS on the line when i'm declaring the i.
What am I missing here?
Apparently fetching CoreData objects is not thread safe. So it's suggested to use the same persistentStoreCoordinator, but different ObjectContexts. Here's my updated code that no longer seems to crash:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
#autoreleasepool {
// Create a new context
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc] init];
// Use an existing coordinator
NSPersistentStoreCoordinator *coordinator = [[DataSource sharedDataSource] persistentStoreCoordinator];
[backgroundContext setPersistentStoreCoordinator:coordinator];
// Getting objectID does not fire the fault, so we grab it but actually fetch the object
// on a background context to be thread safe.
Image *imageEntity = (Image*)[backgroundContext objectWithID:self.imageEntity.objectID];
image = [UIImage imageWithData:imageEntity.data];
// Notify observers that the image is ready on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self willChangeValueForKey:#"image"];
[self didChangeValueForKey:#"image"];
});
}
});
Dizy, also keep in mind that the Image object that is created in the code:
UIImage *i = [UIImage imageWithData:self.imageEntity.data];
is set for autorelease. The dispatch_async method runs on the main queue, so there is a chance that the memory allocated for the image may be released by the time the main thread runs the dispatch block.
CoreData is not thread safe, you've to manage contexts to avoid crashes.
If you plan to heavy use a lot of concurrent processes to update data in Core Data, I would suggest you to take a look to MagicalRecord, an amazing pattern inspired by Active Record of Rails and that handles all these aspects in a really smart way.

Last In-First Out Stack with GCD?

I have a UITableView that displays images associated with contacts in each row. In some cases these images are read on first display from the address book contact image, and where there isn't one they are an avatar rendered based on stored data. I presently have these images being updated on a background thread using GCD. However, this loads the images in the order they were requested, which means during rapid scrolling the queue becomes lengthy and when the user stops scrolling the current cells are the last to get updated. On the iPhone 4, the problem isn't really noticeable, but I am keen to support older hardware and am testing on an iPhone 3G. The delay is tolerable but quite noticeable.
It strikes me that a Last In-First Out stack would seem likely to largely resolve this issue, as whenever the user stopped scrolling those cells would be the next to be updated and then the others that are currently off-screen would be updated. Is such a thing possible with Grand Central Dispatch? Or not too onerous to implement some other way?
Note, by the way, that I am using Core Data with a SQLite store and I am not using an NSFetchedResultsController because of a many-to-many relationship that has to be traversed in order to load the data for this view. (As far as I am aware, that precludes using an NSFetchedResultsController.) [I've discovered an NSFetchedResultsController can be used with many-to-many relationships, despite what the official documentation appears to say. But I'm not using one in this context, yet.]
Addition: Just to note that while the topic is "How do I create a Last In-First Out Stack with GCD", in reality I just want to solve the issue outlined above and there may be a better way to do it. I am more than open to suggestions like timthetoolman's one that solves the problem outlined in another way; if such a suggestion is finally what I use I'll recognize both the best answer to the original question as well as the best solution I ended up implementing... :)
Because of the memory constraints of the device, you should load the images on demand and on a background GCD queue. In the cellForRowAtIndexPath: method check to see if your contact's image is nil or has been cached. If the image is nil or not in cache, use a nested dispatch_async to load the image from the database and update the tableView cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// If the contact object's image has not been loaded,
// Use a place holder image, then use dispatch_async on a background queue to retrieve it.
if (contact.image!=nil){
[[cell imageView] setImage: contact.image];
}else{
// Set a temporary placeholder
[[cell imageView] setImage: placeHolderImage];
// Retrieve the image from the database on a background queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
UIImage *image = // render image;
contact.image=image;
// use an index path to get at the cell we want to use because
// the original may be reused by the OS.
UITableViewCell *theCell=[tableView cellForRowAtIndexPath:indexPath];
// check to see if the cell is visible
if ([tableView visibleCells] containsObject: theCell]){
// put the image into the cell's imageView on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
[[theCell imageView] setImage:contact.image];
[theCell setNeedsLayout];
});
}
});
}
return cell;
}
The WWDC2010 conference video "Introducing Blocks and Grand Central Dispatch" shows an example using the nested dispatch_async as well.
another potential optimization could be to start downloading the images on a low priority background queue when the app launches. i.e.
// in the ApplicationDidFinishLaunchingWithOptions method
// dispatch in on the main queue to get it working as soon
// as the main queue comes "online". A trick mentioned by
// Apple at WWDC
dispatch_async(dispatch_get_main_queue(), ^{
// dispatch to background priority queue as soon as we
// get onto the main queue so as not to block the main
// queue and therefore the UI
dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
dispatch_apply(contactsCount,lowPriorityQueue ,^(size_t idx){
// skip the first 25 because they will be called
// almost immediately by the tableView
if (idx>24){
UIImage *renderedImage =/// render image
[[contactsArray objectAtIndex: idx] setImage: renderedImage];
}
});
});
With this nested dispatch, we are rendering the images on an extremely low priority queue. Putting the image rendering on the background priority queue will allow the images being rendered from the cellForRowAtIndexPath method above to be rendered at a higher priority. So, because of the difference in priorities of the queues, you will have a "poor mans" LIFO.
Good luck.
The code below creates a flexible last in-first out stack that is processed in the background using Grand Central Dispatch. The SYNStackController class is generic and reusable but this example also provides the code for the use case identified in the question, rendering table cell images asynchronously, and ensuring that when rapid scrolling stops, the currently displayed cells are the next to be updated.
Kudos to Ben M. whose answer to this question provided the initial code on which this was based. (His answer also provides code you can use to test the stack.) The implementation provided here does not require ARC, and uses solely Grand Central Dispatch rather than performSelectorInBackground. The code below also stores a reference to the current cell using objc_setAssociatedObject that will enable the rendered image to be associated with the correct cell, when the image is subsequently loaded asynchronously. Without this code, images rendered for previous contacts will incorrectly be inserted into reused cells even though they are now displaying a different contact.
I've awarded the bounty to Ben M. but am marking this as the accepted answer as this code is more fully worked through.
SYNStackController.h
//
// SYNStackController.h
// Last-in-first-out stack controller class.
//
#interface SYNStackController : NSObject {
NSMutableArray *stack;
}
- (void) addBlock:(void (^)())block;
- (void) startNextBlock;
+ (void) performBlock:(void (^)())block;
#end
SYNStackController.m
//
// SYNStackController.m
// Last-in-first-out stack controller class.
//
#import "SYNStackController.h"
#implementation SYNStackController
- (id)init
{
self = [super init];
if (self != nil)
{
stack = [[NSMutableArray alloc] init];
}
return self;
}
- (void)addBlock:(void (^)())block
{
#synchronized(stack)
{
[stack addObject:[[block copy] autorelease]];
}
if (stack.count == 1)
{
// If the stack was empty before this block was added, processing has ceased, so start processing.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[self startNextBlock];
});
}
}
- (void)startNextBlock
{
if (stack.count > 0)
{
#synchronized(stack)
{
id blockToPerform = [stack lastObject];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[SYNStackController performBlock:[[blockToPerform copy] autorelease]];
});
[stack removeObject:blockToPerform];
}
[self startNextBlock];
}
}
+ (void)performBlock:(void (^)())block
{
#autoreleasepool {
block();
}
}
- (void)dealloc {
[stack release];
[super dealloc];
}
#end
In the view.h, before #interface:
#class SYNStackController;
In the view.h #interface section:
SYNStackController *stackController;
In the view.h, after the #interface section:
#property (nonatomic, retain) SYNStackController *stackController;
In the view.m, before #implementation:
#import "SYNStackController.h"
In the view.m viewDidLoad:
// Initialise Stack Controller.
self.stackController = [[[SYNStackController alloc] init] autorelease];
In the view.m:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Set up the cell.
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
else
{
// If an existing cell is being reused, reset the image to the default until it is populated.
// Without this code, previous images are displayed against the new people during rapid scrolling.
[cell setImage:[UIImage imageNamed:#"DefaultPicture.jpg"]];
}
// Set up other aspects of the cell content.
...
// Store a reference to the current cell that will enable the image to be associated with the correct
// cell, when the image subsequently loaded asynchronously.
objc_setAssociatedObject(cell,
personIndexPathAssociationKey,
indexPath,
OBJC_ASSOCIATION_RETAIN);
// Queue a block that obtains/creates the image and then loads it into the cell.
// The code block will be run asynchronously in a last-in-first-out queue, so that when
// rapid scrolling finishes, the current cells being displayed will be the next to be updated.
[self.stackController addBlock:^{
UIImage *avatarImage = [self createAvatar]; // The code to achieve this is not implemented in this example.
// The block will be processed on a background Grand Central Dispatch queue.
// Therefore, ensure that this code that updates the UI will run on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
NSIndexPath *cellIndexPath = (NSIndexPath *)objc_getAssociatedObject(cell, personIndexPathAssociationKey);
if ([indexPath isEqual:cellIndexPath]) {
// Only set cell image if the cell currently being displayed is the one that actually required this image.
// Prevents reused cells from receiving images back from rendering that were requested for that cell in a previous life.
[cell setImage:avatarImage];
}
});
}];
return cell;
}
Ok, I've tested this and it works. The object just pulls the next block off the stack and executes it asynchronously. It currently only works with void return blocks, but you could do something fancy like add an object that will has a block and a delegate to pass the block's return type back to.
NOTE: I used ARC in this so you'll need the XCode 4.2 or greater, for those of you on later versions, just change the strong to retain and you should be fine, but it will memory leak everything if you don't add in releases.
EDIT: To get more specific to your use case, if your TableViewCell has an image I would use my stack class in the following way to get the performance you want, please let me know if it work well for you.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
UIImage *avatar = [self getAvatarIfItExists];
// I you have a method to check for the avatar
if (!avatar)
{
[self.blockStack addBlock:^{
// do the heavy lifting with your creation logic
UIImage *avatarImage = [self createAvatar];
dispatch_async(dispatch_get_main_queue(), ^{
//return the created image to the main thread.
cell.avatarImageView.image = avatarImage;
});
}];
}
else
{
cell.avatarImageView.image = avatar;
}
return cell;
}
Here's the testing code that show's that it works as a stack:
WaschyBlockStack *stack = [[WaschyBlockStack alloc] init];
for (int i = 0; i < 100; i ++)
{
[stack addBlock:^{
NSLog(#"Block operation %i", i);
sleep(1);
}];
}
Here's the .h:
#import <Foundation/Foundation.h>
#interface WaschyBlockStack : NSObject
{
NSMutableArray *_blockStackArray;
id _currentBlock;
}
- (id)init;
- (void)addBlock:(void (^)())block;
#end
And the .m:
#import "WaschyBlockStack.h"
#interface WaschyBlockStack()
#property (atomic, strong) NSMutableArray *blockStackArray;
- (void)startNextBlock;
+ (void)performBlock:(void (^)())block;
#end
#implementation WaschyBlockStack
#synthesize blockStackArray = _blockStackArray;
- (id)init
{
self = [super init];
if (self)
{
self.blockStackArray = [NSMutableArray array];
}
return self;
}
- (void)addBlock:(void (^)())block
{
#synchronized(self.blockStackArray)
{
[self.blockStackArray addObject:block];
}
if (self.blockStackArray.count == 1)
{
[self startNextBlock];
}
}
- (void)startNextBlock
{
if (self.blockStackArray.count > 0)
{
#synchronized(self.blockStackArray)
{
id blockToPerform = [self.blockStackArray lastObject];
[WaschyBlockStack performSelectorInBackground:#selector(performBlock:) withObject:[blockToPerform copy]];
[self.blockStackArray removeObject:blockToPerform];
}
[self startNextBlock];
}
}
+ (void)performBlock:(void (^)())block
{
block();
}
#end
A simple method that may be Good Enough for your task: use NSOperations' dependencies feature.
When you need to submit an operation, get the queue's operations and search for the most recently submitted one (ie. search back from the end of the array) that hasn't been started yet. If such a one exists, set it to depend on your new operation with addDependency:. Then add your new operation.
This builds a reverse dependency chain through the non-started operations that will force them to run serially, last-in-first-out, as available. If you want to allow n (> 1) operations to run simultaneously: find the n th most recently added unstarted operation and add the dependency to it. (and of course set the queue's maxConcurrentOperationCount to n.) There are edge cases where this won't be 100% LIFO but it should be good enough for jazz.
(This doesn't cover re-prioritizing operations if (e.g.) a user scrolls down the list and then back up a bit, all faster than the queue can fill in the images. If you want to tackle this case, and have given yourself a way to locate the corresponding already-enqueued-but-not-started operation, you can clear the dependencies on that operation. This effectively bumps it back to the "head of the line". But since pure first-in-first-out is almost good enough already, you may not need to get this fancy.)
[edited to add:]
I've implemented something very like this - a table of users, their avatars lazy-fetched from gravatar.com in the background - and this trick worked great. The former code was:
[avatarQueue addOperationWithBlock:^{
// slow code
}]; // avatarQueue is limited to 1 concurrent op
which became:
NSBlockOperation *fetch = [NSBlockOperation blockOperationWithBlock:^{
// same slow code
}];
NSArray *pendingOps = [avatarQueue operations];
for (int i = pendingOps.count - 1; i >= 0; i--)
{
NSOperation *op = [pendingOps objectAtIndex:i];
if (![op isExecuting])
{
[op addDependency:fetch];
break;
}
}
[avatarQueue addOperation:fetch];
The icons visibly populate from the top down in the former case. In the second, the top one loads, then the rest load from the bottom up; and scrolling rapidly down causes occasional loading, then immediate loading (from the bottom) of icons of the screenful you stop at. Very slick, much "snappier" feel to the app.
I haven't tried this - just throwing ideas out there.
You could maintain your own stack. Add to the stack and queue to GCD on the foreground thread. The block of code you queue to GCD simply pulls the next block off your stack (the stack itself would need internal synchronization for push & pop) and runs it.
Another option may be to simply skip the work if there's more than n items in the queue. That would mean that if you quickly got the queue backed up, it would quickly press through the queue and only process < n. If you scroll back up, the cell reuse queue, would get another cell and then you would queue it again to load the image. That would always prioritize the n most recently queued. The thing I'm not sure about is how the queued block would know about the number of items in the queue. Perhaps there's a GCD way to get at that? If not, you could have a threadsafe counter to increment/decrement. Increment when queueing, decrement on processing. If you do that, I would increment and decrement as the first line of code on both sides.
Hope that sparked some ideas ... I may play it around with it later in code.
I do something like this, but iPad-only, and it seemed fast enough. NSOperationQueue (or raw GCD) seems like the simplest approach, in that everything can be self-contained and you don't need to worry about synchronization. Also, you might be able to save the last operation, and use setQueuePriority: to lower it. Then the most recent one will be pulled from the queue first. Or go through all -operations in the queue and lower their priority. (You could probably do this after completing each one, I assume this would still be significantly faster than doing the work itself.)
create a thread safe stack, using something like this as a starting point:
#interface MONStack : NSObject <NSLocking> // << expose object's lock so you
// can easily perform many pushes
// at once, keeping everything current.
{
#private
NSMutableArray * objects;
NSRecursiveLock * lock;
}
/**
#brief pushes #a object onto the stack.
if you have to do many pushes at once, consider adding `addObjects:(NSArray *)`
*/
- (void)addObject:(id)object;
/** #brief removes and returns the top object from the stack */
- (id)popTopObject;
/**
#return YES if the stack contains zero objects.
*/
- (BOOL)isEmpty;
#end
#implementation MONStack
- (id)init {
self = [super init];
if (0 != self) {
objects = [NSMutableArray new];
lock = [NSRecursiveLock new];
if (0 == objects || 0 == lock) {
[self release];
return 0;
}
}
return self;
}
- (void)lock
{
[lock lock];
}
- (void)unlock
{
[lock unlock];
}
- (void)dealloc
{
[lock release], lock = 0;
[objects release], objects = 0;
[super dealloc];
}
- (void)addObject:(id)object
{
[self lock];
[objects addObject:object];
[self unlock];
}
- (id)popTopObject
{
[self lock];
id last = 0;
if ([objects count]) {
last = [[[objects lastObject] retain] autorelease];
}
[self unlock];
return last;
}
- (BOOL)isEmpty
{
[self lock];
BOOL ret = 0 == [objects count];
[self unlock];
return ret;
}
#end
then use an NSOperation subclass (or GCD, if you prefer). you can share the stack between the operation and the clients.
so the empty bit and the NSOperation main are the somewhat tricky sections.
let's start with the empty bit. this is tricky because it needs to be threadsafe:
// adding a request and creating the operation if needed:
{
MONStack * stack = self.stack;
[stack lock];
BOOL wasEmptyBeforePush = [stack isEmpty];
[stack addObject:thing];
if (wasEmptyBeforePush) {
[self.operationQueue addOperation:[MONOperation operationWithStack:stack]];
}
[stack unlock];
// ...
}
the NSOperation main should just go through and exhaust the stack, creating an autorelease pool for each task, and checking for cancellation. when the stack is empty or the operation is cancelled, cleanup and exit main. the client will create a new operation when needed.
supporting cancellation for slower requests (e.g. network or disk) can make a huge difference. cancellation in the case of the operation which exhausted the queue would require that the requesting view could remove its request when it is dequeued (e.g. for reuse during scrolling).
another common pitfall: immediate async loading (e.g. adding the operation to the operation queue) of the image may easily degrade performance. measure.
if the task benefits from parallelization, then allow multiple tasks in the operation queue.
you should also identify redundant requests (imagine a user scrolling bidirectionally) in your task queue, if your program is capable of producing them.
I'm a big fan of NSOperationQueue's interface and ease-of-use, but I also needed a LIFO version. I ended up implementing a LIFO version of NSOperationQueue here that has held up quite well for me. It mimics NSOperationQueue's interface, but executes things in a (roughly) LIFO order.