Tag Archives: FSEvents

Developing a FileSystemWatcher for OS X by using FSEvents with Swift 2.0

When Swift was announced at WWDC 2014, I was very disappointed to not be able to use some of powerful C APIs such as FSEvents, to write applications in pure Swift language.

A lot of C APIs needs a C function pointer to pass a callback function when you use the API. From Swift 1.0 to Swift 1.2, it was not possible to use C function pointer in pure Swift language.

It was possible to use FSEvents by wrapping it in an Objective-C class, and by calling this class in Swift by using the bridging features offered by the language, but it was not what I attempted to accomplish.

But things change and at WWDC 2015, Apple announced the possibility to use C function pointers with Swift 2.0.

Today I’m very pleased to give you a sample code which is a good starting point to create your own FileSystemWatcher using the FSEvents API written with Swift 2.0.

import Foundation

public class FileSystemWatcher {

    // MARK: - Initialization / Deinitialization
    
    public init(pathsToWatch: [String], sinceWhen: FSEventStreamEventId) {
        self.lastEventId = sinceWhen
        self.pathsToWatch = pathsToWatch
    }

    convenience public init(pathsToWatch: [String]) {
        self.init(pathsToWatch: pathsToWatch, sinceWhen: FSEventStreamEventId(kFSEventStreamEventIdSinceNow))
    }
    
    deinit {
        stop()
    }

    // MARK: - Private Properties
    
    private let eventCallback: FSEventStreamCallback = { (stream: ConstFSEventStreamRef, contextInfo: UnsafeMutablePointer<Void>, numEvents: Int, eventPaths: UnsafeMutablePointer<Void>, eventFlags: UnsafePointer<FSEventStreamEventFlags>, eventIds: UnsafePointer<FSEventStreamEventId>) in
        print("***** FSEventCallback Fired *****", appendNewline: true)
        
        let fileSystemWatcher: FileSystemWatcher = unsafeBitCast(contextInfo, FileSystemWatcher.self)
        let paths = unsafeBitCast(eventPaths, NSArray.self) as! [String]
        
        for index in 0..<numEvents {
            fileSystemWatcher.processEvent(eventIds[index], eventPath: paths[index], eventFlags: eventFlags[index])
        }
        
        fileSystemWatcher.lastEventId = eventIds[numEvents - 1]
    }
    private let pathsToWatch: [String]
    private var started = false
    private var streamRef: FSEventStreamRef!
    
    // MARK: - Private Methods
    
    private func processEvent(eventId: FSEventStreamEventId, eventPath: String, eventFlags: FSEventStreamEventFlags) {
        print("\t\(eventId) - \(eventFlags) - \(eventPath)", appendNewline: true)
    }
    
    // MARK: - Pubic Properties
    
    public private(set) var lastEventId: FSEventStreamEventId
    
    // MARK: - Pubic Methods
    
    public func start() {
        guard started == false else { return }
        
        var context = FSEventStreamContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        context.info = UnsafeMutablePointer<Void>(unsafeAddressOf(self))
        let flags = UInt32(kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents)
        streamRef = FSEventStreamCreate(kCFAllocatorDefault, eventCallback, &context, pathsToWatch, lastEventId, 0, flags)
        
        FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetMain(), kCFRunLoopDefaultMode)
        FSEventStreamStart(streamRef)
        
        started = true
    }
    
    public func stop() {
        guard started == true else { return }

        FSEventStreamStop(streamRef)
        FSEventStreamInvalidate(streamRef)
        FSEventStreamRelease(streamRef)
        streamRef = nil

        started = false
    }
    
}

I hope this will help you in your future applications and feel free to share this article if you found it helpful 😉