(이 코드는 현재 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()
}
func nextLine() -> String? {
precondition(fileHandle != nil, "Attempt to read from closed file")
if atEof {
return nil
}
var range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
while range.location == NSNotFound {
let tmpData = fileHandle.readDataOfLength(chunkSize)
if tmpData.length == 0 {
atEof = true
if buffer.length > 0 {
let line = NSString(data: buffer, encoding: encoding)
buffer.length = 0
return line as String?
}
return nil
}
buffer.appendData(tmpData)
range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
}
let line = NSString(data: buffer.subdataWithRange(NSMakeRange(0, range.location)),
encoding: encoding)
buffer.replaceBytesInRange(NSMakeRange(0, range.location + range.length), withBytes: nil, length: 0)
return line as String?
}
func rewind() -> Void {
fileHandle.seekToFileOffset(0)
buffer.length = 0
atEof = false
}
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 베타 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()
}
func nextLine() -> String? {
precondition(fileHandle != nil, "Attempt to read from closed file")
while !atEof {
if let range = buffer.range(of: delimData) {
let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding)
buffer.removeSubrange(0..<range.upperBound)
return line
}
let tmpData = fileHandle.readData(ofLength: chunkSize)
if tmpData.count > 0 {
buffer.append(tmpData)
} else {
atEof = true
if buffer.count > 0 {
let line = String(data: buffer as Data, encoding: encoding)
buffer.count = 0
return line
}
}
}
return nil
}
func rewind() -> Void {
fileHandle.seek(toFileOffset: 0)
buffer.count = 0
atEof = false
}
func close() -> Void {
fileHandle?.closeFile()
fileHandle = nil
}
}
extension StreamReader : Sequence {
func makeIterator() -> AnyIterator<String> {
return AnyIterator {
return self.nextLine()
}
}
}