Directory Monitoring and GCD

A reader (@ramsch) writes to call my attention to his post on AAPL’s developer forums (unfortunately you need to log in as a developer to see it … scroll to the bottom of page 2 — reply #38 — if you can) describing an alternate, simpler approach to the directory monitoring code I discussed previously. His approach is interesting in and of itself, and as an introduction to Grand Central Dispatch. Today I provide some remarks on this code, and a working demo project, which you can download here.

GCD

Grand Central Dispatch is a newish technology introduced to iOS with the 4.0 release. GCD is a low-level concurrent execution API, and seems to be part of AAPL’s long-running “we hate user-level threads” campaign. Since it offers some of the same event-processing mechanisms as kqueue, it can also be used to monitor and respond to changes in a directory’s contents.

Approach

The key to GCD-based directory monitoring is the dispatch_source_create method, which “[c]reates a new dispatch source to monitor low-level system objects and automatically submit a handler block to a dispatch queue in response to events”. The “low-level system [object]” in this case is a directory file descriptor, and the “events” are writes. The “handler block” (you can read a bit about blocks here … they’re like closures for C) will trigger our existing updateFns method. By using the dispatch_source_create method, we can replace all the kqueue and CFFileDescriptorContext logic in the earlier demo with GCD-based code.

Code

The major changes are to the RootViewController class definition, and to its startMonitor and stopMonitor methods. (The kqueueFired method and KQCallback static function are also removed.)

Here’s the bulk of the new code; hopefully the comments are sufficient explanation.

@interface RootViewController : UITableViewController
{
	dispatch_source_t	_src;
	
	NSArray*		fns;
}
- (void)startMonitor
{
	// One ping only
	if (_src != NULL) return;

	// Fetch pathname of the directory to monitor
	liveshareAppDelegate* delegate = (liveshareAppDelegate*) [[UIApplication sharedApplication] delegate];
	NSString* docPath = [delegate applicationDocumentsDirectory];
	if (!docPath) return;
	
	// Open an event-only file descriptor associated with the directory
	int dirFD = open([docPath fileSystemRepresentation], O_EVTONLY);
	if (dirFD < 0) return;
	
	// Get the main thread's serial dispatch queue (since we'll be updating the UI)
	dispatch_queue_t queue = dispatch_get_main_queue();
	if (!queue)
	{
		close(dirFD);
		return;
	}
	
	// Create a dispatch source to monitor the directory for writes
	_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,	// Watch for certain events on the VNODE spec'd by the second (handle) argument
				      dirFD,				// The handle to watch (the directory FD)
				      DISPATCH_VNODE_WRITE,		// The events to watch for on the VNODE spec'd by handle (writes)
				      queue);				// The queue to which the handler block will ultimately be dispatched
	if (!_src)
	{
		close(dirFD);
		return;
	}
	
	// Set the block to be submitted in response to an event
	dispatch_source_set_event_handler(_src, ^{[self updateFns];});

	// Set the block to be submitted in response to source cancellation
	dispatch_source_set_cancel_handler(_src, ^{close(dirFD);});
	
	// Unsuspend the source s.t. it will begin submitting blocks
	dispatch_resume(_src);
}
- (void)stopMonitor
{
	if (_src)
	{
		// Stop the source from submitting further blocks (and close the underlying FD)
		dispatch_source_cancel(_src);
		
		// Release the source
		dispatch_release(_src);
		_src = NULL;
	}
}
Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in iPhone. Bookmark the permalink.