⬆️ ⬇️

File Monitoring with GCD

I think that most iOS developers know how to easily enable iTunes File Sharing in their application, adding just one line to Info.plist:
UIFileSharingEnabled = YES 
But this is not even half the battle. The point is that, in an amicable way, the application should now monitor all changes with files occurring in the Documents directory and update its data accordingly. How to release it in your code and tell this article.

image



To begin with, quite a bit of theory from the Concurrency Programming Guide. In GCD, there is such a thing as dispatch source , a fundamental data type that is responsible for coordinating the processing of specific low-level events. To solve our problem, we are most interested in its kind as descriptor sources , which notifies of various operations with files or sockets.



It turns out that we need to create an event dispatch_source_create using dispatch_source_create , the source of which will be the file descriptor (directory file descriptor), and the event itself (file recording) will work out the necessary block for updating the application data.



Here it should be noted that the event is triggered by the first byte of the file recorded on the disk, and the files are now large. Therefore, in the data update block, we launch a recursive check of the end of synchronization with a timeout of one second.

')

So, closer to the body. Let's create two main methods startMonitor and stopMonitor , respectively, launching and stopping the monitoring of the directory we need, as well as a couple of subsidiary methods for checking changes in this directory that will be launched through the handler block.



 - (void)startMonitor { //     dispatch_source_t if (_src != NULL) return; //    Documents   NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; //  ,     (O_EVTONLY) _fileDescriptor = open([docPath fileSystemRepresentation], O_EVTONLY); //    thread,     UI dispatch_queue_t queue = dispatch_get_main_queue(); //    dispatch source    (write) _src = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, _fileDescriptor, DISPATCH_VNODE_WRITE, queue); // handler block      dispatch_source_set_event_handler(_src, ^{ [self directoryDidChange]; }); //     dispatch_source_set_cancel_handler(_src, ^{ close(_fileDescriptor); }); dispatch_resume(_src); } - (void)stopMonitor { if (_src) { //   ,    dispatch_source_cancel(_src); _src = NULL; } } - (void)directoryDidChange { if(!waitingForDocumentsDirectoryTimeout) { // ,    ,     waitingForDocumentsDirectoryTimeout = YES; //       _lastDocumentsDirectoryReferenceArray=[self documentsDirectoryReferenceArray]; //...         ,  [self performSelector:@selector(checkForDocumentsDirectoryChanges) withObject:nil afterDelay:1.0 inModes:[NSArray arrayWithObjects:NSRunLoopCommonModes,nil]]; } } -(void)checkForDocumentsDirectoryChanges { NSArray *newDocumentsDirectoryReferenceArray=[self documentsDirectoryReferenceArray]; //      if(![newDocumentsDirectoryReferenceArray isEqualToArray:_lastDocumentsDirectoryReferenceArray]) { //       _lastDocumentsDirectoryReferenceArray=newDocumentsDirectoryReferenceArray; [self performSelector:@selector(checkForDocumentsDirectoryChanges) withObject:nil afterDelay:1.0 inModes:[NSArray arrayWithObjects:NSRunLoopCommonModes,nil]]; } else { //    waitingForDocumentsDirectoryTimeout=NO; _lastDocumentsDirectoryReferenceArray=nil; // ...         ,  [self scanDocumentsDirectory]; } } -(NSArray *)documentsDirectoryReferenceArray { //         - NSString *documentsDirectoryPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSArray *documentsDirectoryContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectoryPath error:NULL]; NSMutableArray *documentsDirectoryReferenceArray=[NSMutableArray arrayWithCapacity:10]; for(NSString *fileName in documentsDirectoryContents){ NSString *filePath=[documentsDirectoryPath stringByAppendingPathComponent:fileName]; NSError *error; NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error]; NSInteger fileSize = [[fileAttributes objectForKey:NSFileSize] intValue]; NSString *fileWithLength=[NSString stringWithFormat:@"%@-%d",fileName,fileSize]; [documentsDirectoryReferenceArray addObject:fileWithLength]; } return documentsDirectoryReferenceArray; } 




Well, for those who run their eyes on text and who are too lazy to understand, a similar approach has already been implemented in Cocoanetics / DTFoundation class DTFolderMonitor .



Also, from the experience of working with my application using iTunes File Sharing, I want to remind you of the need to launch a method like scanDocumentsDirectory , besides monitoring, as soon as the application becomes active. Its purpose is not only to check, but also to update the data about the files in the application with their actual presence in the directory, since the synchronization of files with iTunes can occur even while the application is in the background or is not started at all.



 - (void)applicationWillEnterForeground:(UIApplication *)application { [self scanDocumentsDirectory]; } 




Links to primary sources
Historical thread of discussions on the topic on the Apple Developer Forum (development account is required for access)

Directory Monitor - the old school monitor from Michael Heyeck

Directory Monitoring and GCD is the same, but updated

Monitoring a Folder with GCD solution from Socoanetics

Source: https://habr.com/ru/post/191868/



All Articles