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
Related
Though AFMultipartBodyStream of AFNetworking lib is a subclass of NSStream conforming to NSStreamDelegate protocol, it could not be processed as with standard way of a regular NSStream. Namely, AFMultipartBodyStream could not be handled with stream event. I looked into the code of AFMultipartBodyStream, and found that it intentionally disabled the scheduleInRunLoop method of NSInputStream abstract class:
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{}
Any specific reason? Is it a way to make it support the standard stream event mechanism so that the stream data handling can be done asynchronously with stream:handleEvent: event handler?
After studying the implementation of AFMultipartBodyStream, I noticed that the way of current implementation could not support the asynchronous way of regular stream IO handling. I then enhanced AFMultipartBodyStream to provide a stream which was connected with the internal multipart data structure, and thus the holder of this AFMultipartBodyStream can handle the multipartbody data as the regular stream which can be scheduled in the runloop. below code snippet shows the main idea:
-(NSInputStream *)inputStream {
// If _inputStream has not been connected with HTTPBodyParts data, establish the connection
if (!_inputStream) {
NSParameterAssert([self.HTTPBodyParts count] != 0);
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFIndex bufferSize = self.contentLength;
CFStreamCreateBoundPair(NULL, &readStream, &writeStream, bufferSize);
_inputStream = (__bridge_transfer NSInputStream *)readStream;
_outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[_outputStream setDelegate:self];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(#"\n====in async block of inputStream....====\n");
[self->_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self open];
[self->_outputStream open];
NSInteger totalBytesSent = self.contentLength;
while (totalBytesSent > 0 && [self->_outputStream hasSpaceAvailable]) {
uint8_t buffer[1024];
NSInteger bytesRead = [self read:buffer maxLength:1024];
totalBytesSent -= bytesRead;
NSLog(#"\n====buffer read (%ld): [%s]====\n", (long)bytesRead, buffer);
if (self.streamError || bytesRead < 0) {
break;
}
NSInteger bytesWritten = [self->_outputStream write:buffer maxLength:(NSUInteger)bytesRead];
if (self->_outputStream.streamError || bytesWritten < 0) {
NSLog(#"\n====Socket write failed[%#]====\n", self->_outputStream.streamError);
break;
}
if (bytesRead == 0 && bytesWritten == 0) {
break;
}
}
[self->_outputStream close];
[self->_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
});
}
return _inputStream;
}
// Added
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{
// Setup the input stream for body stream data consuming
NSInputStream *inputStream = [self inputStream];
NSParameterAssert(inputStream == self.inputStream);
[inputStream setDelegate:self.delegate_];
[inputStream scheduleInRunLoop:aRunLoop forMode:mode];
[inputStream open];
}
// Added
- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
forMode:(__unused NSString *)mode
{
if (_inputStream) {
[_inputStream setDelegate:[self delegate]];
[_inputStream removeFromRunLoop:aRunLoop forMode:mode];
[_inputStream close];
}
}
currently I am wanting to program a chat server. I am having trouble really understanding the documentations though.
Currently, this is my code, basically extracted from the developer library:
#import "ServerSide.h"
#implementation ServerSide
#synthesize socket;
#synthesize socketAddress;
#synthesize handleConnect;
#synthesize portNumber;
- (id)init{
self = [super init];
if (self) {
self.socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, handleConnect, NULL);
}
return self;
}
- (void) bind {
memset(&socketAddress, 0, sizeof(socketAddress));
socketAddress.sin_len = sizeof(socketAddress);
socketAddress.sin_family = AF_INET; /* Address family */
socketAddress.sin_port = htons(self.portNumber); /* Or a specific port */
socketAddress.sin_addr.s_addr= INADDR_ANY;
CFDataRef sincfd = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&socketAddress, sizeof(socketAddress));
CFSocketSetAddress(socket, sincfd);
CFRelease(sincfd);
}
- (void) listen {
CFRunLoopSourceRef socketsource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), socketsource, kCFRunLoopDefaultMode);
CFSocketGetNative(socket);
}
Now, 'handleconnect' is a CFSocketCallBack ivar with no initialization.
Now I have seen others use different implementations to creating a socket server but this was the one from the docs and I would like to build on top of that.
I am currently attempting to connect to the server but it looks like this doesn't even open a socket. I can't seem to connect to it through terminal using telnet localhost 'port#' either.
Heres the client implementation:
#import "Client.h"
#implementation Client
#synthesize host;
#synthesize port;
#synthesize readStream;
#synthesize writeStream;
#synthesize inputStream;
#synthesize outputStream;
- (void)setup {
NSLog(#"Setting up connection to %# : %i", [[NSURL URLWithString:host] absoluteString], port);
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)[[NSURL URLWithString:host] host], port, &readStream, &writeStream);
if(!CFWriteStreamOpen(writeStream)) {
NSLog(#"Error, writeStream not open");
return;
}
}
- (void)open {
NSLog(#"Opening streams.");
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];
}
- (void)close {
NSLog(#"Closing streams.");
[inputStream close];
[outputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream setDelegate:nil];
[outputStream setDelegate:nil];
inputStream = nil;
outputStream = nil;
}
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event {
NSLog(#"Stream triggered.");
switch(event) {
case NSStreamEventHasSpaceAvailable: {
if(stream == outputStream) {
NSLog(#"outputStream is ready.");
}
break;
}
case NSStreamEventHasBytesAvailable: {
if(stream == inputStream) {
NSLog(#"inputStream is ready.");
uint8_t buf[1024];
unsigned int len = 0;
len = (int)[inputStream read:buf maxLength:1024];
if(len > 0) {
NSMutableData* data=[[NSMutableData alloc] initWithLength:0];
[data appendBytes:(const void *)buf length:len];
[self readIn:[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]];
data = nil;
}
}
break;
}
default: {
NSLog(#"Stream is sending an Event: %lu", event);
break;
}
}
}
- (void)readIn:(NSString *)s {
NSLog(#"Reading in the following:");
NSLog(#"%#", s);
}
- (void)writeOut:(NSString *)s {
uint8_t *buf = (uint8_t *)[s UTF8String];
[outputStream write:buf maxLength:strlen((char *)buf)];
NSLog(#"Writing out the following:");
NSLog(#"%#", s);
}
#end
I run the server on a specified port, then tell the client to connect to the specified host and port number.
But now how do I pass data in the socket I have opened...
I don't expect some large explanation, but if someone could give me a better understanding of what needs to be done to further this implementation, I'd greatly appreciate it.
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.
What I am trying to do: I have a url request (post) where I send some information to a api server which then starts streaming data in bytes to me.
1) How do I post data when trying to set up a stream as right now I am just using a url, can I incorporate a NSURLRequest some how?
2) Why isnt my stream even opening (streamStatus returns 0) and thus - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode never being called? , this is my best attempt and for the most part following this Guide
- (void)setUpStreamFromURL:(NSURL *)path {
// iStream is NSInputStream instance variable
iStream = [[NSInputStream alloc] initWithURL:path];
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[iStream open];
NSLog(#"Stream Open: %lu",[iStream streamStatus]); //return 0
}
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSLog(#"Streaming");
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if(!_data) {
_data = [[NSMutableData data] init];
}
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)stream read:buf maxLength:1024];
if(len) {
[_data appendBytes:(const void *)buf length:len];
NSLog(#"DATA BEING SENT : %#", _data);
// bytesRead is an instance variable of type NSNumber.
// [bytesRead setIntValue:[bytesRead intValue]+len]; //getting error that setInt value is not part of NSNumber, and thats true so not sure what to do about it, but this isn't the issue.
} else {
NSLog(#"no buffer!");
}
break;
}
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
stream = nil; // stream is ivar, so reinit it
break;
}
// continued ...
}
}
also incase it helps, my header file:
#import <Foundation/Foundation.h>
#import "Login.h"
#interface Stream : NSStream <NSStreamDelegate> {
NSMutableArray *searchIdList;
NSInputStream *iStream;
NSNumber *bytesRead;
}
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode ;
-(id)initWithLoginObject:(Login *)log;
#property NSMutableData *data;
#end
You can't use NSURLRequest in Stream.
To create a request you can use this.
request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("POST"),(CFURLRef) url, kCFHTTPVersion1_1);
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("User-Agent"), CFSTR("MSControl-US"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Content-Type"), CFSTR("application/json"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Keep-Alive"));
After that you can create the stream using
readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request)
or if you already have an opened stream you can serialize your request With :
NSData *data = (NSData *)CFHTTPMessageCopySerializedMessage(request);
and send with This:
length = [data length];
CFWriteStreamWrite((CFWriteStreamRef)outStream,data,lenght);
Hope this help
I'm trying to learn how to use the NSInputStream class on the iPhone using a unit test. I can get the NSStream to read data from a file using the polling method but for some reason the delegate/event method is not working.
I've posted the relevant code below. Please ignore memory leak errors and such since I'm just trying to ensure I know how to use the NSStream class in a sandboxed environment before rolling it into my larger project.
I'm wondering if maybe I'm missing something with regards to how the run loops work?
This is the logic test that creates a streamer class to read from a file.
#import "StreamingTests.h"
#import "Streamer.h"
#implementation StreamingTests
- (void) testStream {
NSLog(#"Starting stream test.");
Streamer * streamer = [[Streamer alloc] init];
streamer.usePolling = NO;
streamer.readingStream = YES;
NSThread * readThread = [[NSThread alloc] initWithTarget:streamer selector:#selector(startStreamRead:) object:nil];
[readThread start];
while(streamer.readingStream) {
[NSThread sleepForTimeInterval:0.5];
}
[readThread cancel];
}
#end
This is a simple test helper object that reads from an NSStream. When usePolling == YES it read data and outputs the appropriate NSLog messages. However, if usePolling == NO the delegate stream event function is never called.
#implementation Streamer
#synthesize readingStream, usePolling;
- (void) startStreamRead:(NSObject*) context {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSLog(#"starting stream read.");
readingStream = YES;
/*
NSURL * url = [NSURL URLWithString:#"http://www.google.com"];
NSLog(#"Loading: %#",[url description]);
NSInputStream * inStream = [[NSInputStream alloc] initWithURL:url];
*/
NSInputStream * inStream = [[NSInputStream alloc] initWithFileAtPath:#"sample.ttc"];
if(!usePolling) {
[inStream setDelegate: self];
[inStream scheduleInRunLoop: [NSRunLoop currentRunLoop]
forMode: NSDefaultRunLoopMode];
}
[inStream open];
if(usePolling) {
while(1) {
if([inStream hasBytesAvailable]) {
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)inStream read:buf maxLength:1024];
NSLog(#"Read: %d",len);
}
NSStreamStatus status = [inStream streamStatus];
if(status != NSStreamStatusOpen && status != NSStreamStatusOpening) {
NSLog(#"Stream not open.");
break;
}
}
readingStream = NO;
NSStreamStatus status = [inStream streamStatus];
NSError * error = [inStream streamError];
NSLog(#"Status: %d Error Desc: %# Reason: %#",(int)status,[error localizedDescription], [error localizedFailureReason]);
[pool release];
}
}
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSMutableData * _data = nil;
NSNumber * bytesRead = nil;
NSLog(#"Event fired.");
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if(!_data) {
_data = [[NSMutableData data] retain];
}
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)stream read:buf maxLength:1024];
if(len) {
[_data appendBytes:(const void *)buf length:len];
// bytesRead is an instance variable of type NSNumber.
//[bytesRead setIntValue:[bytesRead intValue]+len];
NSLog(#"Read %d bytes",len);
} else {
NSLog(#"no buffer!");
}
break;
}
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[stream release];
stream = nil; // stream is ivar, so reinit it
readingStream = NO;
break;
}
default:
{
NSLog(#"Another event occurred.");
break;
}
// continued ...
}
}
#end
Thanks in advance,
b
The reason for it should be that the run loop is blocked since the unit test is executing. You could refer to the NSRunLoop documentation where the method
runUntilDate:
might help you to run the main run loop in the thread of execution of the unit test like this:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
This lets the run loop run for 1 second giving it time to process part of your file. It should be noted that this does not provide a reliable way for unit testing (since the time interval might differ depending on run loop size) and may then be unsuitable. By giving your unit an interface that could be used to check the status of the input stream read operation (with a reading finished state) such as
-(BOOL)hasFinishedReadingFile
the unit test could repeatedly execute the run loop until the above method returns TRUE and the file is read completely.
Addition: This question on stackoverflow also deals with the problem in a different way.