How to use delegate in NSStream? - objective-c

I am a newbie in Objective-C. I am trying to learn how to work with NSStream. I just used simple code from Apple Support. This code should open a stream from a file in my Desktop and show a simple message when the delegate is called by iStream. At the end of the code, I can see the status is correct, but the delegate never gets called. What am I missing?
#import <Foundation/Foundation.h>
#interface MyDelegate: NSStream <NSStreamDelegate>{
}
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode ;
#end
#implementation MyDelegate
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSLog(#"############# in DELEGATE###############");
}
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
MyDelegate* myDelegate=[[MyDelegate alloc]init];
NSInputStream* iStream= [[NSInputStream alloc] initWithFileAtPath:#"/Users/Augend/Desktop/Test.rtf"];
[iStream setDelegate:myDelegate];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[iStream open];
NSLog(#" status:%#",(NSString*) [iStream streamError]);
}
return 0;
}

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.
If you want to be absolutely sure that stream:handleEvent: has been called before exiting, set a (global) flag in that method and put the runUntilDate: in a while loop that tests for the flag:
while( !delegateHasBeenNotified ){
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
}

Related

NSInputStream from URL doesn't seem to be getting anything

I've got the following code, which I know is being run:
ReadDelegate * del = [[ReadDelegate alloc] init];
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)(#"server.com"), port, &readStream, &writeStream);
NSInputStream * readSock = (__bridge_transfer NSInputStream*)readStream;
[readSock setDelegate:del];
[readSock scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
NSOutputStream * writeSock = (__bridge_transfer NSOutputStream*)writeStream;
[writeSock scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
NSLog(#"Open socket");
[readSock open];
[writeSock open];
[writeSock write:(uint8_t*)("request\0\0\0") maxLength:10];
while (YES) {
//I'm skipping over inconsequential stuff
}
NSLog(#"finished reading");
[readSock close];
[writeSock close];
return [del getMessage];
My ReadDelegate class is declared like #interface ReadDelegate : NSObject <NSStreamDelegate> and includes a - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode. That particular function just has a print statement in it to see if it's ever being called. It's not.
I know for a fact that the connection is being opened because my server is receiving the "request\0\0\0" message and the server is sending the file (I have tests in other environments which can receive the file just fine).
However, as mentioned, the ReadDelegate object declared in the beginning (del) never receives the stream message, even once (to say the stream is open or whatever).
Why is the delegate not being called?
It looks like your stream doesn't receive events because of your while loop.
Every new event from a stream can be handled in a new iteration of run loop. But the new iteration can not be started because the current one never finishes.

NSStreamDelegate not receiving messages

I have a TCP connection class which uses the NSStreamDelegate and works fine. It receives messages and respond to them in any matter. In some cases it should open a second connection. This is a second class which is quite similar to the first one.
On open, the first class should wait until the streams report open state:
- (BOOL)connectDataConnection {
__block BOOL connected = YES;
_dataConnection = [JWTCPConnection connectionWithInputStream:(__bridge NSInputStream *)readStream and outputStream:(__bridge NSOutputStream *)writeStream];
[_dataConnection openWithTimeoutBlock:^{
connected = NO;
}];
return connected;
}
// JWTCPConnection
- (id)initWithInputStream:(NSInputStream *)inStream andOutputStream:(NSOutputStream *)outStream {
if (self = [super init]) {
_iStream = inStream;
_oStream = outStream;
[_iStream setDelegate:self];
[_oStream setDelegate:self];
[_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] foreMode:NSRunLoopCommonModes];
[_oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] foreMode:NSRunLoopCommonModes];
}
return self;
}
- (void)openWithTimeoutBlock:(void (^)())timeoutBlock {
_timeoutBlock = timeoutBlock;
float seconds = 5.0;
dispatch_time_t dispatchTime = dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC);
dispatch_queue_t dispatchQueue = dispatch_queue_create("com.company.app", 0);
dispatch_async(dispatchQueue, ^{
dispatch_after(, dispatchTime, dispatchQueue, ^{
if (_timeoutBlock) {
_timeoutBlock();
[self close];
}
dispatch_semaphore_signal(_connectionSemaphore);
});
});
_connectionSemaphore = dispatch_semaphore_create(0);
[_iStream open];
[_oStream open];
dispatch_semaphore_wait(_connectionSemaphore, DISPATCH_TIME_FOREVER);
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
NSLog(#"a stream handels event...");
}
My problem is, that the stream delegate method -stream:handleEvent: does not get called.
I use almost the identical code in my first class, which works. Even when I remove the dispatch_semaphore_wait(); call, the delegate method doesn't fire.
In the case I don't wait, I can write to the stream. But I have to implement a timeout in an asynchronous environment (of the first class).
I call the -openWithTimeoutBlock: method inside the -stream:handleEvent: method of the first class. Could that interrupt the second classes NSStreamDelegate?
Any ideas how to fix that?

manage socket stream in tabBarView

I'm developing an app using UITabBarController. More specifically, using the storyBoard. I want all of my tab to be able to send and receive data from the server.
Problem is i don't know how. Only the first tab that have initNetworkCommunications is able to send and receive from the server. So what should i do in order for my app be to be able to send and receive from the other tabs?
I've found that using NSNotificationCentre to handle the data would work but is there another way?
Here's the code for creating the socket connection
-(void)initNetworkCommunication
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"169.254.1.1", 2000, &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];
}
lets say i have 2 tabs. The first tab has a connect button which is used to call initNetworkCommunication. From this tab i'm able to send and receive data. But what do i do with the other tab? Is there a way to link this connection over?
i've tried to import each other's controller and use [FirstController sendMessage]; from the secondViewController but doesn't seem to work.
Creating a singleton is fine, what I've done is instead of making a class function (which would force your network to re-init the connection every time you switch tabs) I make the networkconnector a property on a custom implementation of tabBar:
#import <Foundation/Foundation.h>
#import "NetworkController.h"
#interface NetworkStorageTabBarController : UITabBarController
#property (nonatomic, strong) NetworkController *thisNetworkController;
#end
And the implementation file:
#import "NetworkStorageTabBarController.h"
#implementation NetworkStorageTabBarController
#synthesize thisNetworkController;
#end
Then when I load up my tabbed view, I call this in viewWillAppear of the first view that will appear:
//set up networking
NetworkStorageTabBarController *thisTabBar = (NetworkStorageTabBarController *) self.tabBarController;
self.thisNetworkController = thisTabBar.thisNetworkController;
self.thisNetworkController.delegate = self;
So far, this has worked gloriously for me. Took me forever to figure it out, so I hope this helps!
The simplest way is to create a Singleton, let's call it NetworkCommunications.
To make it Singleton (only one instance will be created):
+(NetworkCommunications *)sharedManager {
static dispatch_once_t pred;
static NetworkCommunications *shared = nil;
dispatch_once(&pred, ^{
shared = [[NetworkCommunications alloc] init];
});
return shared;
}
Then you simply call [NetworkCommunications sharedManager] from your tabs to get access to that single instance.
You put your network code in that instance as well.

How to get NSTimer to loop

I'm trying to learn about NSTimer, using Foundation and printing to the console. Can anybody tell me what I need to do to get the following to work? It compiles with no errors, but does not activate my startTimer method -- nothing prints.
My aim is to get one method to call another method to run some statements, and then stop after a set time.
#import <Foundation/Foundation.h>
#interface MyTime : NSObject {
NSTimer *timer;
}
- (void)startTimer;
#end
#implementation MyTime
- (void)dealloc {
[timer invalidate];
[super dealloc];
}
- (void)startTimer {
timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(runTimer:) userInfo:nil repeats:YES];
}
- (void)runTimer:(NSTimer *)aTimer {
NSLog(#"timer fired");
}
#end
int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
MyTime *timerTest = [[MyTime alloc] init];
[timerTest startTimer];
[timerTest release];
[pool release];
return 0;
}
The timer never gets a chance to fire in your program, because the program ends almost immediately after the timer is created.
There's a construct called the Run Loop which is responsible for processing input, including input from timers. One run loop is created for each thread, but it isn't automatically started in this case.
You need to run the run loop and keep it going until the timer has a chance to fire. Fortunately, this is quite easy. Insert:
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
between sending startTimer and release to timerTest. If you want the timer to repeat, you'll need to continue keeping the run loop active.
Note that you only need to do that in a simple program like this; when you are creating an application with a GUI, the run loop will be started via the Cocoa application setup process, and will remain active until the application terminates.
You have to add your timer to the default runloop after initializing it:
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
Put this in -(void)startTimer.

