SWIFT ONLY -- Reading from NSInputStream
I have figured it out myself.
Look at this simple code:
let data: NSData = "Jonathan Yaniv.".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
let stream: NSInputStream = NSInputStream(data: data)
var buffer = [UInt8](count: 8, repeatedValue: 0)
stream.open()
if stream.hasBytesAvailable {
let result :Int = stream.read(&buffer, maxLength: buffer.count)
}
// result = 8 -- because of the size of the buffer.
// buffer contains the first 8 bytes repreenting the word "Jonathan"
Explanation:
The read method signature:
stream.read(<#buffer: UnsafeMutablePointer#>, maxLength: <#Int#>)
It gets a UnsafeMutablePointer as a first parameter, which means the method expects to get a POINTER to an array of type UInt8 - NOT the array itself
Therefore, we add the & notation before the name of the buffer variable.
&buffer == the pointer to the UInt8 array object named buffer.
Receiving Data from NSInputStream in Swift
You're missing the event hasSpaceAvailable
, which I expect is occurring when it says "unknown". It's telling you that it is ready to receive more data.
Generally, I avoid using default
in switch statements for enums, since the compiler will tell you when you've missed something.
NSinputstream read data is returning null value?
I think the code is absolutely fine and it should read the data, may be after you've read 4096 bytes there might be some more bytes available and loop continues, and you are initialising the output variable again, So you might be missing it.
Use the following snippet:
if (aStream == inputStream) {
uint8_t buffer[4096];
int len;
NSString* tempOutput = @"";
while ([inputStream hasBytesAvailable]) {
len = (int)[inputStream read:buffer maxLength:sizeof(buffer)];
NSLog(@"\nThe length is -- %d\n",len);
if (len > 0) {
NSData *data = [[NSData alloc] initWithBytes:buffer length:len];
output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
tempOutput = [tempOutput stringByAppendingString:output];
// output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSUTF8StringEncoding];
}
}
output = tempOutput;
}
Read IntegerType from NSInputStream
The read()
method requires a mutable buffer pointer:
let n = withUnsafeMutablePointer(&buffer) { (p) in
self.read(UnsafeMutablePointer(p), maxLength: sizeof(T))
}
The placeholder type <UInt8>
in UnsafeMutablePointer(p)
can be inferred automatically from the context.
Note that there is a general problem with your approach: If the input
stream is not connected to a real file but to some communication channel
(e.g. a TCP socket, pipe, ...) then the only guarantee is that read()
waits until at least one byte is read and the assertion n == sizeof(T)
can fail. You have to read again in a loop until the required amount
is read.
How to convert NSInputStream to NSString or how to read NSInputStream
Got it. The stream that I tried to access hadn't been opened. Even then, it was read only. So I made a copy of it and then opened it. This still wasn't right though, I was only reading a byte at a time (a single character). So here's the final solution:
NSInputStream *stream = r.HTTPBodyStream;
uint8_t byteBuffer[4096];
[stream open];
if (stream.hasBytesAvailable)
{
NSLog(@"bytes available");
NSInteger bytesRead = [stream read:byteBuffer maxLength:sizeof(byteBuffer)]; //max len must match buffer size
NSString *stringFromData = [[NSString alloc] initWithBytes:byteBuffer length:bytesRead encoding:NSUTF8StringEncoding];
NSLog(@"another pathetic attempt: %@", stringFromData);
}
Get input from STDIN using NSInputStream in Swift
The problem is using UnsafeMutablePointer<UnsafeMutablePointer<UInt8>>()
for readBufferRef
and UnsafeMutablePointer<Int>()
for readBufferLengthRef
.
The modified code
#!/usr/bin/env xcrun swift
import Foundation
let inputStream = NSInputStream(fileAtPath: "/dev/stdin")
inputStream!.open()
println(inputStream!.hasBytesAvailable)
if(inputStream!.hasBytesAvailable) {
// var readBufferRef = UnsafeMutablePointer<UnsafeMutablePointer<UInt8>>()
var readBufferRef = UnsafeMutablePointer<UInt8>()
// var readBufferLengthRef = UnsafeMutablePointer<Int>()
var readBufferLengthRef = 0
// let readBufferIsAvailable = inputStream!.getBuffer(readBufferRef, length: readBufferLengthRef)
let readBufferIsAvailable = inputStream!.getBuffer(&readBufferRef, length: &readBufferLengthRef)
if readBufferIsAvailable {
println("Yay")
}
}
println("Heck")
How do you correctly work with NSStreams (no blocking, read to the end of the data, and message retrying)?
You're probably working too hard here. NSStreamDelegate
was designed before GCD, when the majority of Cocoa work was done on a single thread. While there are still reasons to use it in some cases, in most cases GCD and synchronous methods will make it much easier. For example, to read you'd do something like this:
import Foundation
enum StreamError: ErrorType {
case Error(error: NSError?, partialData: [UInt8])
}
func readStream(inputStream: NSInputStream) throws -> [UInt8] {
let bufferSize = 1024
var buffer = [UInt8](count: bufferSize, repeatedValue: 0)
var data: [UInt8] = []
while true {
let count = inputStream.read(&buffer, maxLength: buffer.capacity)
guard count >= 0 else {
inputStream.close()
throw StreamError.Error(error: inputStream.streamError, partialData: data)
}
guard count != 0 else {
inputStream.close()
return data
}
data.appendContentsOf(buffer.prefix(count))
}
}
let textPath = NSBundle.mainBundle().pathForResource("text.txt", ofType: nil)!
let inputStream = NSInputStream(fileAtPath: textPath)!
inputStream.open()
do {
let data = try readStream(inputStream)
print(data)
} catch let err {
print("ERROR: \(err)")
}
This will block the current queue, sure. So don't run it on the main queue. Put the do
block into a dispatch_async
. If you later need the data on the main queue, dispatch_async
it back, just like any other background process.
NSInputStream / NSOutputStream not opening
Just add the autoreleasepool inside the getStreamsToHostWithName method call since NSStream deallocated when the block execution completed
autoreleasepool {
NSStream.getStreamsToHostWithName(self.host, port: self.port, inputStream: &inputStream, outputStream: &outputStream)
}
I have tested the above code in xcode7 beta 4 and SocketTest tool(http://sourceforge.net/projects/sockettest/?source=typ_redirect)
Alternatively you can also use the CFStreamCreatePairWithSocketToHost API
class TCPConnection: NSObject, NSStreamDelegate {
var host:String?
var port:UInt32?
var inputStream: NSInputStream?
var outputStream: NSOutputStream?
var status = false;
var output = "message"
var bufferSize = 1024;
init(host: String, port:UInt32){
self.host = host
self.port = port
self.status = false
output = ""
super.init()
}
func stream(aStream: NSStream, handleEvent aStreamEvent: NSStreamEvent) {
switch aStreamEvent {
case NSStreamEvent.OpenCompleted:
print("OpenCompleted")
break
case NSStreamEvent.HasBytesAvailable:
print("HasBytesAvailable")
break
case NSStreamEvent.HasSpaceAvailable:
print("HasSpaceAvailable")
break
case NSStreamEvent.EndEncountered:
print("EndEncountered")
// aStream.close()
aStream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
break
case NSStreamEvent.None:
break
case NSStreamEvent.ErrorOccurred:
break
default:
print("# something weird happend")
break
}
}
func connect() {
print("# connecting to \(host):\(port)")
var cfReadStream : Unmanaged<CFReadStream>?
var cfWriteStream : Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, port!, &cfReadStream, &cfWriteStream)
inputStream = cfReadStream!.takeRetainedValue()
outputStream = cfWriteStream!.takeRetainedValue()
inputStream!.delegate = self
outputStream!.delegate = self
inputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
outputStream!.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
inputStream!.open()
outputStream!.open()
}
func read(){
var buffer = [UInt8](count: bufferSize, repeatedValue: 0)
output = ""
while (self.inputStream!.hasBytesAvailable){
var bytesRead: Int = inputStream!.read(&buffer, maxLength: buffer.count)
if bytesRead >= 0 {
output += NSString(bytes: UnsafePointer(buffer), length: bytesRead, encoding: NSASCIIStringEncoding)! as String
} else {
print("# error")
}
print("> \(output)")
}
}
func send(message:String){
let data:NSData = message.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let bytesWritten = self.outputStream!.write(UnsafePointer(data.bytes), maxLength: data.length)
print("< send to \(host)")
}
}
Related Topics
Property Cannot Be Declared Public Because Its Type Uses an Internal Type
How to Install Package in Xcode via Swift Package Manager
How to Launch an External Process
How to Get the Centre of the View
How to Filter Events Created for the Current Date in the Realm Swift
Need Self to Set All Constants of a Swift Class in Init
Swiftui - Remove Space Between Cells
Kvo with Shared Nsuserdefaults in Swift
Swift Equivalent of Array.Componentsjoinedbystring
Initialization of 'Unsafemutablerawpointer' Results in a Dangling Pointer
Convert String to Cgfloat in Swift
Mutate Function Parameters in Swift
How to Implement a Thread Safe Hashtable (Phonebook) Data Structure in Swift
How to Select a Contact with Abpeoplepickernavigationcontroller in Swift
Swift, Avaudiorecorder: Error 317: Ca_Debug_String: Inpropertydata == Null