Skip to main content
replaced http://stackoverflow.com/ with https://stackoverflow.com/
Source Link
URL Rewriter Bot
URL Rewriter Bot

The following Swift code is heavily inspired by the various answers to How to read data from NSFileHandle line by line?How to read data from NSFileHandle line by line?. It reads from the file in chunks, and converts complete lines to strings.

The following Swift code is heavily inspired by the various answers to How to read data from NSFileHandle line by line?. It reads from the file in chunks, and converts complete lines to strings.

The following Swift code is heavily inspired by the various answers to How to read data from NSFileHandle line by line?. It reads from the file in chunks, and converts complete lines to strings.

Swift 3 update
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k

(The code is for Swift 2.2/Xcode 7.3 now. Older versions can be found in the edit history if somebody needs it. An updated version for Swift 3 is provided at the end.)

extension StreamReader : SequenceType { func generate() -> AnyGenerator<String> { return AnyGenerator { return self.nextLine() } } } 

Update for Swift 3/Xcode 8 beta 6: Also "modernized" to use guard and the new Data value type:

class StreamReader { let encoding : String.Encoding let chunkSize : Int var fileHandle : FileHandle! let delimData : Data var buffer : Data var atEof : Bool init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8, chunkSize: Int = 4096) { guard let fileHandle = FileHandle(forReadingAtPath: path), let delimData = delimiter.data(using: encoding) else { return nil } self.encoding = encoding self.chunkSize = chunkSize self.fileHandle = fileHandle self.delimData = delimData self.buffer = Data(capacity: chunkSize) self.atEof = false } deinit { self.close() } /// Return next line, or nil on EOF. func nextLine() -> String? { precondition(fileHandle != nil, "Attempt to read from closed file") // Read data chunks from file until a line delimiter is found: while !atEof { if let range = buffer.range(of: delimData) { // Convert complete line (excluding the delimiter) to a string: let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding) // Remove line (and the delimiter) from the buffer: buffer.removeSubrange(0..<range.upperBound) return line } let tmpData = fileHandle.readData(ofLength: chunkSize) if tmpData.count > 0 { buffer.append(tmpData) } else { // EOF or read error. atEof = true if buffer.count > 0 { // Buffer contains last line in file (not terminated by delimiter). let line = String(data: buffer as Data, encoding: encoding) buffer.count = 0 return line } } } return nil } /// Start reading from the beginning of file. func rewind() -> Void { fileHandle.seek(toFileOffset: 0) buffer.count = 0 atEof = false } /// Close the underlying file. No reading must be done after calling this method. func close() -> Void { fileHandle?.closeFile() fileHandle = nil } } extension StreamReader : Sequence { func makeIterator() -> AnyIterator<String> { return AnyIterator { return self.nextLine() } } } 

(The code is for Swift 2.2/Xcode 7.3 now. Older versions can be found in the edit history if somebody needs it.)

extension StreamReader : SequenceType { func generate() -> AnyGenerator<String> { return AnyGenerator { return self.nextLine() } } } 

(The code is for Swift 2.2/Xcode 7.3 now. Older versions can be found in the edit history if somebody needs it. An updated version for Swift 3 is provided at the end.)

extension StreamReader : SequenceType { func generate() -> AnyGenerator<String> { return AnyGenerator { return self.nextLine() } } } 

Update for Swift 3/Xcode 8 beta 6: Also "modernized" to use guard and the new Data value type:

class StreamReader { let encoding : String.Encoding let chunkSize : Int var fileHandle : FileHandle! let delimData : Data var buffer : Data var atEof : Bool init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8, chunkSize: Int = 4096) { guard let fileHandle = FileHandle(forReadingAtPath: path), let delimData = delimiter.data(using: encoding) else { return nil } self.encoding = encoding self.chunkSize = chunkSize self.fileHandle = fileHandle self.delimData = delimData self.buffer = Data(capacity: chunkSize) self.atEof = false } deinit { self.close() } /// Return next line, or nil on EOF. func nextLine() -> String? { precondition(fileHandle != nil, "Attempt to read from closed file") // Read data chunks from file until a line delimiter is found: while !atEof { if let range = buffer.range(of: delimData) { // Convert complete line (excluding the delimiter) to a string: let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding) // Remove line (and the delimiter) from the buffer: buffer.removeSubrange(0..<range.upperBound) return line } let tmpData = fileHandle.readData(ofLength: chunkSize) if tmpData.count > 0 { buffer.append(tmpData) } else { // EOF or read error. atEof = true if buffer.count > 0 { // Buffer contains last line in file (not terminated by delimiter). let line = String(data: buffer as Data, encoding: encoding) buffer.count = 0 return line } } } return nil } /// Start reading from the beginning of file. func rewind() -> Void { fileHandle.seek(toFileOffset: 0) buffer.count = 0 atEof = false } /// Close the underlying file. No reading must be done after calling this method. func close() -> Void { fileHandle?.closeFile() fileHandle = nil } } extension StreamReader : Sequence { func makeIterator() -> AnyIterator<String> { return AnyIterator { return self.nextLine() } } } 
Swift 2.2 update.
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k

(The code is for Swift 2.2/Xcode 7.3 now. Older versions can be found in the edit history if somebody needs it.)

extension StreamReader : SequenceType { func generate() -> AnyGenerator<String> { return anyGeneratorAnyGenerator { return self.nextLine() } } } 

(The code is for Swift 2/Xcode 7 now. Older versions can be found in the edit history if somebody needs it.)

extension StreamReader : SequenceType { func generate() -> AnyGenerator<String> { return anyGenerator { return self.nextLine() } } } 

(The code is for Swift 2.2/Xcode 7.3 now. Older versions can be found in the edit history if somebody needs it.)

extension StreamReader : SequenceType { func generate() -> AnyGenerator<String> { return AnyGenerator { return self.nextLine() } } } 
Removed all pre-Swift 2 code.
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading
Rollback to Revision 9
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading
litle fix because xcode blame with the nil argument.
Source Link
Rodrigo
  • 12.8k
  • 23
  • 71
  • 101
Loading
Make `tmpData` a constant (feedback @5happy1).
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading
Update for Swift 2
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading
Updated for Swift 1.2/Xcode 6.3.
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading
Improved init method. Simplified NSData options to `nil`
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading
Make the close method work again with failable initializer.
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading
Updated from beta versions of Swift to use failable initializers
Source Link
Loading
Bounty Awarded with 50 reputation awarded by Matt
added rewind, explicit close, read last line without terminator, for-in-loop compatibility
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading
more configurable parameters
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading
Source Link
Martin R
  • 541.4k
  • 98
  • 1.3k
  • 1.4k
Loading