《iOS 6高级开发手册(第4版)》——2.3节秘诀:监测Documents文件夹

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


《iOS 6高级开发手册(第4版)》——2.3节秘诀:监测Documents文件夹

在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
上一篇:基于libmad的MP3解码播放器


下一篇:《MySQL DBA修炼之道》——第1章 理解MySQL 1.1MySQL 介绍