How to check for End-of-File using NSFileHandle's readabilityHandler?
Asked Answered
A

3

13

I am reading data from a NSFileHandle (from a NSPipe) using a readabilityHandler block:

fileHandle.readabilityHandler = ^( NSFileHandle *handle ) {
     [self processData: [handle availableData]];
}

This works fine, I get all the data I expect fed to my processData method. The problem is that I need to know when the last chunk of data was read. availableData should return an empty NSData instance if it reached end-of-file, but the problem is that the reachability handler is not called again on EOF.

I can’t find anything about how to get some kind of notification or callback on EOF. So what am I missing? Is Apple really providing an asynchronous reading API without an EOF callback?

By the way, I cannot use the runloop based readInBackgroundAndNotify method since I don’t have a runloop available. If I cannot get this to work with the NSFileHandle API I probably will directly use a dispatch source to do the IO.

Authentic answered 6/4, 2013 at 13:12 Comment(2)
Are you sure all write ends of the pipe have been closed? Regarding run loops, all threads will create a run loop on demand and you can run one yourself.Eupatrid
Yes, the pipe is properly closed. I did some more experimenting and the same thing also happens with other kinds of NSFileHandle instances. When reading from standard input or a regular file there also is no notification for EOF.Authentic
M
0

I'm afraid you're out of luck doing this with NSFileHandle if you can't use readInBackgroundAndNotify.

Two solutions I see:

  1. Create a runloop and then use readInBackgroundAndNotify.
  2. Roll your own implementation using dispatch_io_*
Measurement answered 13/2, 2015 at 9:18 Comment(0)
C
3

I personally compare current file offset with current file position and stop reading.

extension FileHandle {
    func stopReadingIfPassedEOF() {
        let pos = offsetInFile
        let len = seekToEndOfFile()
        if pos < len {
            // Resume reading.
            seek(toFileOffset: pos)
        }
        else {
            // Stop.
            // File offset pointer stays at the EOF.
            readabilityHandler = nil
        }
    }
}

I couldn't understand why it's been designed in this way for a long time, but now I think this could be intentional.

In my opinion, Apple basically defines FileHandle as an infinite stream, therefore, EOF is not well defined unless you close the file. FileHandle seem to be more like a "channel".

It's also unclear what happens if another process appends/delete some data to/from the file while you're reading from it. What would be the EOF in this case? As far as I find, there's no mention about this case in Apple documentation. As far as I know, there's no true exclusive file I/O lock in macOS like other Unix-like systems.

In my opinion, availableData can return empty data at any time if I/O is not fast enough, and readabilityHandler just don't care about EOF.

Crystallography answered 29/9, 2018 at 3:56 Comment(0)
I
3

I believe the accepted answer is actually incorrect. The readabilityHandler is indeed called when EOF is reached. That is signaled by having availableData be of 0 size.

Here’s a simple playground that attests to this.

import Foundation
import PlaygroundSupport

let pipe = Pipe()
pipe.fileHandleForReading.readabilityHandler = { fh in 
    let d = fh.availableData
    print("Data length: \(d.count)")
    if (d.count == 0) {
        fh.readabilityHandler = nil
    }
}
pipe.fileHandleForWriting.write("Hello".data(using: .utf8)!)
pipe.fileHandleForWriting.closeFile()

PlaygroundPage.current.needsIndefiniteExecution = true
Inkster answered 22/2, 2020 at 21:12 Comment(1)
I experience the same behavior. This is how I've been detecting EOF and it has been working consistently.Rightward
M
0

I'm afraid you're out of luck doing this with NSFileHandle if you can't use readInBackgroundAndNotify.

Two solutions I see:

  1. Create a runloop and then use readInBackgroundAndNotify.
  2. Roll your own implementation using dispatch_io_*
Measurement answered 13/2, 2015 at 9:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.