Lire un fichier/URL ligne par ligne dans Swift
j'essaie de lire un fichier donné dans un NSURL
et de le charger dans un tableau, avec des éléments séparés par un caractère newline n
.
Voici comment je l'ai fait jusqu'à présent:
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
}
Je ne suis pas très heureux avec cela pour quelques raisons. Premièrement, je travaille avec des fichiers dont la taille varie de quelques kilooctets à des centaines de Mo. Comme vous pouvez l'imaginer, travailler avec des chaînes cette grande est lent et lourd. Deuxièmement, ce gèle L'interface utilisateur quand il exécute -- encore une fois, pas bon.
j'ai cherché à lancer ce code dans un thread séparé, mais j'ai eu des problèmes avec cela, et de plus, il ne résout toujours pas le problème de traiter d'énormes cordes.
ce que j'aimerais faire est quelque chose du genre du pseudo suivant:
var aStreamReader = new StreamReader(from_file_or_url)
while aStreamReader.hasNextLine == true {
currentline = aStreamReader.nextLine()
list.addItem(currentline)
}
comment accomplir ceci dans Swift?
quelques notes sur les fichiers que je lis: tous les fichiers se composent de chaînes courtes (<255 caractères) séparées par n
ou rn
. La longueur des fichiers varie de ~100 lignes à plus de 50 millions de lignes. Ils peuvent contenir des caractères européens et / ou des caractères accentués.
9 réponses
(le code est pour Swift 2.2/Xcode 7.3 maintenant. Les versions plus anciennes peuvent être trouvées dans l'historique d'édition si quelqu'un en a besoin. Une version mise à jour de Swift 3 est fournie à la fin.)
le code Swift suivant est fortement inspiré par les différentes réponses Comment lire les données de NSFileHandle ligne par ligne? . Il lit le fichier en morceaux et convertit les lignes complètes en chaînes.
le délimiteur de ligne par défaut ( \n
), encodage de la chaîne (UTF-8) et taille du morceau (4096)
peut être configuré avec des paramètres optionnels.
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
}
}
Utilisation:
if let aStreamReader = StreamReader(path: "/path/to/file") {
defer {
aStreamReader.close()
}
while let line = aStreamReader.nextLine() {
print(line)
}
}
vous pouvez même utiliser le lecteur avec une boucle for-in
for line in aStreamReader {
print(line)
}
par la mise en œuvre du protocole SequenceType
(comparer http://robots.thoughtbot.com/swift-sequences ):
extension StreamReader : SequenceType {
func generate() -> AnyGenerator<String> {
return AnyGenerator {
return self.nextLine()
}
}
}
mise à jour pour Swift 3/Xcode 8 beta 6: également "modernisé" pour
utilisez guard
et le nouveau type de valeur 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()
}
}
}
j'ai emballé le code de la réponse d'algal dans une classe commode (Swift 4.0)
UPD: ce code est indépendant de la Plateforme (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
}
}
}
Utilisation:
guard let reader = LineReader(path: "/Path/to/file.txt") else {
return; // cannot open file
}
for line in reader {
print(">" + line.trimmingCharacters(in: .whitespacesAndNewlines))
}
je suis en retard pour le match, mais voici la petite classe que j'ai écrite pour ce but. Après plusieurs tentatives (essayer de classer NSInputStream
), j'ai trouvé cette approche raisonnable et simple.
rappelez-vous de #import <stdio.h>
dans votre tête de pont.
// 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)
}
}
il s'avère que la bonne vieille API C est assez confortable dans Swift une fois que vous grok UnsafePointer. Voici un chat simple qui lit de stdin et imprime à stdout ligne par ligne. Vous n'avez même pas besoin de Fondation. Darwin suffices:
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()
cette fonction prend un flux de fichier et retourne un AnyGenerator
qui retourne chaque ligne du fichier:
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
}
}
alors par exemple, voici comment vous l'utiliseriez pour imprimer chaque ligne d'un fichier nommé "foo" dans votre pack d'applications:
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
j'ai développé cette réponse en modifiant la réponse D'Alex Brown pour supprimer une fuite de mémoire mentionnée par le commentaire de Martin R, et en la mettant à jour pour travailler avec Swift 2.2 (Xcode 7.3).
Essayer ce réponse, ou de lire le Mac OS Flux " Guide de Programmation .
vous pouvez trouver que la performance sera en fait mieux en utilisant le stringWithContentsOfURL
, cependant, car il sera plus rapide de travailler avec des données basées sur la mémoire (ou la mémoire cartographiée) que les données basées sur disque.
L'exécuter sur un autre thread est bien documenté, aussi, par exemple ici .
mise à Jour
si vous ne voulez pas lire tout à la fois, et vous ne voulez pas utiliser Nsstream, alors vous devrez probablement utiliser le fichier C-level I/O. Il y a beaucoup raisons de ne pas faire cela - blocage, codage de caractères, traitement des erreurs d'e/s, vitesse à nommer mais quelques - unes-c'est ce à quoi servent les bibliothèques de base. J'ai esquissé ci-dessous une réponse simple qui ne traite que des données ACSII:
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)
}
(Note: J'utilise Swift 3.0.1 sur Xcode 8.2.1 avec macOS Sierra 10.12.3)
toutes les réponses que j'ai vu ici manquées qu'il pourrait être à la recherche de LF ou de CRLF. Si tout va bien, il / elle pourrait simplement correspondre sur LF et vérifier la chaîne retournée pour un CR supplémentaire à la fin. Mais la requête générale implique plusieurs chaînes de recherche. En d'autres termes, le délimiteur doit être un Set<String>
, où l'ensemble n'est ni vide, ni contient la chaîne vide, au lieu d'un chaîne unique.
lors de mon premier essai cette dernière année, j'ai essayé de faire la" bonne chose " et de chercher un ensemble général de cordes. C'était trop dur; il faut un analyseur complet et des machines d'état et autres. J'ai renoncé, et le projet c'était de la partie.
maintenant je fais le projet à nouveau, et faire face au même défi à nouveau. Maintenant, je vais chercher en code dur sur CR et LF. Je ne pense pas que quiconque aurait besoin de chercher sur deux semi-indépendants et des caractères semi-dépendants comme celui-ci en dehors de L'analyse CR/LF.
j'utilise les méthodes de recherche fournies par Data
, donc je ne fais pas des encodages de chaîne de caractères et des trucs ici. Juste un traitement binaire brut. Supposons que j'ai un super-ensemble ASCII, comme ISO Latin-1 ou UTF-8, ici. Vous pouvez gérer l'encodage des chaînes de caractères au niveau suivant-supérieur, et vous déterminez si un CR/LF avec des points de code secondaires compte encore comme un CR ou un LF.
l'algorithme: juste continuez à chercher le prochain CR et le prochain LF de votre offset byte courant.
- si aucun des deux n'est trouvé, alors considérer la chaîne de données suivante à partir de l'offset courant à la fin des données. Notez que la longueur du terminator est 0. Marquez ceci comme la fin de votre boucle de lecture.
- si un LF est trouvé en premier, ou seulement un LF est trouvé, considérez que la chaîne de données suivante est à partir du décalage courant vers le LF. Notez que l' la longueur du terminator est de 1. Déplacez l'offset après le LF.
- si seulement un CR est trouvé, faire comme le cas LF (juste avec une valeur d'octet différente).
- sinon, on a un CR suivi d'un LF.
- si les deux sont adjacents, alors manipuler comme le cas LF, sauf la longueur terminator sera 2.
- S'il y a un byte entre eux, et dit byte est aussi CR, alors nous avons obtenu le " Windows developer a écrit un binaire \r\n en mode texte, donnant un problème \r\n". Traitez - le aussi comme le cas LF, sauf que la longueur du terminator sera de 3.
- sinon le CR et le LF ne sont pas connectés, et gérer comme le cas just-CR.
voici un code pour ça:
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
}
}
bien sûr, si vous avez un Data
blocs d'une longueur qui est au moins une fraction significative d'un gigaoctet, vous allez en prendre un coup chaque fois QU'il n'y a plus de CR ou de LF à partir de l'offset byte courant; toujours chercher sans succès jusqu'à la fin à chaque itération. Lire les données en morceaux aiderait:
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)))
}
}
vous devez mélanger ces idées vous-même, puisque je ne l'ai pas encore fait. Considérons:
- bien sûr, vous devez considérer les lignes complètement contenues dans un morceau.
- mais vous devez gérer lorsque les extrémités d'une ligne sont en des morceaux adjacents.
- ou lorsque les paramètres ont au moins un fragment entre eux
- la grande complication est quand la ligne se termine par une séquence de plusieurs octets, mais ladite séquence chevauche deux morceaux! (Une ligne se terminant en juste CR qui est aussi le dernier octet dans le chunk est un cas équivalent, puisque vous avez besoin de lire le prochain chunk pour voir si votre juste-CR est en fait un CRLF ou CR-CRLF. Il y a des manigances similaires quand le morceau se termine par CR-CR.)
- et vous avez besoin de gérer quand il n'y a plus de terminateurs à partir de votre offset courant, mais la fin des données est dans un morceau plus tard.
bonne chance!
Ou vous pouvez simplement utiliser un Generator
:
let stdinByLine = GeneratorOf({ () -> String? in
var input = UnsafeMutablePointer<Int8>(), lim = 0
return getline(&input, &lim, stdin) > 0 ? String.fromCString(input) : nil
})
nous allons essayer
for line in stdinByLine {
println(">>> \(line)")
}
c'est simple, paresseux, et facile à enchaîner avec d'autres choses swift comme les recenseurs et les foncteurs tels que la carte, réduire, filtrer; en utilisant l'emballage lazy()
.
elle généralise à tous FILE
comme:
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
})
}
appelé comme
for line in byLine(stdin) { ... }
je voulais une version qui ne modifierait pas continuellement le buffer ou le code dupliqué, car les deux sont inefficaces, et permettrait n'importe quelle taille de buffer (y compris 1 octet) et n'importe quel délimiteur. Il a une méthode publique: readline()
. Appeler cette méthode retournera la valeur de la chaîne de caractères de la ligne suivante ou zéro à EOF.
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)
}
}
il est appelé comme suit:
guard let myStream = LineStream(path: "/path/to/file.txt")
else { exit(EXIT_FAILURE) }
while let s = myStream.readLine() {
print(s)
}