Мониторинг файлов с помощью GCD

от автора

Думаю, что большинству разработчиков под iOS известно как легко включить iTunes File Sharing в своем приложении, добавив лишь одну строчку в Info.plist:

UIFileSharingEnabled = YES

Но это даже не полдела. Соль в том, что, по-хорошему, приложение теперь должно остлеживать все изменения с файлами, происходящие в директории Documents и соответственно обновлять свои данные. Как это релизовать в своём коде и расскажет данная статья.
image

Для начала совсем немного теории из Concurrency Programming Guide. В GCD есть такое понятие как dispatch source – фундаментальный тип данных, который ответчает за координацию оброботки специфических низкоуровневых событий. Для решения нашей задачи, нас более всего интересует такая его разновидность как descriptor sources, оповещающий о различных операциях с файлами или сокетами.

Получается, что нам нужно методом dispatch_source_create создать диспетчер событий, источником которых послужит дескриптор файлов (directory file descriptor), а по самому событию (запись файла) отработает необходимый блок обновления данных приложения.

Итак, ближе к телу. Создадим два основных метода startMonitor и stopMonitor, соответственно запускающих и останавлювающи мониторинг нужной нам директории, а также пару-тройку вспомогательных методов проверки изменений в этой директории, которые будут запускаться через 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 scanDocumentsDerectory];     } }  -(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; } 

Ну, а для тех кто пробежался глазами по тектсу и кому лень разбираться, подобный подход уже реализован в Cocoanetics/DTFoundation класс DTFolderMonitor.

Еще, из опыта работы над своим приложением, использующем iTunes File Sharing, хочу напомнить о необходимости запуска метода типа scanDocumentDerectory, помимо мониторинга, как только приложение становится активным. Его назначение не только проверять, но и обновлять данные о файлах в приложении с их фактическим наличием в директории, так как синхронизация файлов с iTunes может происходить и в момент, когда приложение находится в бэкграунде, либо совсем не запущено.

 - (void)applicationWillEnterForeground:(UIApplication *)application {     [self scanDocumentsDirectory]; }
Ссылки на первоисточники

Историческая ветка обсуждений по теме на форуме разработчиков Apple (необходим девелоперский акаунт для доступа)
Directory Monitor – олдскульный монитор от Michael Heyeck
Directory Monitoring and GCD он же, но обновленный
Monitoring a Folder with GCD решение от Сocoanetics

ссылка на оригинал статьи http://habrahabr.ru/post/191868/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *