One way to fetch a random row from a CoreData entity

I spent a little time looking around for a way to get a random row out of a CoreData entity, and I didn’t find too much, so I’ll post about it here. In my entity (let’s call it “Items”), there are 198 objects stored. I have a non-alphabetical Decimal “siteOrder” attribute that helps me sort by a canonical order dependent on its original source. Because I have this decimal attribute already, I can take advantage of it by using arc4random() (don’t forget to

#import stdlib.h

for it) to just pick out an appropriate random index, and then set that in an NSPredicate like this:

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
        // Assumes that you know the number of objects per entity, and that your order starts at zero.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"siteOrder = %d", arc4random() % kNumItems];
[request setPredicate:predicate];
[request setFetchLimit:1];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:request error:&error];

Easy!

UIScrollView and Memory Allocations

I ran into a curious problem while using nib-based UIView hierarchies as part of a horizontally scrolling UIScrollView. I followed the Apple PageControl example of progressively loading the next and last page’s viewcontrollers and adding them as subviews, and I also took the step of progressively unloading the viewcontrollers (see below) outside those bounds. Still, every time I was paging back and forth, my real memory allocations would increase by a few MB for every page!

This became a problem pretty quickly, as you can imagine. I ended up finding a solution when I saw this post about the reverse issue.

It turns out that if you assign a viewcontroller’s view as a subview of a UIScrollView, you must both send a removeFromSuperview message to the subview, and also replace the viewcontroller’s spot in the NSArray container in order to get it released.

- (void)unloadPage:(int)page {
if (page < 0) return;
if (page >= kNumItems) return;
if ((NSNull *)[viewControllers objectAtIndex:page] != [NSNull null]) {
ItemViewController *controllerToDelete =
                    [viewControllers objectAtIndex:page];
// Release the view from the scrollview view
[controllerToDelete.view removeFromSuperview];
// release the viewcontroller from the collection
[viewControllers replaceObjectAtIndex:page
                    withObject:[NSNull null]];
}
}

This ended up making my memory allocations steady, but it didn’t totally get rid of the hitching that was present. I believe that because my UIViews held in the UIScrollView load images from the Documents folder, I/O was getting triggered every time a pagination boundary was crossed, which in the PageControl example is unfortunately right in the middle of the transition. However, a smarter way to do the loading is to make most loads occur within the scrollViewDidEndDecelerating: event handler, like so:

- (void) loadSurroundingPagesForPage:(int)page {

[self unloadPage:page + kSurroundingPagesToLoad + 1];
[self unloadPage:page - kSurroundingPagesToLoad - 1];

for (int i=1; i < kSurroundingPagesToLoad; i++) {
[self loadPage:page + i];
[self loadPage:page - i];
}

[self loadPage:page];

lastPageLoaded = page;

}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    pageControlUsed = NO;
[self loadSurroundingPagesForPage:pageControl.currentPage];
}

That way, in normal use, a user flipping slowly through the scrolled items won’t notice the hitch because the subview will be still while it happens! Unfortunately, this means that a user who is continuously scrolling might scroll completely past the loaded content, leading to a “blank” – so, if you’re willing to take a hitch, you can force a load in those conditions.

- (void)scrollViewDidScroll:(UIScrollView *)sender {
if (pageControlUsed) {
return;
}

CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth/2) / pageWidth) + 1;
pageControl.currentPage = page;
// Change of tactics - do lazy load in between pages IFF the new page requested is near our bounds
if (abs(lastPageLoaded - page) >= kSurroundingPagesToLoad-1) {
NSLog(@"Reached bounds without decelerating - must do a lazy load");
[self loadSurroundingPagesForPage:pageControl.currentPage];
}

}

This last bit takes the hitch in the middle of the page transition if we run out of our buffer zone of UIScrollView subviews. A couple possible optimizations come to mind - well, first, you could up the value of the kSurroundingPagesToLoad constant. Also, you could take account of the direction of the scrolling, and load more pages in the forward scroll direction than in the reverse. It would sort of be like a dual-clutch transmission at that point, only unable to cope when shifting unexpectedly or too many times in a row.

Anyway, this approach seems to work well for my project, and it uses the fetchedresultcontroller as well to cache and lazily load the coredata objects which populate the child views, so all of the scrollview stuff only takes up 4-5 MB of data while running. Pretty nice if you ask me!

I hope that helps anyone who was in the same boat as me. As always, use any of the code you find here at your own risk! Good luck! :)