本节书摘来自异步社区《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