Last active
August 29, 2015 14:14
-
-
Save kazk/d2ce1c5e53f3cb18eeec to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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