Читать файл /URL построчно в Swift
Я пытаюсь прочитать файл, указанный в NSURL
и загрузить его в массив с элементами, разделенными символом новой строки \n
,
Вот способ, которым я сделал это до сих пор:
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
}
Я не очень доволен этим по нескольким причинам. Во-первых, я работаю с файлами размером от нескольких килобайт до сотен МБ. Как вы можете себе представить, работа с такими большими строками медленная и громоздкая. Во-вторых, это останавливает пользовательский интерфейс во время его выполнения - опять же, не хорошо.
Я пытался запустить этот код в отдельном потоке, но у меня были проблемы с этим, и, кроме того, он все еще не решает проблему работы с огромными строками.
Я хотел бы сделать что-то вроде следующего псевдокода:
var aStreamReader = new StreamReader(from_file_or_url)
while aStreamReader.hasNextLine == true {
currentline = aStreamReader.nextLine()
list.addItem(currentline)
}
Как бы я сделал это в Swift?
Несколько замечаний о файлах, из которых я читаю: Все файлы состоят из коротких (<255 символов) строк, разделенных либо \n
или же \r\n
, Длина файлов варьируется от ~100 строк до более 50 миллионов строк. Они могут содержать европейские символы и / или символы с акцентами.
12 ответов
(Код для Swift 2.2/Xcode 7.3 теперь. Более старые версии можно найти в истории редактирования, если кому-то это нужно. Обновленная версия для Swift 3 предоставляется в конце.)
Следующий код Swift в значительной степени вдохновлен различными ответами на Как читать данные из NSFileHandle построчно?, Он читает из файла порциями и преобразует полные строки в строки.
Разделитель строки по умолчанию (\n
), строковое кодирование (UTF-8) и размер фрагмента (4096) могут быть установлены с дополнительными параметрами.
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
}
}
Использование:
if let aStreamReader = StreamReader(path: "/path/to/file") {
defer {
aStreamReader.close()
}
while let line = aStreamReader.nextLine() {
print(line)
}
}
Вы даже можете использовать ридер с циклом for-in
for line in aStreamReader {
print(line)
}
путем реализации SequenceType
протокол (сравните http://robots.thoughtbot.com/swift-sequences):
extension StreamReader : SequenceType {
func generate() -> AnyGenerator<String> {
return AnyGenerator {
return self.nextLine()
}
}
}
Обновление для Swift 3/Xcode 8 beta 6: также "модернизировано" для использования guard
и новый 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()
}
}
}
Я завернул код из ответа Алгала в удобный класс (Swift 4.0)
UPD: этот код не зависит от платформы (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
}
}
}
Использование:
guard let reader = LineReader(path: "/Path/to/file.txt") else {
return; // cannot open file
}
for line in reader {
print(">" + line.trimmingCharacters(in: .whitespacesAndNewlines))
}
Swift 4.2 Безопасный синтаксис
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
}
}
}
Использование:
guard let reader = LineReader(path: "/Path/to/file.txt") else {
return
}
reader.forEach { line in
print(line.trimmingCharacters(in: .whitespacesAndNewlines))
}
Эта функция принимает файловый поток и возвращает AnyGenerator
который возвращает каждую строку файла:
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
}
}
Так, например, вот как вы могли бы использовать его для печати каждой строки файла с именем "foo" в комплекте приложений:
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
Я разработал этот ответ, изменив ответ Алекса Брауна, чтобы устранить утечку памяти, упомянутую в комментарии Мартина Р., и обновив его для работы со Swift 2.2 (Xcode 7.3).
Я опаздываю к игре, но вот небольшой класс, который я написал для этой цели. После нескольких разных попыток (попробуйте подкласс NSInputStream
Я нашел это разумным и простым подходом.
Запомни #import <stdio.h>
в вашем соединительном заголовке.
// 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)
}
}
Свифт 5.5: использоватьurl.lines
Документы ADC здесь
Пример использования:
guard let url = URL(string: "https://www.example.com") else {
return
}
// Manipulating an `Array` in memory seems to be a requirement.
// This will balloon in size as lines of data get added.
var myHugeArray = [String]()
do {
// This should keep the inbound data memory usage low
for try await line in url.lines {
myHugeArray.append(line)
}
} catch {
debugPrint(error)
}
Вы можете использовать это в SwiftUI.task { }
модификатор или оберните это вTask
возвращаемый тип, чтобы получить работу от основного потока.
После ответа@dankogai я внес несколько изменений для 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()
Это сработало для меня.
Спасибо
Попробуйте этот ответ или прочитайте Руководство по программированию Mac OS Stream.
Вы можете обнаружить, что на самом деле производительность будет лучше при использовании stringWithContentsOfURL
тем не менее, поскольку с данными на основе памяти (или с отображением в памяти) будет работать быстрее, чем с данными на диске.
Выполнение этого в другом потоке хорошо документировано, например, здесь.
Обновить
Если вы не хотите читать все сразу и не хотите использовать NSStreams, то вам, вероятно, придется использовать файловый ввод-вывод уровня C. Есть много причин, чтобы этого не делать - блокировка, кодировка символов, обработка ошибок ввода-вывода, скорость именования, но только некоторые из них - это то, для чего предназначены библиотеки Foundation. Ниже я набросал простой ответ, который касается только данных 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)
}
Оказывается, старый добрый C API довольно удобен в Swift, как только вы запустите UnsafePointer. Вот простой кот, который читает из stdin и печатает в stdout построчно. Тебе даже не нужен фонд. Дарвина достаточно:
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()
Или вы можете просто использовать Generator
:
let stdinByLine = GeneratorOf({ () -> String? in
var input = UnsafeMutablePointer<Int8>(), lim = 0
return getline(&input, &lim, stdin) > 0 ? String.fromCString(input) : nil
})
Давайте попробуем это
for line in stdinByLine {
println(">>> \(line)")
}
Это просто, лениво и легко связать с другими быстрыми вещами, такими как перечислители и функторы, такие как map, Reduce, Filter; с использованием lazy()
обертка.
Это обобщает на все FILE
как:
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
})
}
называется как
for line in byLine(stdin) { ... }
(Примечание: я использую Swift 3.0.1 на Xcode 8.2.1 с macOS Sierra 10.12.3)
Все ответы, которые я видел здесь, упустили, что он может искать LF или CRLF. Если все идет хорошо, он / она может просто сопоставить на LF и проверить возвращаемую строку на наличие дополнительного CR в конце. Но общий запрос включает несколько строк поиска. Другими словами, разделитель должен быть Set<String>
где набор не является ни пустым, ни содержит пустую строку вместо одной строки.
С моей первой попытки в этом году я попытался сделать "правильную вещь" и найти общий набор строк. Это было слишком сложно; вам нужен полноценный парсер и конечные автоматы и все такое. Я отказался от этого и от проекта, частью которого он был.
Сейчас я снова работаю над проектом и снова сталкиваюсь с той же проблемой. Теперь я собираюсь выполнить жесткий поиск кода на CR и LF. Я не думаю, что кому-то нужно искать по двум полу-независимым и полу-зависимым символам, подобным этому, за пределами разбора CR/LF.
Я использую методы поиска, предоставленные Data
так что я не занимаюсь строковым кодированием и прочим. Просто сырая двоичная обработка. Только предположим, что у меня есть суперсет ASCII, такой как ISO Latin-1 или UTF-8, здесь. Вы можете обрабатывать строковое кодирование на следующем более высоком уровне, и вы понимаете, считается ли CR/LF с присоединенными вторичными кодовыми точками CR или LF.
Алгоритм: просто продолжайте поиск следующего CR и следующего LF из вашего текущего байтового смещения.
- Если ни один из них не найден, то следует считать следующую строку данных от текущего смещения до конца данных. Обратите внимание, что длина терминатора равна 0. Пометьте это как конец цикла чтения.
- Если сначала обнаружен LF или только LF, рассмотрим следующую строку данных, начиная с текущего смещения LF. Обратите внимание, что длина терминатора равна 1. Переместите смещение на после LF.
- Если найден только CR, то как и в случае с LF (просто с другим байтовым значением).
- В противном случае мы получили CR и LF.
- Если два смежных, то обрабатывать как в случае LF, за исключением того, что длина терминатора будет 2.
- Если между ними есть один байт, а указанный байт также является CR, то мы получили сообщение "Разработчик Windows написал двоичный файл \r\n в текстовом режиме, что вызвало проблему \r\r\n". Также обработайте это как случай LF, за исключением того, что длина терминатора будет 3.
- В противном случае CR и LF не связаны, а обрабатываются как случай just-CR.
Вот код для этого:
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
}
}
Конечно, если у вас есть Data
блок длиной, по крайней мере, со значительной долей гигабайта, вы будете получать удар всякий раз, когда больше нет CR или LF от текущего байтового смещения; всегда бесполезно ища до конца во время каждой итерации. Чтение данных порциями поможет:
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)))
}
}
Вы должны сами смешать эти идеи, так как я еще этого не сделал. Рассматривать:
- Конечно, вы должны учитывать строки, полностью содержащиеся в чанке.
- Но вы должны обрабатывать, когда концы строки находятся в смежных кусках.
- Или когда конечные точки имеют по крайней мере один кусок между ними
- Большим осложнением является то, что строка заканчивается многобайтовой последовательностью, но указанная последовательность занимает два фрагмента! (Строка, заканчивающаяся просто CR, который также является последним байтом в чанке, является эквивалентным случаем, так как вам нужно прочитать следующий чанк, чтобы увидеть, является ли ваш just-CR на самом деле CRLF или CR-CRLF. Существуют похожие махинации, когда кусок заканчивается на CR-CR.)
- И вам нужно обрабатывать, когда в вашем текущем смещении больше нет терминаторов, но конец данных находится в более позднем фрагменте.
Удачи!
Я хотел версию, которая не изменяла бы постоянно буфер или дублирующий код, так как оба неэффективны, и допускали бы буфер любого размера (включая 1 байт) и любой разделитель. У него есть один публичный метод: readline()
, Вызов этого метода вернет значение String следующей строки или nil в 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)
}
}
Это называется следующим образом:
guard let myStream = LineStream(path: "/path/to/file.txt")
else { exit(EXIT_FAILURE) }
while let s = myStream.readLine() {
print(s)
}