webentwicklung-frage-antwort-db.com.de

Lesen Sie eine Datei/URL Zeile für Zeile in Swift

Ich versuche, eine in NSURL angegebene Datei zu lesen und in ein Array zu laden, wobei die Elemente durch ein Zeilenvorschubzeichen \n getrennt sind. 

So habe ich es bisher gemacht:

var possList: NSString? = NSString.stringWithContentsOfURL(filePath.URL) as? NSString
if var list = possList {
    list = list.componentsSeparatedByString("\n") as NSString[]
    return list
}
else {
    //return empty list
}

Ich bin aus einigen Gründen nicht sehr glücklich damit. Erstens arbeite ich mit Dateien, deren Größe von wenigen Kilobytes bis zu Hunderten von MB reicht. Wie Sie sich vorstellen können, ist das Arbeiten mit so großen Saiten langsam und unhandlich. Zweitens friert dies die Benutzeroberfläche ein, wenn sie ausgeführt wird - wiederum nicht gut.

Ich habe versucht, diesen Code in einem separaten Thread auszuführen, aber ich hatte Probleme damit, und außerdem löst er immer noch nicht das Problem, mit riesigen Strings umzugehen.

Was ich gerne machen würde, ist etwas im Sinne des folgenden Pseudocodes:

var aStreamReader = new StreamReader(from_file_or_url)
while aStreamReader.hasNextLine == true {
    currentline = aStreamReader.nextLine()
    list.addItem(currentline)
}

Wie würde ich das in Swift erreichen?

Einige Anmerkungen zu den Dateien, aus denen ich gerade lese: Alle Dateien bestehen aus kurzen Zeichenfolgen (<255 Zeichen), die entweder durch \n oder \r\n getrennt sind. Die Länge der Dateien reicht von ~ 100 Zeilen bis zu über 50 Millionen Zeilen. Sie können europäische Zeichen und/oder Zeichen mit Akzenten enthalten.

66
Matt

(Der Code ist jetzt für Swift 2.2/Xcode 7.3. Ältere Versionen befinden sich im Bearbeitungsverlauf, wenn jemand sie benötigt. Am Ende wird eine aktualisierte Version für Swift 3 bereitgestellt.)

Der folgende Swift-Code ist stark von den verschiedenen Antworten auf inspiriert. Wie liest man Daten aus NSFileHandle Zeile für Zeile? . Es liest in Stücken aus der Datei und konvertiert vollständige Zeilen in Strings.

Das Standardzeilentrennzeichen (\n), die Zeichenfolgencodierung (UTF-8) und die Blockgröße (4096) Können mit optionalen Parametern festgelegt werden. 

class StreamReader  {

    let encoding : UInt
    let chunkSize : Int

    var fileHandle : NSFileHandle!
    let buffer : NSMutableData!
    let delimData : NSData!
    var atEof : Bool = false

    init?(path: String, delimiter: String = "\n", encoding : UInt = NSUTF8StringEncoding, chunkSize : Int = 4096) {
        self.chunkSize = chunkSize
        self.encoding = encoding

        if let fileHandle = NSFileHandle(forReadingAtPath: path),
            delimData = delimiter.dataUsingEncoding(encoding),
            buffer = NSMutableData(capacity: chunkSize)
        {
            self.fileHandle = fileHandle
            self.delimData = delimData
            self.buffer = buffer
        } else {
            self.fileHandle = nil
            self.delimData = nil
            self.buffer = nil
            return nil
        }
    }

    deinit {
        self.close()
    }

