美文网首页MacOS开发 技术集锦MAC os开发
使用 FSEventStream 监控 Mac 文件变化

使用 FSEventStream 监控 Mac 文件变化

作者: yww | 来源:发表于2017-07-26 10:55 被阅读792次

    前言

    之前写了一个 Mac 下的文件同步客户端, 需要监控本地的文件变化并同步上去. 几经波折, 最有使用的是FSEventStream 来实现文件监控

    简介

    FSEventStream 是一套 C 语言的方法, 类似于 CoreGraphic.
    由于是 C 语言方法, 建议使用 Objective-C 来编写代码 ,用 swift 会涉及到大量类型转换
    用到的函数有下面几个:

    • FSEventStreamCreate
      创建一个文件监控句柄, 可以在这个函数中绑定一个函数回调
    • FSEventStreamRetain
    • FSEventStreamRelease
      FSEventStream不支持 ARC, 必须手动 retain 和 release
    • FSEventStreamScheduleWithRunLoop
      加入到一个 runloop 中, 才可以实现文件的监控
    • FSEventStreamStart
      启动文件监控
    • FSEventStreamStop
      停止文件监控
    • FSEventStreamInvalidate
      从 runloop 中移除

    整体流程就是创建文件监控句柄, 加入到 runloop, 最后启动就可以了
    停止的时候, 首先调用 stop, 然后 从 runloop 中移除, 最后使用 release 释放

    FSEventStreamCreate

    这个函数负责创建一个文件监控句柄, 函数声明如下

    extern FSEventStreamRef __nullable
    FSEventStreamCreate(
      CFAllocatorRef __nullable  allocator,
      FSEventStreamCallback      callback,
      FSEventStreamContext * __nullable context,
      CFArrayRef                 pathsToWatch,
      FSEventStreamEventId       sinceWhen,
      CFTimeInterval             latency,
      FSEventStreamCreateFlags   flags)  
    

    第一个参数 allocator 传入 NULL 就可以了;
    第二个参数 callback 为事件回调, 当监控的文件夹发生事件之后, 会出发回调函数;
    第三参数context, 由于回调函数是 C 函数, 无法直接使用 self , 如果需要使用 self, 可以利用这个参数将 self 传进去.

    FSEventStreamContext context;
    context.info = (__bridge void * _Nullable)(self);
    context.version = 0;
    context.retain = NULL;
    context.release = NULL;
    context.copyDescription = NULL;
    

    第四个参数 pathsToWatch 为需要监控的文件夹路径数组, 也就是说你可以同时监控多个文件夹.
    第五个参数 sinceWhen 很有用, 可以指定从哪个事件开始监控, 如果是第一次监控, 那么可以设置为kFSEventStreamEventIdSinceNow, 表示从现在开始监控, 后面如果发生事件之后, 回调函数中会传入最新的事件 id, 可以将这个存下来, 以后就以这个事件 id 作为起点. 这样可以做到即使你程序关闭了, 你也不会漏掉事件
    第六个参数 latency 是监控的时间间隔, 可以指定多少秒之后监控一次
    最后一个参数 flags 用于配置文件监控, 具体可以参考文档, 我一般使用kFSEventStreamCreateFlagFileEvents 和 kFSEventStreamCreateFlagUseCFTypes
    前者可以将事件细分到具体的文件, 后者则是方便回调函数使用
    完整的函数调用示例如下

    FSEventStreamContext context;
    context.info = (__bridge void * _Nullable)(self);
    context.version = 0;
    context.retain = NULL;
    context.release = NULL;
    context.copyDescription = NULL;
    self.syncEventStream = FSEventStreamCreate(NULL, &fsevents_callback, &context, (__bridge CFArrayRef _Nonnull)(paths), self.syncEventID, self.syncInterval, kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagUseCFTypes);
    FSEventStreamScheduleWithRunLoop(self.syncEventStream, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
    FSEventStreamStart(self.syncEventStream);
    
    

    回调函数

    所有事件都会在回调函数中响应, 函数声明为

    typedef CALLBACK_API_C( void , FSEventStreamCallback )(ConstFSEventStreamRef streamRef, void * __nullable clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);
    

    我们可以创建一个如下的函数用于接收事件

    void fsevents_callback(ConstFSEventStreamRef streamRef,
                           void *userData,
                           size_t numEvents,
                           void *eventPaths,
                           const FSEventStreamEventFlags eventFlags[],
                           const FSEventStreamEventId eventIds[]) {
      //code here
    }
    

    同样, 来看看参数列表
    第一个参数 streamRef 就是create 时创建的句柄
    第二个参数 userData 则是之前 context 中的 info, 我们之前传入了 self 进来, 那么 userData 就是 self 了
    第三个参数是 eventPaths , 是一个数组, 内容是发生事件的文件路径. 默认情况下是一个 C 语言的数组, 不过我们可以在 create 的时候使用 kFSEventStreamCreateFlagUseCFTypes, 让其变为 CFArray
    第四个参数 numEvents 为事件的个数
    第五个参数eventFlags 是发生的事件, 注意这里有坑.
    最后一个是每一个事件的事件 id
    我们主要需要关注的参数就是 eventPaths 和 eventFlags, eventPaths 没什么好说的, 就是文件的路径.
    eventFlags 则是事件类型. 可以用按位与获取具体的事件

    if( eventFlags[0] & kFSEventStreamEventFlagItemCreated) {
      // 发生了文件创建事件
    }
    

    但是坑来了, 如果你想监控文件移动, 那么你会想移动的事件应该是 xxxMoved, 实际上呢, 文件移动发生的事件是 kFSEventStreamEventFlagItemRenamed .
    不过想想也对, 文件移动和重命名在命令行都是 mv 命令.
    kFSEventStreamEventFlagItemRemoved, 这个事件, 你想通过他监控文件删除, 没问题, 不过当你在 Finder 中去删除一个文件到回收站, 你会发现还是一个 rename 事件, 只有用命令行直接删除文件才是 remove 事件.
    另外, 移动和重命名的事件都是成对的, 也就是说移动或是重命名一个文件同时会发生两个事件, 都是 rename 事件, 这两个时间的事件 id 也是一样的.

    写了一个 demo, 可以略作参考https://github.com/ywwzwb/FSEventStreamDemo

    相关文章

      网友评论

        本文标题:使用 FSEventStream 监控 Mac 文件变化

        本文链接:https://www.haomeiwen.com/subject/hmdnkxtx.html