本节书摘来自异步社区《iOS 6高级开发手册(第4版)》一书中的第2章,第2.3节秘诀:监测Documents文件夹,作者 【美】Erica Sadun,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.3 秘诀:监测Documents文件夹iOS 6高级开发手册(第4版)iOS文档并没有受困在它们的沙盒中,你可以并且应该与用户共享它们。应该允许用户直接控制他们的文档,以及访问他们可能在设备上创建的任何资料。一个简单的Info.plist设置将使iTunes能够显示用户的Documents文件夹的内容,并使那些用户能够根据需要添加和删除资料。
在将来某个时间,你可能使用一个简单的NSMetadataQuery监测器来监视Documents文件夹并报告更新。在编写本书时,元数据监视还没有扩展到iCloud之外以用于其他的文件夹。从OS X导出的代码无法像期望的那样在iOS上工作。目前,准确地讲,有两个搜索域可供iOS使用:即普遍存在的数据范围和普遍存在的文档范围(即iCloud和iCloud)。
直到iOS中出现了一般的功能之后,才能使用kqueue。这种老式技术提供了可伸缩的事件通知。利用kqueue,可以监测添加和清除事件。这粗略地等同于寻找要添加和删除的文件,它们是你想做出反应的主要更新类型。秘诀2-3展示了一个用于监视Documents文件夹的kqueue实现。
2.3.1 支持文档文件共享要支持文件共享,可以向应用程序的Info.plist中添加一个UIFileSharingEnabled键,并把它的值设置为YES,如图2-2所示。在处理非原始的键和值时,这个项目被称为支持iTunes文件共享的Application。iTunes将在每个设备的Apps选项卡中列出所有声明文件共享支持的应用程序,如图2-3所示。
在iTunes中,将在设备的Apps选项卡中列出每个安装的声明了UIFileSharingEnabled的应用程序
2.3.2 用户控制不能指定在Documents文件夹中允许存放哪些类型的项目。用户可以添加他们喜欢的任何项目,以及删除他们希望删除的任何项目。不过,他们不能做的是使用iTunes界面导航子文件夹。注意图2-3中的Inbox文件夹,这是一个从应用程序之间的文档共享中遗留下来的工件,但它不应该出现在那里。用户不能直接管理数据,不应该把子文件夹留在那里以使他们混淆。
用户在iTunes中不能像删除其他文件和文件夹那样删除Inbox,应用程序应该也不能直接把文件写到Inbox中。尊重Inbox的角色,它用于捕获从其他应用程序传入的任何数据。在实现文件共享支持时,总是要检查Inbox以恢复活动状态,并且处理该数据以清空Inbox,以及无论何时应用程序启动和恢复运行时都要删除它。在本章后面将讨论处理传入的文档的最佳实践。
2.3.3 Xcode访问作为一位开发人员,你不仅能够访问Documents文件夹,而且能够访问整个应用程序沙盒。使用Xcode Organizer (Command-2) > Devices选项卡>“设备”> Applications >“应用程序名称”可以浏览沙盒,以及从中上传和下载文件。
通过启用应用程序的UIFileSharingEnabled属性,可以测试基本的文件共享,以及把数据加载到Documents文件夹中。在创建了那些文件之后,可以使用Xcode和iTunes检查、下载和删除它们。
2.3.4 扫描新文档秘诀2-3通过在其beginGeneratingDocumentNotificationsInPath:方法中请求kqueue通知来工作。在这里,它获取一个用于你所提供的路径(在这里是Documents文件夹)的文件描述符,并请求用于添加和清除事件的通知。它将把这个功能添加到当前的运行循环中,无论何时监测的文件夹更新,都会启用通知。
一旦接收到那个回调,它将发布一条通知(我自定义的kDocumentChanged,在kqueueFired方法中),并且继承监视新事件。在主线程上的主运行循环中都会运行它,因此一旦接收到通知,GUI就可以响应并更新它自身。
下面的代码段演示了如何使用秘诀2-3的监视器来更新GUI中的文件列表。无论何时内容改变了,更新通知都允许应用程序刷新那些目录内容清单:
- (void) scanDocuments { NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil]; [self.tableView reloadData]; } - (void) loadView { [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"]; [self scanDocuments]; // React to content changes [[NSNotificationCenter defaultCenter] addObserverForName:kDocumentChanged object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification){ [self scanDocuments]; }]; // Start the watcher NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]; helper = [DocWatchHelper watcherForPath:path]; }把设备连接到iTunes,测试这个秘诀。使用iTunes App选项卡界面添加和删除项目。设备的机载文件列表将会更新,以实时反映那些改变。
在使用这个秘诀时,要知道一些警告。首先,对于较大的文档,在收到了创建它们的通知之后,不应该立即阅读它们。你可能希望调查文件大小,以确定何时应该停止写入数据。第二,iTunes File Sharing在必要时可以暂缓传输,要相应地进行编码。
秘诀2-3 使用kqueue文件监测器
#import <fcntl.h> #import <sys/event.h> #define kDocumentChanged \ @"DocumentsFolderContentsDidChangeNotification" @interface DocWatchHelper : NSObject { CFFileDescriptorRef kqref; CFRunLoopSourceRef rls; } @property (strong) NSString *path; + (id) watcherForPath: (NSString *) aPath; @end @implementation DocWatchHelper @synthesize path; - (void)kqueueFired { int kq; struct kevent event; struct timespec timeout = { 0, 0 }; int eventCount; kq = CFFileDescriptorGetNativeDescriptor(self->kqref); assert(kq >= 0); eventCount = kevent(kq, NULL, 0, &event, 1, &timeout); assert( (eventCount >= 0) && (eventCount < 2) ); if (eventCount == 1) [[NSNotificationCenter defaultCenter] postNotificationName:kDocumentChanged object:self]; CFFileDescriptorEnableCallBacks(self->kqref, kCFFileDescriptorReadCallBack); } static void KQCallback(CFFileDescriptorRef kqRef, CFOptionFlags callBackTypes, void *info) { DocWatchHelper *helper = (DocWatchHelper *)(__bridge id)(CFTypeRef) info; [helper kqueueFired]; } - (void) beginGeneratingDocumentNotificationsInPath: (NSString *) docPath { int dirFD; int kq; int retVal; struct kevent eventToAdd; CFFileDescriptorContext context = { 0, (void *)(__bridge CFTypeRef) self, NULL, NULL, NULL }; dirFD = open([docPath fileSystemRepresentation], O_EVTONLY); assert(dirFD >= 0); kq = kqueue(); assert(kq >= 0); eventToAdd.ident = dirFD; eventToAdd.filter = EVFILT_VNODE; eventToAdd.flags = EV_ADD | EV_CLEAR; eventToAdd.fflags = NOTE_WRITE; eventToAdd.data = 0; eventToAdd.udata = NULL; retVal = kevent(kq, &eventToAdd, 1, NULL, 0, NULL); assert(retVal == 0); self->kqref = CFFileDescriptorCreate(NULL, kq, true, KQCallback, &context); rls = CFFileDescriptorCreateRunLoopSource( NULL, self->kqref, 0); assert(rls != NULL); CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFRelease(rls); CFFileDescriptorEnableCallBacks(self->kqref, kCFFileDescriptorReadCallBack); } - (void) dealloc { self.path = nil; CFRunLoopRemoveSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); CFFileDescriptorDisableCallBacks(self->kqref, kCFFileDescriptorReadCallBack); } + (id) watcherForPath: (NSString *) aPath { DocWatchHelper *watcher = [[self alloc] init]; watcher.path = aPath; [watcher beginGeneratingDocumentNotificationsInPath:aPath]; return watcher; } @end