    /// Return next line, or nil on EOF.
    func nextLine() -> String? {
        precondition(fileHandle != nil, "Attempt to read from closed file")

        if atEof {
            return nil
        }

        // Read data chunks from file until a line delimiter is found:
        var range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
        while range.location == NSNotFound {
            let tmpData = fileHandle.readDataOfLength(chunkSize)
            if tmpData.length == 0 {
                // EOF or read error.
                atEof = true
                if buffer.length > 0 {
                    // Buffer contains last line in file (not terminated by delimiter).
                    let line = NSString(data: buffer, encoding: encoding)

                    buffer.length = 0
                    return line as String?
                }
                // No more lines.
                return nil
            }
            buffer.appendData(tmpData)
            range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
        }

        // Convert complete line (excluding the delimiter) to a string:
        let line = NSString(data: buffer.subdataWithRange(NSMakeRange(0, range.location)),
            encoding: encoding)
        // Remove line (and the delimiter) from the buffer:
        buffer.replaceBytesInRange(NSMakeRange(0, range.location + range.length), withBytes: nil, length: 0)

        return line as String?
    }

    /// Start reading from the beginning of file.
    func rewind() -> Void {
        fileHandle.seekToFileOffset(0)
        buffer.length = 0
        atEof = false
    }

    /// Close the underlying file. No reading must be done after calling this method.
    func close() -> Void {
        fileHandle?.closeFile()
        fileHandle = nil
    }
}

Verwendungszweck:

if let aStreamReader = StreamReader(path: "/path/to/file") {
    defer {
        aStreamReader.close()
    }
    while let line = aStreamReader.nextLine() {
        print(line)
    }
}

Sie können den Reader sogar mit einer For-in-Schleife verwenden

for line in aStreamReader {
    print(line)
}

durch Implementierung des SequenceType-Protokolls (vergleiche http://robots.thoughtbot.com/Swift-sequences ):

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

Update für Swift 3/Xcode 8 beta 6: Auch "modernisiert" auf Use_guard und den neuen Werttyp Data:

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()
        }
    }
}
134
Martin R

Ich habe den Code von Algens Antwort in eine praktische Klasse (Swift 4.0) verpackt.

UPD: Dieser Code ist plattformunabhängig (macOS, iOS, ubuntu)

import Foundation

/// Read text file line by line
public class LineReader {
    public let path: String

    fileprivate let file: UnsafeMutablePointer<FILE>!

    init?(path: String) {
        self.path = path
        file = fopen(path, "r")
        guard file != nil else { return nil }
    }

    public var nextLine: String? {
        var line:UnsafeMutablePointer<CChar>? = nil
        var linecap:Int = 0
        defer { free(line) }
        return getline(&line, &linecap, file) > 0 ? String(cString: line!) : nil
    }

    deinit {
        fclose(file)
    }
}

extension LineReader: Sequence {
    public func  makeIterator() -> AnyIterator<String> {
        return AnyIterator<String> {
            return self.nextLine
        }
    }
}

Verwendungszweck:

guard let reader = LineReader(path: "/Path/to/file.txt") else {
    return; // cannot open file
}

for line in reader {
    print(">" + line.trimmingCharacters(in: .whitespacesAndNewlines))      
}

Repository auf github

18
Andy C

Ich bin spät dran, aber hier ist eine kleine Klasse, die ich zu diesem Zweck geschrieben habe. Nach einigen Versuchen (versuchen Sie, die Unterklasse NSInputStream zu unterteilen), habe ich festgestellt, dass dies ein vernünftiger und einfacher Ansatz ist.

Denken Sie an #import <stdio.h> in Ihrem Bridging-Header.

// Use is like this:
let readLine = ReadLine(somePath)
while let line = readLine.readLine() {
    // do something...
}

class ReadLine {

    private var buf = UnsafeMutablePointer<Int8>.alloc(1024)
    private var n: Int = 1024

    let path: String
    let mode: String = "r"

    private lazy var filepointer: UnsafeMutablePointer<FILE> = {
        let csmode = self.mode.withCString { cs in return cs }
        let cspath = self.path.withCString { cs in return cs }

        return fopen(cspath, csmode)
    }()

    init(path: String) {
        self.path = path
    }

