Skip to content

Instantly share code, notes, and snippets.

@kazk
Last active August 29, 2015 14:14
Show Gist options
  • Save kazk/d2ce1c5e53f3cb18eeec to your computer and use it in GitHub Desktop.
Save kazk/d2ce1c5e53f3cb18eeec to your computer and use it in GitHub Desktop.
import Foundation
private let DefaultChunkSize: Int = 256
private let DefaultEndOfLine: String = "\n"
/// Class for reading file line by line.
///
/// Based on DDFileReader by Dave DeLong.
/// [How to read data from NSFileHandle line by line?](http://stackoverflow.com/a/3711079/666371)
public class FileReader {
private let _filePath: String!
private let _EOLData: NSData!
private let _EOLDataSize: Int!
private let _EOLCharacterSet: NSCharacterSet!
private let _chunkSize: Int!
private let _fileHandle: NSFileHandle!
private let _totalFileLength: UInt64!
private var _offset: UInt64 = 0
public init?(filePath: String,
chunkSize: Int = DefaultChunkSize,
endOfLine: String = DefaultEndOfLine)
{
if let data = endOfLine.dataUsingEncoding(NSUTF8StringEncoding) {
_EOLData = data
_EOLDataSize = data.length
_EOLCharacterSet = NSCharacterSet(charactersInString: endOfLine)
} else {
return nil
}
if let handle = NSFileHandle(forReadingAtPath: filePath) {
_filePath = filePath
_fileHandle = handle
_totalFileLength = _fileHandle.seekToEndOfFile()
_chunkSize = chunkSize
} else {
return nil
}
}
/// Closes the file read.
deinit {
_fileHandle.closeFile()
}
/// Returns line including EOL.
public func readLine() -> String? {
if _offset >= _totalFileLength { return nil }
_fileHandle.seekToFileOffset(_offset)
let readData = NSMutableData()
var foundEOL = false
while !foundEOL {
if _offset >= _totalFileLength { break }
autoreleasepool {[unowned self] in
var chunk = self._fileHandle.readDataOfLength(self._chunkSize)
if let range = chunk.rangeOfData(self._EOLData) {
foundEOL = true
let n = range.location + self._EOLDataSize
chunk = chunk.subdataWithRange(NSRange(location: 0, length: n))
}
readData.appendData(chunk)
self._offset += UInt64(chunk.length)
}
}
return NSString(data: readData, encoding: NSUTF8StringEncoding)
}
/// Returns line without EOL.
public func readTrimmedLine() -> String? {
return self.readLine()?.stringByTrimmingCharactersInSet(_EOLCharacterSet)
}
/// Enumerates lines with EOL, optionally stopping.
public func enumerateLines(body: (String, inout Bool)->()) {
var stop = false
while !stop {
if let line = self.readLine() {
body(line, &stop)
} else {
break
}
}
}
/// Enumerates lines without EOL, optionally stopping.
public func enumerateTrimmedLines(body: (String, inout Bool)->()) {
var stop = false
while !stop {
if let line = self.readTrimmedLine() {
body(line, &stop)
} else {
break
}
}
}
/// Enumerate all lines without EOL.
public func enumerateTrimmedLines(body: (String)->()) {
while let line = self.readTrimmedLine() { body(line) }
}
/// Enumerate all lines with EOL.
public func enumerateLines(body: (String)->()) {
while let line = self.readLine() { body(line) }
}
/// Reset offset to 0.
public func resetOffset() {
_offset = 0
}
}
private extension NSData {
func rangeOfData(dataToFind: NSData) -> NSRange? {
let _bytes = UnsafePointer<Int8>(bytes)
let _searchBytes = UnsafePointer<Int8>(dataToFind.bytes)
let _searchLength = dataToFind.length
var searchIndex = 0
var range = NSRange(location: NSNotFound, length: _searchLength)
for idx in 0..<length {
if _bytes[idx] == _searchBytes[searchIndex] {
if range.location == NSNotFound { range.location = idx }
searchIndex++
if searchIndex == _searchLength { return range }
} else { // No match or Matching sequence broke.
searchIndex = 0 // Return to the first search index
range.location = NSNotFound // discarding previous found location.
}
}
return (range.location != NSNotFound) ? range : nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment