NSStream and Sockets, NSStreamDelegate methods not being called - objective-c

I've followed the guide Setting Up Socket Streams and have effectively duplicated that code in my class. No matter what I try the delegate methods just don't seem to get called.
In the header file I have (basically):
#interface myClass : NSObject <NSStreamDelegate> {
NSInputStream *inputStream;
NSOutputStream *outputStream;
}
- (void)connect;
#end;
The connect method:
- (void)connect {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)#"host.example.com", 1234, &readStream, &writeStream);
inputStream = (NSInputStream *)readStream;
outputStream = (NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
Also tried using CFStreamCreatePairWithSocketToCFHost() and [NSStream getStreamsToHost:port:inputStream:outputStream: - all with exactly the same result.
I've set a breakpoint at the beginning of the connect method, stepped through every line and every pointer is valid and seems to point to the correct object.
In GDB, after the setDelegate calls, po [inputStream delegate] prints <myClass: 0x136380> as expected, so it has set the delegate correctly.
For the life of me I can't work out why it refuses to call the stream:handleEvent: method on my class:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
NSLog(#"got an event");
}
Hopefully I've missed something really simple and obvious and a second pair of eyes can spot my mistake.
Thanks in advance to anyone who has the patience and taken the time to read this far!

In the lines like this:
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
Instead of using [NSRunLoop currentRunLoop] I changed it to [NSRunLoop mainRunLoop].
EDIT 2011-05-30:
The reason this did not work is because I was setting up the sockets in a background thread via +[NSThread detachNewThreadSelector:toTarget:withObject:].
Doing it that way created a new run loop, which after reading the run loop developer documentation I discovered that you need to tell the NSRunLoop to run manually.
Running it in the same run loop as the main thread was fine on performance, though I was able to squeeze a bit more performance out by writing a wrapper class and running all network I/O on a background thread.

That solution will work if & only if you don't have blocking work on thread 0. This is often OK, but a better solution is to create a new thread (i.e. using a class method to create the thread on demand) and then enqueue on that thread.
i.e.
+ (NSThread *)networkThread {
static NSThread *networkThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
networkThread =
[[NSThread alloc] initWithTarget:self
selector:#selector(networkThreadMain:)
object:nil];
[networkThread start];
});
return networkThread;
}
+ (void)networkThreadMain:(id)unused {
do {
#autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
} while (YES);
}
- (void)scheduleInCurrentThread
{
[inputstream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
}
With this, you can schedule the input stream using:
[self performSelector:#selector(scheduleInCurrentThread)
onThread:[[self class] networkThread]
withObject:nil
waitUntilDone:YES];
This will allow you to run your network operations without worrying about deadlocks anymore.

Related

socket communication at separate thread

I already read 40 pages about threading, but still not sure about my case.
I have an NSObject, that open a socket connection to a device. This Object handle all the communication, sending and receiving messages and so on. I would like, that this stuff works on separate thread. I tried something like this:
- (IBAction)connect:(id)sender {
SocketConnectionController *sock = [SocketConnectionController new];
[[sock initWithParams:ip.text :port.text.intValue] performSelectorInBackground:#selector(initWithParams::) withObject:nil];
[[SocketConnectionController sharedInstance] sendCommand:GET_ID_STRING];
}
As you see, I am sending some message by using the existing instance of SocketConnectionController, but it doesn't send anything. There maybe some leak of understanding my side. I am sure, that the connection is open because of flashing lights on device. Am I creating the thread on the right way? If so, how can I use it now?
1. UPDATE:
I tried something like this:
NSOperationQueue* queue = [NSOperationQueue new];
SocketConnection *sock = [SocketConnection new];
[queue addOperation:sock];
but at the CPU graph I see, that the stuff still running on Thread 1 (mainThread).
What am I doing wrong?
2. UPDATE
I found out, that the run loop, that I need for the Input and Output Stream still running on the main Thread.
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
That's why the code of 1. Update don't work. So I need to create a new Thread and then a new run loop for this thread. That can not be done automatically by Cocoa-Framework like in the code before (performSelectorInBackground).
I found the way to perform the socket connection on the separate thread:
NSThread * nThread;
- (NSThread *)networkThread {
nThread = nil;
nThread =
[[NSThread alloc] initWithTarget:self
selector:#selector(networkThreadMain:)
object:nil];
[nThread start];
NSLog(#"thread: %#, debug description: %#", nThread, nThread.debugDescription);
return nThread;
}
- (void)networkThreadMain:(id)unused {
do {
[[NSRunLoop currentRunLoop] run];
} while (YES);
}
- (void)scheduleInCurrentThread:(id)unused
{
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
}
-(BOOL) connectWithError:(NSString **) e
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"127.0.0.1", [server port], &readStream, &writeStream);
inputStream = (__bridge NSInputStream *) readStream;
outputStream = (__bridge NSOutputStream *) writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[self performSelector:#selector(scheduleInCurrentThread:)
onThread:[self networkThread]
withObject:nil
waitUntilDone:YES];
[inputStream open];
[outputStream open];
}
All you need is to input this code in the new class of type NSThread.

NSOutputStream crashing with Bad Access after write (Objective-c)

I have been trying to get a basic TCP client up and running for my iOS application but have run into a block which i cannot seem to work my head around.
So far i can connect, send a message which is received on the server side but then my app crashes.
Client.h
#import <Foundation/Foundation.h>
#interface Client : NSObject <NSStreamDelegate>
{
NSInputStream *inputStream;
NSOutputStream *outputStream;
}
-(void)initNetworkCommunication;
-(void)send:(NSString*)message;
#end
Client.m
#import "Client.h"
#implementation Client
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"10.0.1.51", 7769, &readStream, &writeStream);
inputStream = ( NSInputStream *)CFBridgingRelease(readStream);
outputStream = ( NSOutputStream *)CFBridgingRelease(writeStream);
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
- (void)send:(NSString*)message
{
NSData *data = [[NSData alloc] initWithData:[message dataUsingEncoding:NSUTF8StringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
NSLog(#"stream event %i", streamEvent);
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
if (theStream == inputStream) {
uint8_t buffer[1024];
int len;
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
if (nil != output) {
NSLog(#"server said: %#", output);
}
}
}
}
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
NSLog(#"End Encountered!");
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
theStream = nil;
break;
default:
NSLog(#"Unknown event");
}
}
#end
The output in the console is
2013-10-03 17:01:38.542 MMORPG[6076:70b] stream event 1
2013-10-03 17:01:38.543 MMORPG[6076:70b] Stream opened
2013-10-03 17:01:43.495 MMORPG[6076:70b] stream event 4
2013-10-03 17:01:43.495 MMORPG[6076:70b] Unknown event
It seems like, my message is sent, i receive stream event #4 and then i get a bad access crash. The problem is i have no idea what its having trouble accessing?
Any help would be greatly appreciated!
The problem is that NSStream keeps an assign/unsafe_unretained reference to its delegate. If the delegate is released before the stream is closed and released, the stream will try to call methods on its now deallocated delegate, causing a crash. The solution is to either make sure some other object has a strong reference to the client, preventing its early deallocation, or else make sure you close and release the stream before its delegate is deallocated.

accessing method from one class to another that receives message from server

i will explain the best i can if u need further explanation or detail please comment..
I am having a local python server running on my computer that gives messages to client based on the request.
I have a MAC client application in which i have 3 classes -
Chatlogic class- this class initialises the connection and sends and receives the messages from the server.
Login class - this maintains the login of user to the application , in this class i have a instance of the Chatlogic class, i can send messages through the object of the chatlogic class like this [chatLogicObject sendmessage:something]
My problem is this = When ever i receive it comes in the chatLogic class instance and not in the LoginClass so i have a method called -(void)messageReceived in login class that should override the same method in the chatLogic class (But this does not work).
How can i receive the method in the Loginclass ?
To avoid confusion i have added my chatlogic class
#import <Foundation/Foundation.h>
#interface chatLogic : NSObject <NSStreamDelegate>
#property (copy)NSOutputStream *outputStream;
#property (copy)NSInputStream *inputStream;
#property (copy)NSString *streamStatus;
-(void)initNetworkCommunications;
-(void)sendMessage:(id)sender;
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)streamEvent;
-(void)messageReceived:(NSString*)message; //i want this method to be used in some other classes
#end
The implementation file is as follows
import "chatLogic.h"
#implementation chatLogic
#synthesize inputStream ;
#synthesize outputStream ;
#synthesize streamStatus;
-(id)init{
if (self == [super init]) {
}
return self;
}
-(void)initNetworkCommunications{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"192.168.1.22", 80, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)streamEvent{
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
streamStatus = [[NSString alloc]initWithFormat:#"Stream opened"];
break;
case NSStreamEventHasBytesAvailable:
if (aStream == inputStream) {
uint8_t buffer[1024];
int len;
while ([inputStream hasBytesAvailable]) {
len = (int)[inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
NSString *output = [[NSString alloc]initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
if (nil != output) {
NSLog(#"server said: %#",output);
[self messageReceived:output];
}
}
}
}
case NSStreamEventErrorOccurred:
{
NSLog(#"cannot connect to the host!");
break;
}
case NSStreamEventEndEncountered:
{
break;
}
default:
{
NSLog(#"unknown event!!");
}
}
}
-(void)sendMessage:(id)sender{
}
-(void)messageReceived:(NSString*)message{
NSLog(#"the received message in chat logic class");
}
-(void)dealloc{
[inputStream close];
[outputStream close];
[inputStream release];
[outputStream release];
[super dealloc];
}
#end
I think you want to receive same message in more than one class instances. The best way to do is using NSNotificationCentre. As soon as the message is received in one class you can post a notification which in turn will be heard by all the listeners who have registered for that notification.e
Delegation is also fine but is usually used when you are sure who will be the listeners(I am not sure what is happening in your case.). Hoping this helps.
Update:
Yes, you can send an object as well which can contain some information.. you can see how to post notification with objects using this link https://stackoverflow.com/a/4127535/919545
If I understood everything correctly, you are talking about delegation here.
You should define a ChatLogicDelegate protocol with a method like ( -(void)didReceiveMessage:(NSString*)message ) , add a delegate property to the ChatLogic class and when instantiating it, setting the loginClassObject to be the delegate of chatLogicObject. In the ChatLogic class, call [delegate didReceiveMessage:json] whenever you have to and the loginClassObject is going to get the message.
Apple's documentation on delegation

NSInputStream does not call Delegate (stream:handleEvent:)

I searched the web for a long time...I didn't find an answer for my issue, so I decided to post here.
I try to establish a connection to a NNTP-server using NSStream.
In a test-program, I open the streams and send a message. The delegate-method (stream:handleEvent:) is called twice for the output-stream (NSStreamEventOpenCompleted, NSStreamEventHasSpaceAvailable) but never for the input-stream!
Why does the input stream never call the delegate? Any ideas?
Basically, the code looks like this:
init and open streams:
CFReadStreamRef tmpiStream;
CFWriteStreamRef tmpoStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)SERVER, PORT, &tmpiStream, &tmpoStream);
iStream = (__bridge NSInputStream *) tmpiStream;
oStream = (__bridge NSOutputStream *)tmpoStream;
[iStream setDelegate:self];
[oStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[iStream open];
[oStream open];
send message:
NSData *data = [[NSData alloc] initWithData:[messageString dataUsingEncoding:NSASCIIStringEncoding]];
[oStream write:[data bytes] maxLength:[data length]];
receive messages:
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
NSLog(#"EventCode: %i", eventCode);
//switch-case-statement...(using constants - NSStreamEventOpenCompleted...)
}
The class which contains that code inherits from NSObjects and implements NSStreamDelegate.
(iOS5 with ARC)
Thx for any help!
EDIT:
I just tried "polling" after opening streams like this - it's working:
while (![iStream hasBytesAvailable])
{}
uint8_t buffer[1024];
int len;
NSString *str = #"";
while ([iStream hasBytesAvailable])
{
len = [iStream read:buffer maxLength:sizeof(buffer)];
if (len > 0)
{
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSISOLatin1StringEncoding];
if (output != nil)
{
str = [str stringByAppendingString:output];
}
}
}
NSLog(#"Response: %#", str);
But, for sure, I still need a better (async) solution ;)
I found your answer here, I have the same issue. Your answer worked for me, but I think this works better:
How to use delegate in NSStream?
To quote him:
The run loop isn't running long enough for the delegate method to be
called.
Add:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
right after you open the stream. This is only necessary in a program
without a GUI -- otherwise the run loop would be spun for you.
So mine looks like this:
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:30.0]];
Edited:
Then when you have the data, for example I have all my data complete when NSStreamEventEndEncountered occurs in the delegate method stream:handleEvent I put this:
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
CFRunLoopStop(CFRunLoopGetCurrent());
[theStream close];
Which closes the run loop before the 30.0s or however long you set it. It would be the same to your answer if your code was in NSStreamHasBytesAvailable.
This allows me to still use the delegate methods and keep the run loop open until the download is completed, without skipping the delegate method all together.
Are you letting the run loop run in the default mode?
Try checking the input stream status (-streamStatus).
Is anything logged to the console?
You claim the delegate method is called for the output stream, but your NSLog() call doesn't log the stream. Are you sure?
Finally, this is unrelated, but you should be using a __bridge_transfer cast if you mean to transfer ownership of the owned Core Foundation stream objects to ARC. Even better, use CFBridgingRelease() since that has an obvious symmetry with the Create in the function name. Unless there are CFRelease() calls which you didn't show, then the existing code is leaking those streams.
I had this same issue and then I realized that I accidentally called
[iStream setDelegate:self]
before I created iStream.

NSStream Handle Event Giving Status 4

I was trying on a TCP connection app, and I am getting a NSStreamEvent "4" on handleEvent. What am I doing wrong?
My code is like,
-(void) initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"localhost", 80, &readStream, &writeStream);
inputStream = (__bridge_transfer NSInputStream *)readStream;
outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
- (IBAction)didTapButton:(id)sender {
NSString *response = inputTextField.text;
NSLog(#"%#", response);
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
NSLog(#"Stream has bytes available");
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
NSLog(#"Stream closed");
break;
default:
NSLog(#"Unknown event: %# : %d", theStream, streamEvent);
}
}
The console gives,
2012-05-29 13:37:07.132 GestureTrial[24289:f803] Stream opened
2012-05-29 13:37:07.133 GestureTrial[24289:f803] Stream opened
2012-05-29 13:37:07.133 GestureTrial[24289:f803] Unknown event: <__NSCFOutputStream: 0x6b85c70> : 4
when tried to send a message to server. I tried it with a tcp tester app for Mac, and it's working fine, so might not be a firewall issue. The output is same for device as well as simulator. Any help would be much appreciated.
Actually you're not doing anything wrong.
This event (it is NSStreamEventHasSpaceAvailable) usually occours after writing to the stream telling you that stream is ready for writing again and after opening a writable stream. Please refer to NSStream Class Reference or, to be exact: Stream Event Constants.
If you're not familliar to << operator, it means shift bits to left for n places (each shift equals to multiplying by 2). Translation would be:
typedef enum {
NSStreamEventNone = 0,
NSStreamEventOpenCompleted = 1,
NSStreamEventHasBytesAvailable = 2,
NSStreamEventHasSpaceAvailable = 4,
NSStreamEventErrorOccurred = 8,
NSStreamEventEndEncountered = 16
};
In many applications you will se this event simply ignored (not handled) because it usually occours very soon after writing to the stream. If something goes wrong you get NSStreamEventErrorOccurred or NSStreamEventEndEncountered and these are the ones you need to handle. You could use NSStreamEventHasSpaceAvailable as a flag that it is o.k. to send some more data.
You should also know that both streams (inputStream and outputStream) are calling the same delegate method. That's why you get two NSStreamEventOpenCompleted events to begin with. But again in many cases this shouldnt be a problem. You can always check which stream is the originator of the event if needed.