    func readline() -> String? {
        // unsafe for unknown input
        if getline(&buf, &n, filepointer) > 0 {
            return String.fromCString(UnsafePointer<CChar>(buf))
        }

        return nil
    }

    deinit {
        buf.dealloc(n)
        fclose(filepointer)
    }
}
4
Albin Stigo

Es stellt sich heraus, dass sich die gute altbekannte C-API in Swift ziemlich wohl fühlt, sobald Sie UnsafePointer grokken. Hier ist eine einfache Katze, die von stdin liest und Zeile für Zeile auf stdout druckt. Sie brauchen nicht einmal eine Stiftung. Darwin reicht aus:

import Darwin
let bufsize = 4096
// let stdin = fdopen(STDIN_FILENO, "r") it is now predefined in Darwin
var buf = UnsafePointer<Int8>.alloc(bufsize)
while fgets(buf, Int32(bufsize-1), stdin) {
    print(String.fromCString(CString(buf)))
}
buf.destroy()
2
dankogai

Diese Funktion nimmt einen Dateistream und gibt eine AnyGenerator zurück, die jede Zeile der Datei zurückgibt:

func lineGenerator(file:UnsafeMutablePointer<FILE>) -> AnyGenerator<String>
{
  return AnyGenerator { () -> String? in
    var line:UnsafeMutablePointer<CChar> = nil
    var linecap:Int = 0
    defer { free(line) }
    return getline(&line, &linecap, file) > 0 ? String.fromCString(line) : nil
  }
}

So würden Sie zum Beispiel jede Zeile einer Datei mit dem Namen "foo" in Ihrem App-Bundle drucken:

let path = NSBundle.mainBundle().pathForResource("foo", ofType: nil)!
let file = fopen(path,"r") // open the file stream
for line in lineGenerator(file) {
  // suppress print's automatically inserted line ending, since
  // lineGenerator captures each line's own new line character.
  print(line, separator: "", terminator: "")
}
fclose(file) // cleanup the file stream

Ich habe diese Antwort entwickelt, indem ich die Antwort von Alex Brown modifizierte, um ein von Martin Rs Kommentar erwähntes Speicherleck zu entfernen, und es mit Swift 2.2 (Xcode 7.3) aktualisiert.

2
algal

Swift 4.2 Sichere Syntax

class LineReader {

    let path: String

    init?(path: String) {
        self.path = path
        guard let file = fopen(path, "r") else {
            return nil
        }
        self.file = file
    }
    deinit {
        fclose(file)
    }

    var nextLine: String? {
        var line: UnsafeMutablePointer<CChar>?
        var linecap = 0
        defer {
            free(line)
        }
        let status = getline(&line, &linecap, file)
        guard status > 0, let unwrappedLine = line else {
            return nil
        }
        return String(cString: unwrappedLine)
    }

    private let file: UnsafeMutablePointer<FILE>
}

extension LineReader: Sequence {
    func makeIterator() -> AnyIterator<String> {
        return AnyIterator<String> {
            return self.nextLine
        }
    }
}

Verwendungszweck:

guard let reader = LineReader(path: "/Path/to/file.txt") else {
    return
}
reader.forEach { line in
    print(line.trimmingCharacters(in: .whitespacesAndNewlines))      
}
1
Vyacheslav

(Hinweis: Ich verwende Swift 3.0.1 unter Xcode 8.2.1 mit macOS Sierra 10.12.3.)

Alle Antworten, die ich hier gesehen habe, haben versäumt, dass er nach LF oder CRLF suchen könnte. Wenn alles gut geht, könnte er/sie einfach auf LF passen und die zurückgegebene Zeichenfolge am Ende auf eine zusätzliche CR überprüfen. Die allgemeine Abfrage umfasst jedoch mehrere Suchzeichenfolgen. Mit anderen Worten muss das Trennzeichen ein Set<String> sein, wobei die Menge weder leer ist noch die leere Zeichenfolge enthält, anstatt einer einzelnen Zeichenfolge.

