4

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!

Jarmez De La Rocha
  • 618
  • 2
  • 9
  • 19
  • 1
    Can you post the stack trace for the crash? An EXC_BAD_ACCESS crash typically indicates a memory management error. Using the NSZombies instrument might help you track the problem down. – Andrew Madsen Oct 03 '13 at 16:43
  • With Zombies enabled i get this extra message in my output 2013-10-03 17:48:43.989 MMORPG[6332:70b] *** -[Client respondsToSelector:]: message sent to deallocated instance 0x8ecffc0 Could this be something to do with the NSStreamDelegate which my object implements? – Jarmez De La Rocha Oct 03 '13 at 16:53
  • 1
    Yes, if your NSStream's delegate (a Client instance) is deallocated while the stream is still around and open, you'd get a crash like you describe. You need to make sure something has a strong reference to the client, or else make sure you close and release the stream before Client is deallocated. – Andrew Madsen Oct 03 '13 at 17:11
  • How very stupid of me! Good call, was missing a strong reference in my view controller. I really do not know how i overlooked that!! – Jarmez De La Rocha Oct 03 '13 at 17:17
  • Glad to hear it. I turned my comment into a real answer, in case you'd like to accept it. – Andrew Madsen Oct 03 '13 at 17:19

1 Answers1

9

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.

Andrew Madsen
  • 21,309
  • 5
  • 56
  • 97