9

How would you monitor a directory with NSFileManager?

I would like to able to detect when a file is deleted/added in my documents directory while my app is running.

Paulo Mattos
  • 18,845
  • 10
  • 77
  • 85
Gavy
  • 1,939
  • 1
  • 19
  • 20

2 Answers2

12

Here's my own version of DirectoryWatcher written in Swift using GCD instead of Mach and using a closure instead of a delegate

import Foundation

@objc public class DirectoryWatcher : NSObject {
    override public init() {
        super.init()
    }

    deinit {
        stop()
    }

    public typealias Callback = (_ directoryWatcher: DirectoryWatcher) -> Void

    @objc public convenience init(withPath path: String, callback: @escaping Callback) {
        self.init()
        if !watch(path: path, callback: callback) {
            assert(false)
        }
    }

    private var dirFD : Int32 = -1 {
        didSet {
            if oldValue != -1 {
                close(oldValue)
            }
        }
    }
    private var dispatchSource : DispatchSourceFileSystemObject?

    @objc public func watch(path: String, callback: @escaping Callback) -> Bool {
        // Open the directory
        dirFD = open(path, O_EVTONLY)
        if dirFD < 0 {
            return false
        }

        // Create and configure a DispatchSource to monitor it
        let dispatchSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: dirFD, eventMask: .write, queue: DispatchQueue.main)
        dispatchSource.setEventHandler {[unowned self] in
            callback(self)
        }
        dispatchSource.setCancelHandler {[unowned self] in
            self.dirFD = -1
        }
        self.dispatchSource = dispatchSource

        // Start monitoring
        dispatchSource.resume()

        // Success
        return true
    }

    @objc public func stop() {
        // Leave if not monitoring
        guard let dispatchSource = dispatchSource else {
            return
        }

        // Don't listen to more events
        dispatchSource.setEventHandler(handler: nil)

        // Cancel the source (this will also close the directory)
        dispatchSource.cancel()
        self.dispatchSource = nil
    }
}

Use it like Apple's DirectoryWatcher example, something like this:

let directoryWatcher = DirectoryWatcher(withPath: "/path/to/the/folder/you/want/to/monitor/", callback: {
    print("the folder changed")
})

Destroying the object will stop watching, or you can stop it explicitly

directoryWatcher.stop()

It should be compatible with Objective C they way it's written (untested). Using it would be like this:

DirectoryWatcher *directoryWatcher = [DirectoryWatcher.alloc initWithPath: @"/path/to/the/folder/you/want/to/monitor/" callback: ^(DirectoryWatcher *directoryWatcher) {
    NSLog(@"the folder changed")
}];

Stopping it is similar

[directoryWatcher stop];
John Stephen
  • 7,625
  • 2
  • 31
  • 45
  • Should seriously consider making this the primary answer, as the original answer has not aged well. Although the doc reference in the original is correct, the code referred to in the original is so old, it has all but disappeared (as per the comments). And the referred blog post actual utilizes direct run loop manipulation (a bit foreign in the age of Swift). – markgo2k Nov 20 '19 at 20:56
  • Nice solution, but does not track sub folders – KamyFC Oct 21 '20 at 13:14
  • @kamyFC That's true, but I don't know of any that do (Apple's DirectoryWatcher doesn't, does it?) – John Stephen Oct 24 '20 at 00:45
  • Does this work on Linux? – DrMickeyLauer Jan 16 '22 at 13:17
6

Look Kernel Queues: An Alternative to File System Events in Apple documentation. There is an example for iOS in AVPlayerDemo (look DirectoryWatcher class).

Also, check Directory Monitor blog post.

djromero
  • 19,551
  • 4
  • 71
  • 68
  • 2
    Apple has added official sample on DirectoryWatcher https://developer.apple.com/library/ios/samplecode/DocInteraction/Introduction/Intro.html – Roman Blachman Sep 04 '14 at 12:01
  • Apple moved the sample, here's the new link https://developer.apple.com/library/content/samplecode/AVPlayerDemo – John Stephen Apr 05 '18 at 18:25