Bei meinem ersten Versuch im letzten Jahr habe ich versucht, "das Richtige" zu tun und nach allgemeinen Saiten zu suchen. Es war zu schwer; Sie benötigen einen voll ausgebauten Parser und Zustandsautomaten und dergleichen. Ich habe es aufgegeben und das Projekt war Teil davon.

Jetzt mache ich das Projekt wieder und stehe vor der gleichen Herausforderung. Jetzt werde ich hart auf CR und LF suchen. Ich glaube nicht, dass irgendjemand außerhalb von CR/LF-Parsing nach zwei semi-unabhängigen und semi-abhängigen Zeichen suchen muss.

Ich verwende die Suchmethoden, die von Data bereitgestellt werden, daher mache ich hier keine String-Kodierungen und so. Nur rohe binäre Verarbeitung. Nehmen Sie einfach an, ich habe hier eine ASCII - Superset wie ISO Latin-1 oder UTF-8. Sie können die Zeichenfolgencodierung auf der nächsthöheren Ebene abwickeln. Sie legen fest, ob ein CR/LF mit angehängten sekundären Codepunkten noch als CR oder LF zählt.

Der Algorithmus: Suchen Sie einfach nach dem nächsten CR und dem nächsten LF von Ihrem aktuellen Byte-Offset.

  • Wenn keine gefunden wird, dann betrachten Sie die nächste Datenzeichenfolge als vom aktuellen Offset bis zum Datenende. Beachten Sie, dass die Abschlusslänge 0 ist. Markieren Sie dies als das Ende Ihrer Leseschleife.
  • Wenn zuerst ein LF oder nur ein LF gefunden wird, betrachten Sie den nächsten Datenstring als vom aktuellen Offset zum LF. Beachten Sie, dass die Abschlusslänge 1 ist. Verschieben Sie den Versatz nach nach LF.
  • Wenn nur ein CR gefunden wird, verwenden Sie den Fall LF (nur mit einem anderen Byte-Wert).
  • Ansonsten bekamen wir eine CR gefolgt von einer LF .
    • Wenn die beiden nebeneinander liegen, verfahren Sie wie im Fall LF, nur dass die Abschlusslänge 2 ist.
    • Wenn sich ein Byte zwischen ihnen befindet und das besagte Byte auch CR ist, dann haben wir den "Windows-Entwickler haben im Textmodus eine Binärdatei geschrieben, wodurch sich ein Problem ergibt." Behandeln Sie es auch wie den Fall LF, nur dass die Abschlusslänge 3 ist.
    • Ansonsten sind CR und LF nicht miteinander verbunden und werden wie der Fall "Just-CR" behandelt.

Hier ist ein Code dafür:

struct DataInternetLineIterator: IteratorProtocol {

    /// Descriptor of the location of a line
    typealias LineLocation = (offset: Int, length: Int, terminatorLength: Int)

    /// Carriage return.
    static let cr: UInt8 = 13
    /// Carriage return as data.
    static let crData = Data(repeating: cr, count: 1)
    /// Line feed.
    static let lf: UInt8 = 10
    /// Line feed as data.
    static let lfData = Data(repeating: lf, count: 1)

    /// The data to traverse.
    let data: Data
    /// The byte offset to search from for the next line.
    private var lineStartOffset: Int = 0

    /// Initialize with the data to read over.
    init(data: Data) {
        self.data = data
    }

