使用 FSEventStream 监控 Mac 文件变化

前言

之前写了一个 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容