Skip to content

Instantly share code, notes, and snippets.

@algal
Created August 14, 2019 21:25
Show Gist options
  • Save algal/f0265169eb0b7a4b25a8cb657798e59a to your computer and use it in GitHub Desktop.
Save algal/f0265169eb0b7a4b25a8cb657798e59a to your computer and use it in GitHub Desktop.
Lazily read a line at a time in Swift
import Foundation
// known good: Swift 5
typealias LineIteratorState = (
// 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,LineIteratorState>
{
var state:LineIteratorState = (linePtr:nil,linecap:0,filePtr:nil)
state.filePtr = fopen(fileURL.path,"r")
func cleanup(state s:LineIteratorState) {
if let actualLine = s.linePtr { free(actualLine) }
fclose(s.filePtr)
}
return sequence(state: state, next: { (currentState) -> String? in
if getline(&currentState.linePtr, &currentState.linecap, currentState.filePtr) > 0,
let theLine = currentState.linePtr {
return String.init(cString:theLine)
}
else {
cleanup(state: currentState)
return nil
}
})
}
// example of how to use:
var seq = lines(ofFile: fileURL)
for line in seq {
print(line)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment