Does anyone know of a simple example that creates (network-based) NSStreams on a separate thread?
What I am actually trying to do is unschedule (from the main thread) and reschedule (to a helper/network thread) an open NSInputStream and NSOutputStream that I am receiving from a third party framework (see Can an open, but inactive, NSStream that is scheduled on the main thread be moved to a different thread?). Nobody has answered that question thus far, so I am trying to do this myself to see if it could be made to work.
To test what is possible, I am trying to modify the code here (which includes a iOS client and a very short, python-based server [awesome!]):
http://www.raywenderlich.com/3932/how-to-create-a-socket-based-iphone-app-and-server
so that after the NSInputStream and NSOutputStream is created and opened I attempt to move it onto a helper thread.
The challenge that I am experiencing is that the helper thread does not seem to be responding to delegate messages from the streams or any messages that I am sending via:
performSelector:onThread:withObject:waitUntilDone:modes:. I suspect that I am doing something wrong with setting up the helper thread's NSRunLoop (see networkThreadMain below).
So, [ChatClientViewController viewDidLoad] now looks like:
- (void)viewDidLoad {
[super viewDidLoad];
[self initNetworkCommunication];
[self decoupleStreamsFromMainThread];
[self spoolUpNetworkThread];
inputNameField.text = #"cesare";
messages = [[NSMutableArray alloc] init];
self.tView.delegate = self;
self.tView.dataSource = self;
}
With these implementations:
- (void) initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"localhost", 80, &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];
}
- (void) decoupleStreamsFromMainThread
{
inputStream.delegate = nil;
outputStream.delegate = nil;
[inputStream removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
[outputStream removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
}
- (void) spoolUpNetworkThread
{
[NSThread detachNewThreadSelector: #selector(networkThreadMain) toTarget: self withObject: nil];
}
- (void) networkThreadMain
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
static dispatch_once_t once;
dispatch_once(&once, ^{
[self rescheduleThreads];
((ChatClientAppDelegate * )[[UIApplication sharedApplication] delegate]).networkThread = [NSThread currentThread];
[inputStream setDelegate:self];
[outputStream setDelegate:self];
NSLog(#"inputStream status is: %#", [NSInputStream streamStatusCodeDescription: [inputStream streamStatus]]);
NSLog(#"outputStream status is: %#", [NSOutputStream streamStatusCodeDescription: [outputStream streamStatus]]);
[[NSRunLoop currentRunLoop] runUntilDate: [NSDate distantFuture]];
});
[pool release]; // Release the objects in the pool.
}
- (void) rescheduleThreads
{
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
Any ideas on what might be wrong? Thanks in advance!
Something along the lines of the code in the question below works well ( I have similar code in my app):
How to properly open and close a NSStream on another thread
Related
I created NSStreams to transfer something using the below code:
dispatch_async(dispatch_get_main_queue(), ^{
// open input
[self.inputStream setDelegate:controller];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
// open output
[self.outputStream setDelegate:controller];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream open];
});
At some point, I want to close the streams using the below code:
// Shut down input
[self.inputStream close];
self.inputStream = nil;
// Shut down output
[self.outputStream close];
self.outputStream = nil;
NSLog(#"input streams status:%i", [self.inputStream streamStatus]);
NSLog(#"output streams status:%i", [self.outputStream streamStatus]);
NSLog(#"input streams status:%#", [self.inputStream streamError]);
NSLog(#"output streams status:%#", [self.outputStream streamError]);
All input/output streams are strong properties.
Stream status return zero, but my app hangs. When I put device in debug mode, the monitor shows that my device CPU start running in 98+% after close method called. It seems like it's waiting for something to finish, but I still don't know what that is.
Does anyone know what may cause this issues?
Try removing the streams from the run loop.
// Shut down input
[self.inputStream close];
[self.inputStream removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
self.inputStream = nil;
// Shut down output
[self.outputStream close];
[self.outputStream removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
self.outputStream = nil;
More information can be found here.
I create a MySocketClient class which implements NSStreamDelegate, and implements the method - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
and then call the bellow function to connect the server:
- (void)connectToHost:(NSString *)host onPort:(UInt32)port{
NSLog(#"will connect...");
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
_inputStream = (__bridge NSInputStream *)readStream;
_outputStream = (__bridge NSOutputStream *)writeStream;
_inputStream.delegate = self;
_inputStream.delegate = self;
[_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream open];
[_outputStream open];
}
It is great to connect my server, but the delegate method:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
not called.
The 'connectToHost' is called in the method - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions of AppDelegate.m file as bellow shown:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GoEasySocketClient *client = [[GoEasySocketClient alloc] init];
[client connectToHost:#"myhost" onPort:myport];
});
But the strange thing is if I use the AppDelegate as the NSStreamDelegate, and implement the method - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
and when I call connectToHost method, the delegate method - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode will be called and the eventCode is NSStreamEventOpenCompleted.
As you use the second thread you should run your own run loop according to apple documentation. So in your case it should be as follows:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
GoEasySocketClient *client = [[GoEasySocketClient alloc] init];
[client connectToHost:#"myhost" onPort:myport];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
Or you can use [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]] instead of [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]].
The only difference is that the first one "returns after either the first input source is processed or limitDate is reached" and
other one "runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate: until the specified expiration date".
I’ve tried the first one with NSURLConnection and it works perfectly.
You can also use main thread’s run loop. In this case you should change code as follows:
[_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
Regarding your question:
But the strange thing is if I use the AppDelegate as the
NSStreamDelegate, and implement the method - (void)stream:(NSStream
*)aStream handleEvent:(NSStreamEvent)eventCode and when I call connectToHost method, the delegate method - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode will be called and the eventCode is NSStreamEventOpenCompleted.
It happens because you call connectToHost method from the main thread with its own run loop. This run loop starts automatically during app launch.
I am working through a tutorial for a chat messaging app. I typed in the code in the ViewController.m but I am getting these error messages:
Parse Issue Expected identified or ‘(‘
(for the 1st bold line in the code below) and
Semantic Issue Method definition for ‘initNetworkCommunication’ not
found
(for the 2nd bold line in the code below)
How can I resolve these issues? Note: I am using Xcode 6.4
#import "ViewController.h"
#interface ViewController ()
**- (void)initNetworkCommunication; {**
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)# 192.168.99.2, 80, &read-Stream,&writeStream);
inputStream = (NSInputStream *)readStream;
outputStream = (NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoop-Mode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode: NSDefaultRunLoop-Mode];
[inputStream open];
[outputStream open];
[self initNetworkCommunication];
}
#end
**#implementation ViewController**
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)joinChat:(id)sender {
}
#end
Make this:
- (void)initNetworkCommunication; {
This:
- (void)initNetworkCommunication {
And move this:
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)# 192.168.99.2, 80, &read-Stream,&writeStream);
inputStream = (NSInputStream *)readStream;
outputStream = (NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoop-Mode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode: NSDefaultRunLoop-Mode];
[inputStream open];
[outputStream open];
[self initNetworkCommunication];
}
Below this:
#implementation ViewController
You want to take out the semi-colon in this line:
- (void)initNetworkCommunication; {
It's just not proper syntax having that there.
I'm building an application that interfaces with external accessories (thus with the ExternalAccessory framework).
Part of this interfacing is waiting for events to occur on the in/out streams to the external device.
For that, I use a thread, and block it in the NSRunLoop to make sure it keeps waiting for events.
- (void)eventThreadSelector
{
// Setting up the environment
self.session = <got the EASession>;
self.inputStream = [self.session inputStream];
self.inputStream.delegate = self;
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
self.outputStream = [self.session outputStream];
self.outputStream.delegate = self;
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream open];
// Waiting for events coming from the NSStreams
while (![[NSThread currentThread] isCancelled]) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
}
// When the device is unplugged,
[self.inputStream close];
[self.inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.inputStream.delegate = nil;
self.inputStream = nil;
[self.outputStream close];
[self.outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
self.outputStream.delegate = nil;
self.outputStream = nil;
self.session = nil;
}
- (void)accessoryDidDisconnect:(EAAccessory *)accessory
{
DDLogVerbose(#"EID: Got accessoryDidDisconnect notification on main thread? %i", [[NSThread currentThread] isMainThread]);
[self.eventThread cancel];
self.eventThread = nil;
}
So far, all of this works great :) But there's one thing I can't wrap my head around.
When I unplug the external device, the processor briefly jumps to 100% and above. I've done a little bit of investigation, and this seems to be linked to the NSRunLoop going nuts. That CPU peak has a negative impact on the UI :(
How can I avoid this processor usage peak?
I am newbie in ios development, I am doing an application using tcp socket, and followed several tutorials and everything related to the connection works great. but the ui freezes until my authentication is completed. for this use "dispatch_queue" follows
-(BOOL)Connect {
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^ {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)IP,PORT, &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];
[[NSRunLoop currentRunLoop] run];
});
return true;
}
up here, the connection works fine and does not lock. now my problem is that when I write something on the outputstream not send anything.
from another class by pressing a button I need to send something to the server, the function is called but does not send nothing, because it is in another thread I think.
-(void)Send:(NSMutableData *)_output{
[outputStream write:[_output bytes] maxLength:[_output length]];
}
How I can send something in outputstream if it is in another thread?
excuse my spelling, I do not speak much English
reading some forums, I found that NSThreads can pass data between them with:
performSelector:onThread:withObject:waitUntilDone:
but i don't know how I can create an instance of thread that is already running. Someone can help me?
for NS Thread just add all the above code in a function and call it like this:
[self performSelectorOnMainThread:#selector(TcpSocketFunction) withObject:nil waitUntilDone:NO];
Where TcpSocketFunction is the function name with a defination like.
- (void) TcpSocketFunction {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)IP,PORT, &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];
[[NSRunLoop currentRunLoop] run];
}