Próbuję odczytać plik podany w NSURL
i załadować go do tablicy, z elementami oddzielonymi znakiem nowego wiersza \n
.
Oto sposób, w jaki zrobiłem to do tej pory:
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
}
Nie jestem z tego zadowolony z kilku powodów. Po pierwsze, pracuję z plikami o wielkości od kilku kilobajtów do setek MB. Jak możesz sobie wyobrazić, praca z tak dużymi strunami jest powolna i nieporęczna. Po drugie, powoduje to zawieszenie interfejsu użytkownika podczas wykonywania - znowu, niedobrze.
Zajrzałem do uruchomienia tego kodu w osobnym wątku, ale miałem z tym problem, a poza tym nadal nie rozwiązuje problemu radzenia sobie z dużymi ciągami.
Chciałbym zrobić coś w stylu następującego pseudokodu:
var aStreamReader = new StreamReader(from_file_or_url)
while aStreamReader.hasNextLine == true {
currentline = aStreamReader.nextLine()
list.addItem(currentline)
}
Jak mógłbym to osiągnąć w Swift?
Kilka uwag na temat plików Czytam od: Wszystkie pliki składają się z krótkich (<255 znaków) ciągi oddzielone albo \n
albo \r\n
. Długość plików waha się od ~ 100 do ponad 50 milionów wierszy. Mogą zawierać znaki europejskie i / lub znaki z akcentami.
Odpowiedzi:
(Kod jest teraz dla Swift 2.2 / Xcode 7.3. Starsze wersje można znaleźć w historii edycji, jeśli ktoś tego potrzebuje. Zaktualizowana wersja dla Swift 3 znajduje się na końcu.)
Poniższy kod Swift jest mocno zainspirowany różnymi odpowiedziami na temat Jak czytać dane z NSFileHandle wiersz po wierszu? . Czyta z pliku fragmentami i konwertuje całe linie na łańcuchy.
Domyślny separator linii (
\n
), kodowanie ciągów (UTF-8) i rozmiar fragmentu (4096) można ustawić za pomocą parametrów opcjonalnych.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 } }
Stosowanie:
if let aStreamReader = StreamReader(path: "/path/to/file") { defer { aStreamReader.close() } while let line = aStreamReader.nextLine() { print(line) } }
Możesz nawet użyć czytnika z pętlą for-in
for line in aStreamReader { print(line) }
implementując
SequenceType
protokół (porównaj http://robots.thoughtbot.com/swift-sequences ):extension StreamReader : SequenceType { func generate() -> AnyGenerator<String> { return AnyGenerator { return self.nextLine() } } }
Aktualizacja dla Swift 3 / Xcode 8 beta 6: także „zmodernizowana” do użytku
guard
i nowyData
typ wartości: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() } } }
źródło
generate()
funkcję do klasy StreamReader i zadeklarować ją jakoclass StreamReader : Sequence { ... }
. Ale wydaje się, że dobrym stylem Swift jest używanie rozszerzeń dla oddzielnych elementów funkcjonalności.while !aStreamReader.atEof { try autoreleasepool { guard let line = aStreamReader.nextLine() else { return } ...code... } }
Wydajna i wygodna klasa do czytania pliku tekstowego wiersz po wierszu (Swift 4, Swift 5)
Uwaga: ten kod jest niezależny od platformy (macOS, iOS, ubuntu)
import Foundation /// Read text file line by line in efficient way 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 } } }
Stosowanie:
guard let reader = LineReader(path: "/Path/to/file.txt") else { return; // cannot open file } for line in reader { print(">" + line.trimmingCharacters(in: .whitespacesAndNewlines)) }
Repozytorium na github
źródło
Swift 4.2 Bezpieczna składnia
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 } } }
Stosowanie:
guard let reader = LineReader(path: "/Path/to/file.txt") else { return } reader.forEach { line in print(line.trimmingCharacters(in: .whitespacesAndNewlines)) }
źródło
Spóźniłem się na mecz, ale oto mała klasa, którą napisałem w tym celu. Po kilku różnych próbach (spróbuj podklasy
NSInputStream
) stwierdziłem, że jest to rozsądne i proste podejście.Pamiętaj o tym
#import <stdio.h>
w swoim nagłówku mostkowania.// 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) } }
źródło
withCString
nie jest konieczne (i naprawdę niebezpieczne), możesz po prostu zadzwonićreturn fopen(self.path, self.mode)
. Można dodać sprawdzenie, czy plik naprawdę można otworzyć, obecniereadline()
po prostu się zawiesi.UnsafePointer<CChar>
Nie potrzebna jest obsada. Wreszcie przykład użycia nie kompiluje się.Ta funkcja pobiera adres URL pliku i zwraca sekwencję, która zwróci każdy wiersz pliku, leniwie je czytając. Działa ze Swift 5. Opiera się na podstawowym
getline
:typealias LineState = ( // pointer to a C string representing a line linePtr:UnsafeMutablePointer<CChar>?, linecap:Int, filePtr:UnsafeMutablePointer<FILE>? ) /// Returns a sequence which iterates through all lines of the the file at the URL. /// /// - Parameter url: file URL of a file to read /// - Returns: a Sequence which lazily iterates through lines of the file /// /// - warning: the caller of this function **must** iterate through all lines of the file, since aborting iteration midway will leak memory and a file pointer /// - precondition: the file must be UTF8-encoded (which includes, ASCII-encoded) func lines(ofFile url:URL) -> UnfoldSequence<String,LineState> { let initialState:LineState = (linePtr:nil, linecap:0, filePtr:fopen(url.path,"r")) return sequence(state: initialState, next: { (state) -> String? in if getline(&state.linePtr, &state.linecap, state.filePtr) > 0, let theLine = state.linePtr { return String.init(cString:theLine) } else { if let actualLine = state.linePtr { free(actualLine) } fclose(state.filePtr) return nil } }) }
Na przykład, oto jak można go użyć do wydrukowania każdego wiersza pliku o nazwie „foo” w pakiecie aplikacji:
let url = NSBundle.mainBundle().urlForResource("foo", ofType: nil)! for line in lines(ofFile:url) { // suppress print's automatically inserted line ending, since // lineGenerator captures each line's own new line character. print(line, separator: "", terminator: "") }
Opracowałem tę odpowiedź, modyfikując odpowiedź Alexa Browna, aby usunąć wyciek pamięci wspomniany w komentarzu Martina R., oraz aktualizując ją do wersji Swift 5.
źródło
Wypróbuj tę odpowiedź lub przeczytaj Podręcznik programowania strumienia w systemie Mac OS .
Może się okazać, że wydajność będzie faktycznie lepsza przy użyciu
stringWithContentsOfURL
, ponieważ praca z danymi opartymi na pamięci (lub mapowanymi w pamięci) będzie szybsza niż z danymi na dysku.Wykonywanie go na innym wątku jest dobrze udokumentowane, również np. Tutaj . .
Aktualizacja
Jeśli nie chcesz czytać wszystkiego od razu i nie chcesz używać NSStreams, prawdopodobnie będziesz musiał użyć wejścia / wyjścia pliku na poziomie C. Jest wiele powodów, aby tego nie robić - blokowanie, kodowanie znaków, obsługa błędów we / wy, szybkość, by wymienić tylko kilka - po to są biblioteki Foundation. Poniżej naszkicowałem prostą odpowiedź, która dotyczy tylko danych 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) }
źródło
case 0...127
znaki spoza zestawu ASCII?Okazuje się, że dobre, stare, fascynujące C API jest całkiem wygodne w Swift, gdy już poznasz UnsafePointer. Oto prosty kot, który czyta ze standardowego wejścia i drukuje na standardowe wyjście wiersz po wierszu. Nie potrzebujesz nawet Fundacji. Darwin wystarczy:
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()
źródło
fgets()
czyta znaki do (włącznie) znaku nowej linii (lub EOF). A może źle zrozumiałem twój komentarz?Lub możesz po prostu użyć
Generator
:let stdinByLine = GeneratorOf({ () -> String? in var input = UnsafeMutablePointer<Int8>(), lim = 0 return getline(&input, &lim, stdin) > 0 ? String.fromCString(input) : nil })
Wypróbujmy to
for line in stdinByLine { println(">>> \(line)") }
Jest prosty, leniwy i łatwy do połączenia z innymi szybkimi elementami, takimi jak wyliczanie i funktory, takie jak mapowanie, redukcja, filtrowanie; za pomocą
lazy()
opakowania.Uogólnia się to dla wszystkich
FILE
jako: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 }) }
zwany jak
for line in byLine(stdin) { ... }
źródło
getline()
przydziela bufor dla danych.(Uwaga: używam Swift 3.0.1 na Xcode 8.2.1 z macOS Sierra 10.12.3)
Wszystkie odpowiedzi, które tu widziałem, pominęły, że mógł szukać LF lub CRLF. Jeśli wszystko pójdzie dobrze, może po prostu dopasować się do LF i sprawdzić, czy na końcu zwrócony ciąg jest dodatkowy CR. Ale ogólne zapytanie obejmuje wiele ciągów wyszukiwania. Innymi słowy, separatorem musi być a
Set<String>
, gdzie zestaw nie jest pusty ani nie zawiera pustego ciągu zamiast pojedynczego ciągu.Podczas mojej pierwszej próby w zeszłym roku próbowałem zrobić „właściwą rzecz” i poszukać ogólnego zestawu ciągów. To było zbyt trudne; potrzebujesz pełnego parsera i maszyn stanowych i tym podobnych. Zrezygnowałem z tego i projektu, którego był częścią.
Teraz ponownie wykonuję projekt i znów stoję przed tym samym wyzwaniem. Teraz przejdę do wyszukiwania twardego kodu na CR i LF. Nie sądzę, żeby ktokolwiek musiał szukać takich dwóch częściowo niezależnych i częściowo zależnych znaków poza analizą CR / LF.
Używam metod wyszukiwania dostarczonych przez
Data
, więc nie robię tutaj kodowania ciągów i innych rzeczy. Tylko surowe przetwarzanie binarne. Załóżmy tylko, że mam tutaj superset ASCII, taki jak ISO Latin-1 lub UTF-8. Możesz obsługiwać kodowanie ciągów w następnej wyższej warstwie i zastanawiasz się, czy CR / LF z dołączonymi dodatkowymi punktami kodowymi nadal liczy się jako CR czy LF.Algorytm: po prostu szukaj dalej następnej CR i następnej LF z bieżącego przesunięcia bajtów.
Oto kod:
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 } }
Oczywiście, jeśli masz
Data
blok o długości, która stanowi co najmniej znaczący ułamek gigabajta, otrzymasz trafienie, gdy nie będzie już CR ani LF z bieżącego przesunięcia bajtów; zawsze bezowocne poszukiwanie do końca podczas każdej iteracji. Odczytywanie danych w fragmentach pomogłoby: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))) } }
Musisz samodzielnie wymieszać te pomysły, ponieważ jeszcze tego nie zrobiłem. Rozważać:
Powodzenia!
źródło
Idąc za odpowiedzią @ dankogai , wprowadziłem kilka modyfikacji dla Swift 4+,
let bufsize = 4096 let fp = fopen(jsonURL.path, "r"); var buf = UnsafeMutablePointer<Int8>.allocate(capacity: bufsize) while (fgets(buf, Int32(bufsize-1), fp) != nil) { print( String(cString: buf) ) } buf.deallocate()
To zadziałało dla mnie.
Dzięki
źródło
Chciałem wersji, która nie modyfikuje ciągle bufora lub duplikatu kodu, ponieważ oba są nieefektywne i pozwalałyby na bufor dowolnego rozmiaru (w tym 1 bajt) i dowolny ogranicznik. Posiada jedną metodę publiczną:
readline()
. Wywołanie tej metody zwróci wartość String następnego wiersza lub zero w 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) } }
Nazywa się to następująco:
guard let myStream = LineStream(path: "/path/to/file.txt") else { exit(EXIT_FAILURE) } while let s = myStream.readLine() { print(s) }
źródło