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?
Related
I'm trying to configure and iOS device to connect to a macOS device over a WiFi network using Bonjour and NSStreams. I'd like to secure the connection via SSL. I started from Apple's Cocoa Echo example code here. It works beautifully without SSL, but when I try to configure a secure connection, I'm getting CFNetwork SSLHandshake failed (-9800) on both the client and server side. Here's my configuration.
macOS (server)
// establish connection
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &readStream, &writeStream);
if (readStream && writeStream) {
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
SQConnection * connection = [[SQConnection alloc] initWithInputStream:(__bridge NSInputStream *)readStream outputStream:(__bridge NSOutputStream *)writeStream];
[self.connections addObject:connection];
[connection open];
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(SQConnectionDidCloseNotification:) name:SQConnectionDidCloseNotification object:connection];
NSLog(#"Added connection.");
}
// open streams (in SQConnection.open)
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
// SSL
[self.inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
[self.outputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
#(FALSE), kCFStreamSSLValidatesCertificateChain,
kCFNull, kCFStreamSSLPeerName,
nil];
CFReadStreamSetProperty((CFReadStreamRef)self.inputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
CFWriteStreamSetProperty((CFWriteStreamRef)self.outputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[self.outputStream open];
iOS (client)
NSInputStream * istream;
NSOutputStream * ostream;
if ([netService qNetworkAdditions_getInputStream:&istream outputStream:&ostream]) {
NSLog(#"OPEN STREAMS");
self.inputStream = istream;
self.outputStream = ostream;
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
[self.inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
[self.outputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[self.outputStream open];
}
My best guess is that I haven't actually configured a certificate to be used for SSL negotiation. This is something I was hoping macOS would take care of automatically, but Apple's docs are woefully incomplete on this. The best I've found is a couple paragraphs here on "Securing and Configuring the Connection."
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'm trying to connect to a server in a custom GCD queue. This is how I'm doing it.
- (void) initNetworkCommunication{
if(!self.connQueue){
self.connQueue = dispatch_queue_create("connection_queue", NULL);
}
dispatch_async(self.connQueue, ^(void) {
if(self.inputStream ==nil && self.outputStream ==nil) {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
NSString *host= [[NSUserDefaults standardUserDefaults] objectForKey:#"ip_preference"];
NSNumber *portNum = [[NSUserDefaults standardUserDefaults] objectForKey:#"port_preference"];
int port = [portNum intValue];
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
self.inputStream = (__bridge NSInputStream *)readStream;
self.outputStream = (__bridge NSOutputStream *)writeStream;
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.outputStream open];
[self.inputStream open];
} else {
NSLog(#"persistant connection alerady opened");
return;
}
});
}
Now, if I write this piece of code in dispatch_sync, it calls delegate function correctly.
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
But, when I use dispatch_asynch, which is what I want to do, it does not call my delegate function.
From what I've read on SO so far, GCD queues have a runloop associated with them but these are not run by the system and we need to do so ourselves. I understand this in theory, but but how is it done. Dispatch sources associated with this somehow?
Thank You in advance.
Add this method after [self.inputStream open];
[[NSRunLoop currentRunLoop]run];
This puts the receiver into a permanent loop, during which time it processes data from all attached input sources.
See apple docs about RunLoops
The other way let run on dispatch_get_main_queue when use dispatch_async();
I have an application that creates a background thread for the network messages. The application works nearly perfectly unless the server it connects to closes the connection. I am unsure of why this happens but any advice is greatly appreciated. I've included the snippets of code that can be followed to the problem. If something is vague or more detail is needed please let me know.
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventErrorOccurred:
{
NSLog(#"NSStreamEventErrorOccurred");
[self Disconnect:self];
}
}
}
- (void)Disconnect:(id)sender {
[self performSelector:#selector(closeThread) onThread:[[self class]networkThread] withObject:nil waitUntilDone:YES];
[outputStream close];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream release];
outputStream = nil;
}
+ (NSThread*)networkThread
{
// networkThread needs to be static otherwise I get an error about a missing block type specifier
static NSThread* networkThread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
networkThread = [[NSThread alloc] initWithTarget:self selector:#selector(networkThreadMain:) object:nil];
[networkThread start];
});
return networkThread;
}
The hang up occurs on the return networkThread line. After executing that line the application seems to hang and freeze and I can't put my finger on why.
Thanks in advance.
EDIT
Here is the snippet of code for CloseThread for those interested
- (void)closeThread
{
/*if(!inputStream)
return;
[inputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[inputStream release];
inputStream = nil;*/
}
I suggest changing:
[self performSelector:#selector(closeThread) onThread:[[self class]networkThread] withObject:nil waitUntilDone:YES];
to:
[self performSelector:#selector(closeThread) onThread:[[self class]networkThread] withObject:nil waitUntilDone:NO];
That is, don't wait.
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