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.
- Modern Async/await: Uses
AsyncStreamfor natural async/await integration - Swift 6 Ready: Full concurrency support with
Sendableconformance - 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
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)")}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)")}leteventStream=FolderContentMonitor.makeStream(paths:["/Users/you/Documents","/Users/you/Desktop"])forawaiteventin eventStream {print("Change in \(event.eventPath): \(event.change)")}leteventStream=FolderContentMonitor.makeStream(url: folderURL)letmonitorTask=Task{forawaiteventin eventStream {
// Process file events
awaithandleFileChange(event)}}
// Stop monitoring
monitorTask.cancel()leteventStream=FolderContentMonitor.makeStream(url: documentsURL)forawaiteventin eventStream
where event.change.contains(.isFile) && event.change.contains(.modified){awaitprocessModifiedFile(event.url)}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:
uiUpdateStreamreceives the event firstbackupStreamreceives the event secondlogStreamreceives 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.
The Change struct provides detailed information about what changed:
.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
.created- Item was created.modified- Item was modified.removed- Item was removed.renamed- Item was renamed/moved
.changeOwner- Ownership changed.finderInfoModified- Finder info changed.inodeMetaModified- Inode metadata changed.xattrsModified- Extended attributes changed
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.
Different applications can generate different event patterns:
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)
file.txt changed (isFile, modified, xattrsModified)
Add AsyncFileMonitor to your Package.swift:
dependencies:[.package(url:"https://github.com/yourusername/AsyncFileMonitor.git", from:"1.0.0")]Or add it through Xcode:
- File > Add Package Dependencies
- Enter the repository URL
- Select your target
- macOS 14.0+
- Swift 6.0+
- Xcode 16.0+
AsyncFileMonitor provides the same core functionality as RxFileMonitor but with modern Swift concurrency:
import RxFileMonitor
import RxSwift
letmonitor=FolderContentMonitor(url: folderUrl)letdisposeBag=DisposeBag()
monitor.rx.folderContentChange
.subscribe(onNext:{ event inprint("File changed: \(event.filename)")}).disposed(by: disposeBag)import AsyncFileMonitor
leteventStream=FolderContentMonitor.makeStream(url: folderUrl)forawaiteventin eventStream {print("File changed: \(event.filename)")}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 watchExample 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
# 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 cleanCopyright (c) 2016 Christian Tietze, RxSwiftCommunity (original RxFileMonitor)
Copyright (c) 2025 Christian Tietze (AsyncFileMonitor modernization)
Distributed under The MIT License. See LICENSE file for details.