UIFileSharingEnabled = YES
Но это даже не полдела. Соль в том, что, по-хорошему, приложение теперь должно остлеживать все изменения с файлами, происходящие в директории Documents и соответственно обновлять свои данные. Как это релизовать в своём коде и расскажет данная статья.
Для начала совсем немного теории из 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]; }
Directory Monitor – олдскульный монитор от Michael Heyeck
Directory Monitoring and GCD он же, но обновленный
Monitoring a Folder with GCD решение от Сocoanetics
ссылка на оригинал статьи http://habrahabr.ru/post/191868/
Добавить комментарий