---
title: Waiting for File Write Completion on iOS
teaser: 'It''s straight forward to watch a directory for file writes, but how can
  you be alerted when a file has finished being written?

  '
tags: ios,swift,til
author:
- Adam Sharp
- Sid Raval
published_on: 2019-01-24
---

During [investment time](https://thoughtbot.com/blog/investment-time),
I've been writing an [iOS client to `pass(1)`](https://github.com/sidraval/passGUI).
As part of the app's onboarding, the user is asked to upload, via iTunes, their
PGP private key to the application's documents directory.

I want the application to detect when this file is written to the device, then
read the file and add its content to the keychain. Apple has [helpfully
published](https://forums.developer.apple.com/thread/90531) some code relating
to watching for changes in the documents directory, which I happily
implemented... leading to a runtime crash of my application. It seems the file
was not ready for reading when I attempted to read it.

Here's a class with the problematic code:

```swift
import Foundation

fileprivate let queue = DispatchQueue(label: "FileMonitorQueue", target: .main)

final class FileWatcher {
  let fsSource: DispatchSourceFileSystemObject
  var readSource: DispatchSourceRead! // explained later

  init(in directory: URL, filename: String) {
    let fileDescriptor = open(directory.path, O_EVTONLY)

    fsSource = DispatchSource.makeFileSystemObjectSource(
      fileDescriptor: fileDescriptor,
      eventMask: .write,
      queue: queue
    )

    fsSource.setEventHandler {
      /* do whatever you like here */
    }

    fsSource.setCancelHandler {
      close(fileDescriptor)
    }

    fsSource.resume()
  }

  deinit {
    fsSource.cancel()
  }
}
```

(n.b. some error handling has been left out for brevity; see [this gist] for a
more complete version)

[this gist]: https://gist.github.com/sidraval/86f3de0b57472cea7b7ecfd957427ee5

The problem is that the blocked passed to `setEventHandler` fires as soon as
writing _begins_, not when the write completes.

To remedy that, we can create a [`DispatchSourceRead`] which has an event
handler that fires when a file becomes readable.

[`DispatchSourceRead`]: https://developer.apple.com/documentation/dispatch/dispatchsource/2300104-makereadsource

```swift
fsSource.setEventHandler {
  do {
    let fileHandle = try FileHandle(forReadingFrom: documentsDirectory.appendingPathComponent(filename))

    self.readSource = DispatchSource.makeReadSource(fileDescriptor: fileHandle.fileDescriptor, queue: queue)
    self.readSource.setEventHandler {
        fileHandle.readToEndOfFileInBackgroundAndNotify()
    }
    self.readSource.resume()

    self.readSource.setCancelHandler {
        fileHandle.closeFile()
    }
  } catch {
    /* error handling here */
  }
}
```

Now we can listen to `NSFileHandleReadToEndOfFileCompletion` using `NotificationCenter`:

```swift
import Foundation

final class FileListener {
  init() {
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(handler),
      name: .NSFileHandleReadToEndOfFileCompletion,
      object: nil
    )
  }

  @objc func handler() {
    /* file is available for reading here */
  }
}
```

And voila:

![](https://images.thoughtbot.com/blog-vellum-image-uploads/peltAf0bRGm8QeWDBFJO_file-detect.gif)
