- PVSM.RU - https://www.pvsm.ru -
Думаю, что большинству разработчиков под iOS известно как легко включить iTunes File Sharing в своем приложении, добавив лишь одну строчку в Info.plist:
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 [1] класс DTFolderMonitor
.
Еще, из опыта работы над своим приложением [2], использующем iTunes File Sharing, хочу напомнить о необходимости запуска метода типа scanDocumentDerectory
, помимо мониторинга, как только приложение становится активным. Его назначение не только проверять, но и обновлять данные о файлах в приложении с их фактическим наличием в директории, так как синхронизация файлов с iTunes может происходить и в момент, когда приложение находится в бэкграунде, либо совсем не запущено.
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[self scanDocumentsDirectory];
}
Автор: aplekhanov
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/monitoring/42043
Ссылки в тексте:
[1] Cocoanetics/DTFoundation: https://github.com/Cocoanetics/DTFoundation
[2] своим приложением: http://pdfboxapp.com
[3] Историческая ветка обсуждений по теме на форуме разработчиков Apple: https://devforums.apple.com/thread/44497?start=0&tstart=0
[4] Directory Monitor: http://www.mlsite.net/blog/?p=2312
[5] Directory Monitoring and GCD: http://www.mlsite.net/blog/?p=2655
[6] Monitoring a Folder with GCD: https://www.cocoanetics.com/2013/08/monitoring-a-folder-with-gcd/
[7] Источник: http://habrahabr.ru/post/191868/
Нажмите здесь для печати.