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.
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.
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")
Stream to Get Data - NSInputStream
There are two ways to get data from a stream: polling and using stream events.
Polling is simpler, but will block the thread it is running in. If you use this method, you don't need to perform the setDelegate:
or scheduleInRunLoop:forMode:
calls. Polling is performed by repeatedly calling read:maxLength:
.
NSInteger result;
uint8_t buffer[BUFFER_LEN]; // BUFFER_LEN can be any positive integer
while((result = [iStream read:buffer maxLength:BUFFER_LEN]) != 0) {
if(result > 0) {
// buffer contains result bytes of data to be handled
} else {
// The stream had an error. You can get an NSError object using [iStream streamError]
}
}
// Either the stream ran out of data or there was an error
Using stream events requires setting the delegate and adding the stream to a run loop. Instead of blocking the thread, the stream will send a stream:handleEvent:
message to its delegate when certain events occur, including when it receives data. The delegate can then retrieve the data from the stream. Here is an example stream:handleEvent:
method:
- (void)stream:(NSInputStream *)iStream handleEvent:(NSStreamEvent)event {
BOOL shouldClose = NO;
switch(event) {
case NSStreamEventEndEncountered:
shouldClose = YES;
// If all data hasn't been read, fall through to the "has bytes" event
if(![iStream hasBytesAvailable]) break;
case NSStreamEventHasBytesAvailable: ; // We need a semicolon here before we can declare local variables
uint8_t *buffer;
NSUInteger length;
BOOL freeBuffer = NO;
// The stream has data. Try to get its internal buffer instead of creating one
if(![iStream getBuffer:&buffer length:&length]) {
// The stream couldn't provide its internal buffer. We have to make one ourselves
buffer = malloc(BUFFER_LEN * sizeof(uint8_t));
freeBuffer = YES;
NSInteger result = [iStream read:buffer maxLength:BUFFER_LEN];
if(result < 0) {
// error copying to buffer
break;
}
length = result;
}
// length bytes of data in buffer
if(freeBuffer) free(buffer);
break;
case NSStreamEventErrorOccurred:
// some other error
shouldClose = YES;
break;
}
if(shouldClose) [iStream close];
}
Use NSOutputstream and NSInputstream to write and read every interval
Looks like NSInputStream and NSOutputStream are not supposed to be re-used after a stream finishes. I fix the problem by changing the code to below:
func start(ipAddress ipAddress: String) {
self.conn = Connection(serverAddress: ipAddress)
self.sendTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "getTimeAndUpdateUI", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.sendTimer!, forMode: NSDefaultRunLoopMode)
}
func getTimeAndUpdateUI() {
self.conn!.connect()
let time = self.conn!.receive()
self.timeLabel.text = "Current time is \(time)"
self.conn!.disconnect()
}
Every time iOS poll the server for information, it recreates an instance of NSInputStream and NSOutputStream.
Any suggestion to improve is welcome. Thanks.
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)")
}
}
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;
}
Related Topics
Accessing and Manipulating Array Item in an Environmentobject
Firestore Security Rules Breaking with Update Rule
Cfdictionary Get Value for Key in Swift3
How to Make a Function with a Loop Asynchronous in Swift
Randomizing Node Movement Duration
How to Group an Array of Image Urls Together in Firebase Realtime Database? (Swift)
How to Use a Specific Gmt for a Function Which Will Be Recognised by Other Time Zones
Contacts Not Recognized When Body Is Changed from Circle to Rectangle
How to Parse JSON in JSON with Swiftyjson
Can't Import Packages Using Swift 4 Package Manager
Nsattributedstring and Emojis: Issue with Positions and Lengths
How to Procedurally Draw Rectangle/Lines in Swift Using Cgcontext
How to Get String from Ascii Code in Swift
How to Convert Unix Timestamp into Swift Nsdate Object