Quinn, Jens, thanks for pointing me at the NSStream classes. For some
reason, I discarded NSStream quickly after seeing the first paragraph
of Apple's
documentation on output streams
<https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Streams/Articles/WritingOutputStreams.html>
listing 5 relatively complex steps — without realizing that this API also
has a synchronous interface.
Obviously, the NSStream API is too low level and thus difficult to use.
Therefore, I would also like to see Apple build a great IO streaming API
for Swift. I've implemented Quinn's example using NSInputStream and Swift's
Generator design pattern. Except for mapping NSInputStream to a generator —
which is really messy — everything else is straightforward and much more
extensible and scalable than the standard Foundation approach. The code is
below.
== Matthias
See TextStreams.swift · GitHub
class ByteGenerator: GeneratorType {
typealias Element = UInt8
let input: NSInputStream
var buffer: [UInt8]
var index: Int = 0
var eof: Bool = true
init?(path: String, capacity: Int = 1024) {
guard let input = NSInputStream(fileAtPath: path) else {
return nil
}
self.buffer = [UInt8](count: capacity, repeatedValue: 0)
input.open()
if input.hasBytesAvailable {
self.eof = input.read(&self.buffer, maxLength: self.buffer.count *
sizeof(UInt8)) <= 0
}
self.input = input
}
deinit {
input.close()
}
func next() -> UInt8? {
guard !self.eof else {
return nil
}
if self.index >= self.buffer.count {
self.index = 0
self.eof = !input.hasBytesAvailable ||
input.read(&self.buffer, maxLength: self.buffer.count *
sizeof(UInt8)) <= 0
guard !self.eof else {
return nil
}
}
self.index += 1
return self.buffer[self.index - 1]
}
}
struct CharacterGenerator<G: GeneratorType, U: UnicodeCodecType where
G.Element == U.CodeUnit>: GeneratorType {
typealias Element = Character
var source: G
var decoder: U
mutating func next() -> Character? {
guard case .Result(let scalar) = self.decoder.decode(&self.source) else
{
return nil
}
return Character(scalar)
}
}
struct LineGenerator<G: GeneratorType where G.Element == Character>:
GeneratorType {
typealias Element = String
var source: G
mutating func next() -> String? {
guard let fst = source.next() else {
return nil
}
guard fst != "\n" else {
return ""
}
var line = String(fst)
while let ch = source.next() {
if (ch == "\n") {
return line
}
line.append(ch)
}
return line
}
}
if let input = ByteGenerator(path: "/Users/username/filename.txt") {
var generator = LineGenerator(source: CharacterGenerator(source: input,
decoder: UTF8()))
var i = 0
while let line = generator.next() {
print("\(i): \(line)")
i += 1
}
}
*Matthias Zenger* matthias@objecthub.net
···
On Sun, Jun 5, 2016 at 8:36 PM, Jens Alfke <jens@mooseyard.com> wrote:
On Jun 4, 2016, at 6:25 PM, Matthias Zenger via swift-users < > swift-users@swift.org> wrote:
I wanted to use NSFileHandle (for a use case that requires streaming) and
realized that this is an API that cannot really be used in Swift because
it's based on Objective-C exceptions.
You’re right, NSFileHandle is a very archaic class (kind of a coelacanth)
and its I/O methods signal errors by throwing exceptions. It’s almost
unique in that regard; in general Cocoa APIs are only supposed to throw
exceptions for programmer errors like assertion failures.
Are there any alternatives?
NSStream.
—Jens