iOS network on background thread causes application to hang - ios

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.

Related

objective-c socket programming with NSStream

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.

-[NSRunLoop runUntilDate:] consumes 100% CPU

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?

Recovering a lost connection using sockets

I'm trying to dig deeper by using this example of an iPhone Chart Server and all is working as expected.
What I wanted to learn next is how to recover when the connection to the server is lost (for whatever reason) while the app is running.
First issue is that the app tries to open the input & output streams concurrently thus if I implement an alert, I get it two times. I managed to resolve this by closing the streams if there is an error.
The second is that if I'm another view controller and the connection is lost I can't seem to be able to recover it.I call the sendSocketMessage and if there is the error flat to try to use the initNetworkCommunication but I get a fatal error.
This 2nd issue is troubling me. I've added an All Exceptions breakpoint but got nothing. How I try to 'test' this is by making sure the server works and the app loads and connects. Then I shut-down the server, try a few clicks on the app and I get the alert. I switch-on the server and try to click again, I get the message sent to the server but then the app crashes with no info!
#implementation ViewController
bool connectionError = true;
- (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.
}
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)#"192.168.1.1", 6035, &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)closeAll {
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 *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
connectionError = false;
if (theStream == inputStream) {
uint8_t buffer[1024];
long len;
NSMutableData *currentMessage;
currentMessage = [NSMutableData dataWithBytes:"" length:strlen("")];
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
[currentMessage appendBytes:buffer length:len];
}
NSData * nullByte = [NSMutableData dataWithLength:1];
len = currentMessage.length;
NSRange searchRange = {0, len};
for (NSRange r; (r = [currentMessage rangeOfData:nullByte options:0 range:searchRange]).length; ) {
NSString * message = [[NSString alloc] initWithBytes:currentMessage.bytes length:r.location encoding:NSUTF8StringEncoding];
searchRange.location = r.location+r.length;
searchRange.length = len - searchRange.location;
[self messageReceived:message];
}
[currentMessage replaceBytesInRange:(NSRange){0, searchRange.location} withBytes:NULL length:0];
}
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
connectionError = true;
[self closeAll];
[self connectionLost];
break;
case NSStreamEventEndEncountered:
break;
default:
NSLog(#"Unknown event");
}
}
- (void) connectionLost {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Alert!"
message:#"Connection to the server lost!"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
- (void) messageReceived:(NSString *)message {
[messages addObject:message];
if (inputStream.streamStatus == NSStreamStatusClosed || inputStream.streamStatus == NSStreamStatusError || inputStream.streamStatus == NSStreamStatusNotOpen) {
[self closeAll];
[self initNetworkCommunication];
}
// do things with the message...
}
- (void) initConnection {
[self initNetworkCommunication];
messages = [[NSMutableArray alloc] init];
}
- (IBAction)joinChat:(id)sender {
[self initConnection];
[self sendSocketMessage: #"iam:" message: _inputNameField.text];
}
- (void) sendSocketMessage:(NSString*) sendCommand message:(NSString*) sendMessage
{
if (outputStream.streamStatus == NSStreamStatusClosed || outputStream.streamStatus == NSStreamStatusError || outputStream.streamStatus == NSStreamStatusNotOpen) {
[self closeAll];
[self initNetworkCommunication];
}
NSString *response = [NSString stringWithFormat: #"%#%#", sendCommand, sendMessage];
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
NSLog(#"clint sent: %#", response);
}
#end
The error I get is this screen grab:
int main(int argc, char * argv[]) {
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
Thread 1: EXC_BAD_ACCESS(code=1, address=0x0)
Any advice?
First issue:
I would use a strategy that involves pairing your alerts to connection objects. Since there is no built-in connection object in the API for dealing with CF streams, you'll have to create your own. Typically, a useful connection object would have properties for the associated NSInputStream and NSOutputStream. This approach should eliminate the problem of getting two alerts, since you will be showing an alert for the one connection object, instead of it's associated IO streams. This also encapsulates properties and operations neatly into one connection object, which you can separate from your view controller. Another benefit to using a connection object is that you will have more flexibility in your view controllers' object graph, since you will be using the connection object through composition, instead of inheritance in view controller subclasses.
Second issue:
The main function is shown when you get the error, not because it is causing the problem, but rather it is the function at the end of the call stack--when an exception is thrown, the system exits each call in the stack until it is caught, or until it reaches the end of the call stack, which is typically the main function in Objective-C.
I would first go your breakpoints panel, and add a general breakpoint, by tapping on the + sign in the bottom left corner, and selecting Add Exception Breakpoint. This usually helps me, when it successfully shows the point in my code that initially threw an exception, instead of showing the main function, which isn't helpful. You don't need to edit this breakpoint once it is added. Sometimes it causes the debugger to stop unnecessarily, if there are other exceptions that are thrown and caught in the system. If this is the case, then you can simply disable the exception break point until you arrive at the point in question, and then manually re-enable the break point before initiating an operation.
Edit
Another approach that you can use, if the catch-all Exception break point fails, is to use an exception handler. You would typically apply this inside your application:didLaunchWithOptions: method, like this:
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
And, implement your handler to print out some useful information:
void uncaughtExceptionHandler(NSException *exception) {
debugLog(#"CRASH: %#", exception);
debugLog(#"Stack trace: %#", [exception callStackSymbols]);
}

SIGSEGV in background thread NSRunLoop runMode:beforeDate:

I am using background threads with following pattern:
// Defined in .h-file as ivar
BOOL backgroundThreadIsAlive;
// .m file
static NSThread *backgroundThread = nil;
- (id)init
{
if (self = [super init])
{
if (backgroundThread == nil)
{
backgroundThreadIsAlive = YES;
backgroundThread = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[backgroundThread start];
}
}
return self;
}
- (void)threadMain:(id)data
{
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (backgroundThreadIsAlive)
{
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
My application sometimes crash with SIGSEGV in the line
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
I am using ARC. The crash is not reproducible. Just found it, when diving into testflight and saw this is the most often crash I got. It seems to appear independently from iOS version (I support iOS5+) and device type.
May someone have a hint for me, what I do wrong?
Is there a better solution doing background-threads? (maybe using GCD)
Is there a way to reproduce those issues?
Thank you for your time and guidiance. :)

Good Example of network-based NSStreams on a Separate Thread?

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

Resources