NSStreamEventEndEncountered not happen for NSInputStream when read content from file

I created a NSInputStream to load content from a file(IOS):
NSString* fileName = [[NSBundle mainBundle] pathForResource:#"resource" ofType:#".dat"];
NSInputStream* dataStream = [NSInputStream inputStreamWithFileAtPath:fileName];
if (dataStream == nil) {
NSLog(#"load asset failed");
return;
}
[dataStream setDelegate:self];
[dataStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[dataStream open];
Then, add event handler:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventEndEncountered: {
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
break;
}
}
}
I want to catch the event:NSStreamEventEndEncountered, but not happend. I can only catch NSStreamEventOpenCompleted and NSStreamEventHasBytesAvailable.
Anything wrong? Thanks for any help!
I can't see anything wrong with the code you've posted. Make sure that when you're finished with the stream that you are closing it yourself rather than simply relying on getting an NSStreamEventEndEncountered notification, which you can do simply with something like this:
- (void) disconnect {
// Close all open streams
[inputStream close];
[outputStream close];
}
You'll usually only get NSStreamEventEndEncountered if the connection is closed by the other end of the stream, which depending on what you're doing may be beyond your control.
I just ran in to this. Replace NSStreamEventEndEncountered with 4 in the switch/case statement.
NSStreamEventEndEncountered as an NSStream enum doesn't end up being caught in a case statement.
None of these answers are correct. To trigger the NSStreamEventEndEncountered event, you must attempt to read data from the input stream when there is no data to read (in other words, when the paired output stream stops writing data).