Skip to content

Repository files navigation

AsyncFileMonitor

SwiftPlatformSwift Package Manager

Modern async/await Swift Package for monitoring file system events using CoreFoundation's FSEvents API.

AsyncFileMonitor is the modernized successor to RxFileMonitor, providing the same powerful file monitoring capabilities with Swift 6 concurrency support and no external dependencies.

Features

  • Modern Async/await: Uses AsyncStream for natural async/await integration
  • Swift 6 Ready: Full concurrency support with Sendable conformance
  • FSEvents Integration: Efficient file system monitoring using Apple's native FSEvents API
  • Flexible Monitoring: Monitor single files, directories, or multiple paths
  • Event Filtering: Rich event information with detailed change flags

Usage

Basic Usage

import AsyncFileMonitor
// Monitor a directory
leteventStream=FolderContentMonitor.makeStream(url:URL(fileURLWithPath:"/path/to/monitor/"))
// Use async/await to process events
forawaiteventin eventStream {print("File changed: \(event.filename) at \(event.eventPath)")print("Change type: \(event.change)")}

Advanced Configuration

import AsyncFileMonitor
// Create a stream with custom configuration
leteventStream=FolderContentMonitor.makeStream(
url:URL(fileURLWithPath:"/Users/you/Documents"),
latency:0.5 // Coalesce rapid changes
)
// Process file events
forawaiteventin eventStream {
// Filter for file changes only
guard event.change.contains(.isFile)else{continue}
// Skip system files
guard event.filename !=".DS_Store"else{continue}print("Document changed: \(event.filename)")}

Monitoring Multiple Paths

leteventStream=FolderContentMonitor.makeStream(paths:["/Users/you/Documents","/Users/you/Desktop"])forawaiteventin eventStream {print("Change in \(event.eventPath): \(event.change)")}

Task-based Processing

leteventStream=FolderContentMonitor.makeStream(url: folderURL)letmonitorTask=Task{forawaiteventin eventStream {
// Process file events
awaithandleFileChange(event)}}
// Stop monitoring
monitorTask.cancel()

Filtering Events

leteventStream=FolderContentMonitor.makeStream(url: documentsURL)forawaiteventin eventStream
where event.change.contains(.isFile) && event.change.contains(.modified){awaitprocessModifiedFile(event.url)}

Multiple Concurrent Streams

AsyncFileMonitor uses a multicast AsyncStream approach where multiple streams from the same monitor share a single FSEventStream and receive identical events in registration order:

// Create multiple independent streams monitoring the same directory
letuiUpdateStream=FolderContentMonitor.makeStream(url: documentsURL)letbackupStream=FolderContentMonitor.makeStream(url: documentsURL)letlogStream=FolderContentMonitor.makeStream(url: documentsURL)
// Process events differently in each stream
Task{forawaiteventin uiUpdateStream {awaitupdateUI(for: event)}}Task{forawaiteventin backupStream {guard event.change.contains(.modified)else{continue}awaitbackupFile(event.url)}}Task{forawaiteventin logStream {
logger.info("File changed: \(event.filename)")}}

Ordering Guarantee: Events are delivered to subscribers in registration order. In the example above, for each file system event:

  1. uiUpdateStream receives the event first
  2. backupStream receives the event second
  3. logStream receives the event third

You should probably not rely on the kind of things like subscription order, but I figured it's better you know just in case that you run into concurrency-related issues in your app, than having to guess.

Event Types

The Change struct provides detailed information about what changed:

File Type Flags

  • .isFile - The item is a regular file
  • .isDirectory - The item is a directory
  • .isSymlink - The item is a symbolic link
  • .isHardlink - The item is a hard link

Change Type Flags

  • .created - Item was created
  • .modified - Item was modified
  • .removed - Item was removed
  • .renamed - Item was renamed/moved

Metadata Changes

  • .changeOwner - Ownership changed
  • .finderInfoModified - Finder info changed
  • .inodeMetaModified - Inode metadata changed
  • .xattrsModified - Extended attributes changed

Latency Configuration

Control event coalescing with the latency parameter:

// No latency - all events reported immediately (can be noisy)
leteventStream=FolderContentMonitor.makeStream(url: url, latency:0.0)
// 1-second latency - coalesces rapid changes
leteventStream=FolderContentMonitor.makeStream(url: url, latency:1.0)

A latency of 0.0 can produce too much noise when applications make multiple rapid changes to files. Experiment with slightly higher values (e.g., 0.1-1.0 seconds) to reduce noise.

Understanding File Events

Different applications can generate different event patterns:

TextEdit (atomic saves):

texteditfile.txt changed (isFile, renamed, xattrsModified)
texteditfile.txt changed (isFile, renamed, finderInfoModified, xattrsModified)
texteditfile.txt.sb-56afa5c6-DmdqsL changed (isFile, renamed)
texteditfile.txt changed (isFile, renamed, finderInfoModified, inodeMetaModified, xattrsModified)
texteditfile.txt.sb-56afa5c6-DmdqsL changed (isFile, modified, removed, renamed, changeOwner)

Simple editors (direct writes):

file.txt changed (isFile, modified, xattrsModified)

Installation

Swift Package Manager

Add AsyncFileMonitor to your Package.swift:

dependencies:[.package(url:"https://github.com/yourusername/AsyncFileMonitor.git", from:"1.0.0")]

Or add it through Xcode:

  1. File > Add Package Dependencies
  2. Enter the repository URL
  3. Select your target

Requirements

  • macOS 14.0+
  • Swift 6.0+
  • Xcode 16.0+

Migration from RxFileMonitor

AsyncFileMonitor provides the same core functionality as RxFileMonitor but with modern Swift concurrency:

Before (RxFileMonitor)

import RxFileMonitor
import RxSwift
letmonitor=FolderContentMonitor(url: folderUrl)letdisposeBag=DisposeBag()
monitor.rx.folderContentChange
.subscribe(onNext:{ event inprint("File changed: \(event.filename)")}).disposed(by: disposeBag)

After (AsyncFileMonitor)

import AsyncFileMonitor
leteventStream=FolderContentMonitor.makeStream(url: folderUrl)forawaiteventin eventStream {print("File changed: \(event.filename)")}

Demo -- Command Line Tool

AsyncFileMonitor includes a built-in CLI tool for monitoring file changes to demo the capabilities:

# Monitor a single directory
swift run watch /Users/username/Documents
# Monitor multiple directories
swift run watch /path/to/folder1 /path/to/folder2
# Show usage help
swift run watch

Example Output:

🎯 Starting AsyncFileMonitor CLI
📁 Monitoring paths:
• /Users/username/Documents
📡 Press Ctrl+C to stop monitoring
[14:23:15.123] 📄 /Users/username/Documents/test.txt
🔄 isFile, modified
🆔 Event ID: 12345678
[14:23:15.456] 📄 /Users/username/Documents/newfile.txt
🔄 isFile, created
🆔 Event ID: 12345679

Building and Testing

# Build
make build
# Build and run CLI tool
swift run watch /path/to/monitor
# Generate documentation
make docs
# Preview documentation in browser
make docs-preview
# Generate static documentation website
make docs-static
# Run tests 
make test# Format code
make format
# Clean
make clean

License

Copyright (c) 2016 Christian Tietze, RxSwiftCommunity (original RxFileMonitor)
Copyright (c) 2025 Christian Tietze (AsyncFileMonitor modernization)

Distributed under The MIT License. See LICENSE file for details.

About

Swift Concurrency wrapper for monitoring file system events using FSEvents

Resources

Stars

47 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages