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
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
import AsyncFileMonitor
// Monitor a directory
let eventStream = FolderContentMonitor.makeStream(url: URL(fileURLWithPath: "/path/to/monitor/"))
// Use async/await to process events
for await event in eventStream {
print("File changed: \(event.filename) at \(event.eventPath)")
print("Change type: \(event.change)")
}
import AsyncFileMonitor
// Create a stream with custom configuration
let eventStream = FolderContentMonitor.makeStream(
url: URL(fileURLWithPath: "/Users/you/Documents"),
latency: 0.5 // Coalesce rapid changes
)
// Process file events
for await event in 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)")
}
let eventStream = FolderContentMonitor.makeStream(paths: [
"/Users/you/Documents",
"/Users/you/Desktop"
])
for await event in eventStream {
print("Change in \(event.eventPath): \(event.change)")
}
let eventStream = FolderContentMonitor.makeStream(url: folderURL)
let monitorTask = Task {
for await event in eventStream {
// Process file events
await handleFileChange(event)
}
}
// Stop monitoring
monitorTask.cancel()
let eventStream = FolderContentMonitor.makeStream(url: documentsURL)
for await event in eventStream
where event.change.contains(.isFile) && event.change.contains(.modified) {
await processModifiedFile(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
let uiUpdateStream = FolderContentMonitor.makeStream(url: documentsURL)
let backupStream = FolderContentMonitor.makeStream(url: documentsURL)
let logStream = FolderContentMonitor.makeStream(url: documentsURL)
// Process events differently in each stream
Task {
for await event in uiUpdateStream {
await updateUI(for: event)
}
}
Task {
for await event in backupStream {
guard event.change.contains(.modified) else { continue }
await backupFile(event.url)
}
}
Task {
for await event in 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:
uiUpdateStream
receives the event firstbackupStream
receives the event secondlogStream
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.
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)
let eventStream = FolderContentMonitor.makeStream(url: url, latency: 0.0)
// 1-second latency - coalesces rapid changes
let eventStream = 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
let monitor = FolderContentMonitor(url: folderUrl)
let disposeBag = DisposeBag()
monitor.rx.folderContentChange
.subscribe(onNext: { event in
print("File changed: \(event.filename)")
})
.disposed(by: disposeBag)
import AsyncFileMonitor
let eventStream = FolderContentMonitor.makeStream(url: folderUrl)
for await event in 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 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
# 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
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.