I have an application that connects to a server using NSStream on another thread. The application also closes the connection should the user decide to log out. The problem is that I am never able to successfully close the stream or the thread upon having the user disconnect. Below is my code sample on how I approach creating a thread for my network and trying to close the stream:
+ (NSThread*)networkThread
{
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;
}
+ (void)networkThreadMain:(id)sender
{
while (YES)
{
@autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
}
}
- (void)scheduleInThread:(id)sender
{
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[inputStream open];
}
- (void)closeThread
{
[inputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[inputStream release];
inputStream = nil;
}
Call made when trying to connect the inputstream:
[self performSelector:@selector(scheduleInThread:) onThread:[[self class] networkThread] withObject:nil waitUntilDone:YES];
Any advice is greatly appreciated.
The way you're mixing static and instance variables is confusing. Are you married to doing it that way? If you put this inside an NSOperation and ran it using an NSOperationQueue I think you'd get much cleaner encapsulation. The operation will manage its own async thread so you don't have to. Also, I highly recommend using ARC if you can.
A few notes:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface AsyncStreamOperation : NSOperation
@end
NS_ASSUME_NONNULL_END
#import "AsyncStreamOperation.h"
@interface AsyncStreamOperation ()
@property (atomic, strong) AsyncStreamOperation *config;
@property (atomic, strong) NSInputStream *stream;
@property (atomic, assign, getter=isExecuting) BOOL executing;
@property (atomic, assign, getter=isFinished) BOOL finished;
@end
@implementation AsyncStreamOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
- (instancetype)initWithStream:(NSInputStream *)stream
{
self = [super init];
if(self) {
_stream = stream;
}
return self;
}
- (BOOL)isAsynchronous
{
return YES;
}
- (BOOL)isExecuting
{
@synchronized (self) {
return _executing;
}
}
- (void)setExecuting:(BOOL)executing
{
@synchronized (self) {
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
[self didChangeValueForKey:@"isExecuting"];
}
}
- (BOOL)isFinished
{
@synchronized (self) {
return _finished;
}
}
- (void)setFinished:(BOOL)finished
{
@synchronized (self) {
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
[self didChangeValueForKey:@"isFinished"];
}
}
- (void)start
{
// Get runloop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// Schedule stream
[self.stream scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[self.stream open];
// Loop until finished
// NOTE: If -cancel is not called, you need to add your own logic to close the stream so this loop ends and the operation completes
while(self.executing && !self.finished && !self.cancelled && self.stream.streamStatus != NSStreamStatusClosed) {
@autoreleasepool {
// Maximum speed once per second or CPU goes through the roof
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
}
self.executing = NO;
self.finished = YES;
}
- (void)cancel
{
[super cancel];
[self.stream close];
self.executing = NO;
self.finished = YES;
}
@end