Thursday, May 30, 2013

About NSManagedObjectContext PerformBlock Method.

Recently working on my to be announced project "***", I need to create core data file and import the data into my database for the user when and only when it's the first time to run the app. The whole process of importing data to my database is
1, read data from variety property list.
2, convert the data to the usable data my program can use.
3, store these data in my database via Core Data.
To implement this, I borrowed a piece of code from Stanford's CS193p Online Course Core Data Demo. The key part I borrowed is followed.


// Whenever the table is about to appear, if we have not yet opened/created or demo document, do so.

- (void)viewWillAppear:(BOOL)animated
    [super viewWillAppear:animated];
    if (!self.managedObjectContext) [self useDemoDocument];

- (void)useDemoDocument
    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
    url = [url URLByAppendingPathComponent:@"Demo Document"];
    UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];
    if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
        [document saveToURL:url
          completionHandler:^(BOOL success) {
              if (success) {
                  self.managedObjectContext = document.managedObjectContext;
                  [self refresh];
    } else if (document.documentState == UIDocumentStateClosed) {
        [document openWithCompletionHandler:^(BOOL success) {
            if (success) {
                self.managedObjectContext = document.managedObjectContext;

#pragma mark - Refreshing

// Fires off a block on a queue to fetch data from Flickr.
// When the data comes back, it is loaded into Core Data by posting a block to do so on
//   self.managedObjectContext's proper queue (using performBlock:).
// Data is loaded into Core Data by calling photoWithFlickrInfo:inManagedObjectContext: category method.

- (IBAction)refresh
    [self.refreshControl beginRefreshing];
    dispatch_queue_t fetchQ = dispatch_queue_create("Flickr Fetch", NULL);
    dispatch_async(fetchQ, ^{
        NSArray *photos = [FlickrFetcher latestGeoreferencedPhotos];
        // put the photos in Core Data
        [self.managedObjectContext performBlock:^{
            for (NSDictionary *photo in photos) {
                [Photo photoWithFlickrInfo:photo inManagedObjectContext:self.managedObjectContext];
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.refreshControl endRefreshing];

This code shows, every time the view is opened, NSManagedObjectContext State gets examined. If it's not assigned (is nil), then we go deep to see if we can do anything includes: "Create", "Open"... maybe more if we have more "else if" statement. Mostly the code did in "useDemoDocument" is assign one valid value to NSManagedObjectContext object, except it will initiate the database through refresh function if the database is not existed.

In refresh function. (KEY)

It starts the refreshControl first, refreshControl is added since iOS6.0, only available for UITableViewController. Developer could enable it through Xcode Inspection for the UITableViewController. (BTW, you can add action to it by just drag and drop through Xcode as many developers do for segue things, you can only make it functional through code.)

Then, it creates a queue, in which we could do some tasks that would not block our UIView. This is used when you have some time consuming task needs to be done without much attention from the user, like download files, core data operation (insert, delete, update) and post content to internet without user's notice.

Then, assign tasks to this queue with dispatch_async. In the queue, the code fetches data from the internet first. When it's done, we call
- (void)performBlock:(void (^)())blockParametersblock
The block to perform.
  • Available in iOS 5.0 and later.
Declared InNSManagedObjectContext.h

This function was added with his brother performBlockAndWait: coming with iOS5.0. The best part of this is the developer knows who is associated with making the most operations. Because NSManagedObjectContext is the handler here to insert all of the data into our database, no one can ask it to do any conflict job until it's all done with the job I am asking it to be doing it now. Which means this NSManagedObjectContext is blocked until this task is done. Otherwise, your data can't be successfully added to your database here. The data will be lost every time.

At last, the function asks the main queue (which is also queue responsible for main UI related operations) to dismiss the refresh control it started at the start this function is called.

So for this function, three key parts.
1st, use dispatch_queue to do any time consuming task.
2nd, use performBlock to import the large data at the background for NSManagedObjectContext, or conflict will be showed up.
3rd, any NSManagedObjectContext related operations must be placed inside performBlock's block. Otherwise, it's equally like not using performBlock. So I can't make a loop out to call performBlock for every property list, since one will block the other when it's called. That's what's wrong I initially did, after the first time I opened the app, it seems app could be saved and loaded in my UITableView, the second time I opened my app, nothing shows up.

More detail, please check this.

No comments :

Post a Comment