    mutating func next() -> LineLocation? {
        guard self.data.count - self.lineStartOffset > 0 else { return nil }

        let nextCR = self.data.range(of: DataInternetLineIterator.crData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
        let nextLF = self.data.range(of: DataInternetLineIterator.lfData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
        var location: LineLocation = (self.lineStartOffset, -self.lineStartOffset, 0)
        let lineEndOffset: Int
        switch (nextCR, nextLF) {
        case (nil, nil):
            lineEndOffset = self.data.count
        case (nil, let offsetLf):
            lineEndOffset = offsetLf!
            location.terminatorLength = 1
        case (let offsetCr, nil):
            lineEndOffset = offsetCr!
            location.terminatorLength = 1
        default:
            lineEndOffset = min(nextLF!, nextCR!)
            if nextLF! < nextCR! {
                location.terminatorLength = 1
            } else {
                switch nextLF! - nextCR! {
                case 2 where self.data[nextCR! + 1] == DataInternetLineIterator.cr:
                    location.terminatorLength += 1  // CR-CRLF
                    fallthrough
                case 1:
                    location.terminatorLength += 1  // CRLF
                    fallthrough
                default:
                    location.terminatorLength += 1  // CR-only
                }
            }
        }
        self.lineStartOffset = lineEndOffset + location.terminatorLength
        location.length += self.lineStartOffset
        return location
    }

}

Wenn Sie einen Data-Block mit einer Länge von mindestens einem signifikanten Bruchteil eines Gigabytes haben, werden Sie natürlich einen Treffer erhalten, wenn im aktuellen Byte-Offset kein CR oder LF mehr vorhanden ist. Bei jeder Iteration immer bis zum Schluss fruchtlos suchen. Das Lesen der Daten in Brocken würde helfen:

struct DataBlockIterator: IteratorProtocol {

    /// The data to traverse.
    let data: Data
    /// The offset into the data to read the next block from.
    private(set) var blockOffset = 0
    /// The number of bytes remaining.  Kept so the last block is the right size if it's short.
    private(set) var bytesRemaining: Int
    /// The size of each block (except possibly the last).
    let blockSize: Int

    /// Initialize with the data to read over and the chunk size.
    init(data: Data, blockSize: Int) {
        precondition(blockSize > 0)

        self.data = data
        self.bytesRemaining = data.count
        self.blockSize = blockSize
    }

    mutating func next() -> Data? {
        guard bytesRemaining > 0 else { return nil }
        defer { blockOffset += blockSize ; bytesRemaining -= blockSize }

        return data.subdata(in: blockOffset..<(blockOffset + min(bytesRemaining, blockSize)))
    }

}

Sie müssen diese Ideen selbst mischen, da ich sie noch nicht gemacht habe. Erwägen:

  • Natürlich müssen Sie Zeilen berücksichtigen, die vollständig in einem Block enthalten sind.
  • Sie müssen jedoch damit umgehen, wenn sich die Enden einer Linie in benachbarten Brocken befinden.
  • Oder wenn sich auf den Endpunkten mindestens ein Stück befindet
  • Die große Komplikation ist, wenn die Zeile mit einer Multibyte-Sequenz endet, die jedoch zwei Blöcke umfasst! (Eine Zeile, die in nur CR endet und auch das letzte Byte im Block ist, ist ein gleichwertiger Fall, da Sie den nächsten Block lesen müssen, um zu sehen, ob Ihre Just-CR tatsächlich eine CRLF oder CR-CRLF ist Chunk endet mit CR-CR.)
  • Sie müssen damit umgehen, wenn der aktuelle Offset keine Abschlusszeichen mehr enthält, das Datenende sich jedoch in einem späteren Block befindet.

Viel Glück!

1
CTMacUser

Versuchen Sie this answer oder lesen Sie die Mac OS Stream-Programmieranleitung

Möglicherweise stellen Sie jedoch fest, dass die Leistung mit stringWithContentsOfURL tatsächlich besser ist, da es schneller ist, mit speicherbasierten (oder speicherzuordnenden) Daten zu arbeiten, als mit datenträgerbasierten.

Die Ausführung in einem anderen Thread ist gut dokumentiert, auch zum Beispiel hier .

Aktualisieren

Wenn Sie nicht alles auf einmal lesen möchten und NSStreams nicht verwenden möchten, müssen Sie wahrscheinlich Datei-E/A auf C-Ebene verwenden. Es gibt viele Gründe, dies nicht zu tun - Blockieren, Zeichenkodierung, Behandlung von E/A-Fehlern, Geschwindigkeit, um nur einige zu nennen - dafür sind die Foundation-Bibliotheken da. Ich habe unten eine einfache Antwort entworfen, die sich nur mit ACSII-Daten befasst:

class StreamReader {

    var eofReached = false
    let fileHandle: UnsafePointer<FILE>

    init (path: String) {
        self.fileHandle = fopen(path.bridgeToObjectiveC().UTF8String, "rb".bridgeToObjectiveC().UTF8String)
    }

    deinit {
        fclose(self.fileHandle)
    }

    func nextLine() -> String {
        var nextChar: UInt8 = 0
        var stringSoFar = ""
        var eolReached = false
        while (self.eofReached == false) && (eolReached == false) {
            if fread(&nextChar, 1, 1, self.fileHandle) == 1 {
                switch nextChar & 0xFF {
                case 13, 10 : // CR, LF
                    eolReached = true
                case 0...127 : // Keep it in ASCII
                    stringSoFar += NSString(bytes:&nextChar, length:1, encoding: NSASCIIStringEncoding)
                default :
                    stringSoFar += "<\(nextChar)>"
                }
            } else { // EOF or error
                self.eofReached = true
            }
        }
        return stringSoFar
    }
}

// OP's original request follows:
var aStreamReader = StreamReader(path: "~/Desktop/Test.text".stringByStandardizingPath)

while aStreamReader.eofReached == false { // Changed property name for more accurate meaning
    let currentline = aStreamReader.nextLine()
    //list.addItem(currentline)
    println(currentline)
}
1
Grimxn

Oder Sie können einfach eine Generator verwenden:

let stdinByLine = GeneratorOf({ () -> String? in
    var input = UnsafeMutablePointer<Int8>(), lim = 0
    return getline(&input, &lim, stdin) > 0 ? String.fromCString(input) : nil
})

Lass es uns ausprobieren

for line in stdinByLine {
    println(">>> \(line)")
}

Es ist einfach, faul und einfach mit anderen Swift-Dingen wie Aufzählern und Funktoren, wie z. B. Karten, Reduzieren, Filtern, zu ketten. mit dem Wrapper lazy().


Es verallgemeinert alle FILE als:

let byLine = { (file:UnsafeMutablePointer<FILE>) in
    GeneratorOf({ () -> String? in
        var input = UnsafeMutablePointer<Int8>(), lim = 0
        return getline(&input, &lim, file) > 0 ? String.fromCString(input) : nil
    })
}

genannt wie

for line in byLine(stdin) { ... }
0
Alex Brown

Ich wollte eine Version, die nicht ständig den Puffer oder den Code dupliziert, da beide ineffizient sind und Puffer jeder Größe (einschließlich 1 Byte) und jedes Trennzeichen zulassen. Es gibt eine öffentliche Methode: readline(). Beim Aufruf dieser Methode wird der String-Wert der nächsten Zeile oder Null bei EOF zurückgegeben.

import Foundation

// LineStream(): path: String, [buffSize: Int], [delim: String] -> nil | String
// ============= --------------------------------------------------------------
// path:     the path to a text file to be parsed
// buffSize: an optional buffer size, (1...); default is 4096
// delim:    an optional delimiter String; default is "\n"
// ***************************************************************************
class LineStream {
    let path: String
    let handle: NSFileHandle!

    let delim: NSData!
    let encoding: NSStringEncoding

    var buffer = NSData()
    var buffSize: Int

    var buffIndex = 0
    var buffEndIndex = 0

    init?(path: String,
      buffSize: Int = 4096,
      delim: String = "\n",
      encoding: NSStringEncoding = NSUTF8StringEncoding)
    {
      self.handle = NSFileHandle(forReadingAtPath: path)
      self.path = path
      self.buffSize = buffSize < 1 ? 1 : buffSize
      self.encoding = encoding
      self.delim = delim.dataUsingEncoding(encoding)
      if handle == nil || self.delim == nil {
        print("ERROR initializing LineStream") /* TODO use STDERR */
        return nil
      }
    }

  // PRIVATE
  // fillBuffer(): _ -> Int [0...buffSize]
  // ============= -------- ..............
  // Fill the buffer with new data; return with the buffer size, or zero
  // upon reaching end-of-file
  // *********************************************************************
  private func fillBuffer() -> Int {
    buffer = handle.readDataOfLength(buffSize)
    buffIndex = 0
    buffEndIndex = buffer.length

    return buffEndIndex
  }

  // PRIVATE
  // delimLocation(): _ -> Int? nil | [1...buffSize]
  // ================ --------- ....................
  // Search the remaining buffer for a delimiter; return with the location
  // of a delimiter in the buffer, or nil if one is not found.
  // ***********************************************************************
  private func delimLocation() -> Int? {
    let searchRange = NSMakeRange(buffIndex, buffEndIndex - buffIndex)
    let rangeToDelim = buffer.rangeOfData(delim,
                                          options: [], range: searchRange)
    return rangeToDelim.location == NSNotFound
        ? nil
        : rangeToDelim.location
  }

  // PRIVATE
  // dataStrValue(): NSData -> String ("" | String)
  // =============== ---------------- .............
  // Attempt to convert data into a String value using the supplied encoding; 
  // return the String value or empty string if the conversion fails.
  // ***********************************************************************
    private func dataStrValue(data: NSData) -> String? {
      if let strVal = NSString(data: data, encoding: encoding) as? String {
          return strVal
      } else { return "" }
}

  // PUBLIC
  // readLine(): _ -> String? nil | String
  // =========== ____________ ............
  // Read the next line of the file, i.e., up to the next delimiter or end-of-
  // file, whichever occurs first; return the String value of the data found, 
  // or nil upon reaching end-of-file.
  // *************************************************************************
  func readLine() -> String? {
    guard let line = NSMutableData(capacity: buffSize) else {
        print("ERROR setting line")
        exit(EXIT_FAILURE)
    }

    // Loop until a delimiter is found, or end-of-file is reached
    var delimFound = false
    while !delimFound {
        // buffIndex will equal buffEndIndex in three situations, resulting
        // in a (re)filling of the buffer:
        //   1. Upon the initial call;
        //   2. If a search for a delimiter has failed
        //   3. If a delimiter is found at the end of the buffer
        if buffIndex == buffEndIndex {
            if fillBuffer() == 0 {
                return nil
            }
        }

        var lengthToDelim: Int
        let startIndex = buffIndex

        // Find a length of data to place into the line buffer to be
        // returned; reset buffIndex
        if let delim = delimLocation() {
            // SOME VALUE when a delimiter is found; append that amount of
            // data onto the line buffer,and then return the line buffer
            delimFound = true
            lengthToDelim = delim - buffIndex
            buffIndex = delim + 1   // will trigger a refill if at the end
                                    // of the buffer on the next call, but
                                    // first the line will be returned
        } else {
            // NIL if no delimiter left in the buffer; append the rest of
            // the buffer onto the line buffer, refill the buffer, and
            // continue looking
            lengthToDelim = buffEndIndex - buffIndex
            buffIndex = buffEndIndex    // will trigger a refill of buffer
                                        // on the next loop
        }

        line.appendData(buffer.subdataWithRange(
            NSMakeRange(startIndex, lengthToDelim)))
    }

    return dataStrValue(line)
  }
}

Es heißt wie folgt:

guard let myStream = LineStream(path: "/path/to/file.txt")
else { exit(EXIT_FAILURE) }

while let s = myStream.readLine() {
  print(s)
}
0
Pinecone