diff --git a/JFRWebSocket.m b/JFRWebSocket.m index 4af3b80..a0e7334 100644 --- a/JFRWebSocket.m +++ b/JFRWebSocket.m @@ -53,13 +53,20 @@ @interface JFRResponse : NSObject @end +//holds the shared thread +@interface JFRThread : NSThread + +//The shared run loop for all network related task ++(NSRunLoop*)sharedLoop; + +@end + @interface JFRWebSocket () @property(nonatomic, strong)NSURL *url; @property(nonatomic, strong)NSInputStream *inputStream; @property(nonatomic, strong)NSOutputStream *outputStream; @property(nonatomic, strong)NSOperationQueue *writeQueue; -@property(nonatomic, assign)BOOL isRunLoop; @property(nonatomic, strong)NSMutableArray *readStack; @property(nonatomic, strong)NSMutableArray *inputQueue; @property(nonatomic, strong)NSData *fragBuffer; @@ -93,6 +100,7 @@ @implementation JFRWebSocket - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray*)protocols { if(self = [super init]) { + [JFRThread sharedLoop]; //create the background thread and run loop self.voipEnabled = NO; self.selfSignedSSL = NO; self.queue = dispatch_get_main_queue(); @@ -112,12 +120,9 @@ - (void)connect return; } - //everything is on a background thread. - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - self.isCreated = YES; - [self createHTTPRequest]; - self.isCreated = NO; - }); + self.isCreated = YES; + [self createHTTPRequest]; + self.isCreated = NO; } ///////////////////////////////////////////////////////////////////////////// - (void)disconnect @@ -245,19 +250,16 @@ - (void)initStreamsWithData:(NSData *)data port:(NSNumber*)port [self.inputStream setProperty:settings forKey:key]; [self.outputStream setProperty:settings forKey:key]; } - [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; - [self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [self.inputStream scheduleInRunLoop:[JFRThread sharedLoop] forMode:NSDefaultRunLoopMode]; + [self.outputStream scheduleInRunLoop:[JFRThread sharedLoop] forMode:NSDefaultRunLoopMode]; [self.inputStream open]; [self.outputStream open]; - NSInteger len = [self.outputStream write:[data bytes] maxLength:[data length]]; - if(len < 0 || len == NSNotFound) { - [self doWriteError]; - return; - } - self.isRunLoop = YES; - while (self.isRunLoop) { - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; - } + [self dequeueWithBlock:^{ + NSInteger len = [self.outputStream write:[data bytes] maxLength:[data length]]; + if(len < 0 || len == NSNotFound) { + [self doWriteError]; + } + }]; } ///////////////////////////////////////////////////////////////////////////// @@ -297,14 +299,14 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode ///////////////////////////////////////////////////////////////////////////// -(void)disconnectStream:(NSError*)error { - [self.writeQueue waitUntilAllOperationsAreFinished]; - [self.inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; - [self.outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + //need to pull custom NSOperations from write queue here... + [self.writeQueue cancelAllOperations]; + [self.inputStream removeFromRunLoop:[JFRThread sharedLoop] forMode:NSDefaultRunLoopMode]; + [self.outputStream removeFromRunLoop:[JFRThread sharedLoop] forMode:NSDefaultRunLoopMode]; [self.outputStream close]; [self.inputStream close]; self.outputStream = nil; self.inputStream = nil; - self.isRunLoop = NO; _isConnected = NO; if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { @@ -629,14 +631,20 @@ -(BOOL)processCloseCode:(uint16_t)code return NO; } ///////////////////////////////////////////////////////////////////////////// --(void)dequeueWrite:(NSData*)data withCode:(JFROpCode)code -{ +-(void)dequeueWithBlock:(void (^)(void))block { if(!self.writeQueue) { self.writeQueue = [[NSOperationQueue alloc] init]; self.writeQueue.maxConcurrentOperationCount = 1; } + [self.writeQueue addOperationWithBlock:block]; +} +///////////////////////////////////////////////////////////////////////////// +-(void)dequeueWrite:(NSData*)data withCode:(JFROpCode)code +{ //we have a queue so we can be thread safe. - [self.writeQueue addOperationWithBlock:^{ + //need to change to custom NSOperations to store code and data pending to be written + __weak JFRWebSocket *weakSelf = self; + [self dequeueWithBlock:^{ //stream isn't ready, let's wait int tries = 0; while(!self.outputStream || !self.isConnected) { @@ -686,9 +694,9 @@ -(void)dequeueWrite:(NSData*)data withCode:(JFROpCode)code if(!self.outputStream) { break; } - NSInteger len = [self.outputStream write:([frame bytes]+total) maxLength:(NSInteger)(offset-total)]; + NSInteger len = [weakSelf.outputStream write:([frame bytes]+total) maxLength:(NSInteger)(offset-total)]; if(len < 0 || len == NSNotFound) { - [self doWriteError]; + [weakSelf doWriteError]; break; } else { total += len; @@ -725,6 +733,47 @@ -(void)dealloc ///////////////////////////////////////////////////////////////////////////// @end +///////////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////////// +@interface JFRThread () + +@property(nonatomic, assign)BOOL isRunning; +@property(nonatomic, strong)NSRunLoop *loop; + +@end +///////////////////////////////////////////////////////////////////////////// +@implementation JFRThread + +///////////////////////////////////////////////////////////////////////////// ++(NSRunLoop*)sharedLoop { + return [[self shared] loop]; +} +///////////////////////////////////////////////////////////////////////////// ++(instancetype)shared { + static JFRThread *manager = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + manager = [[self alloc] init]; + }); + return manager; +} +///////////////////////////////////////////////////////////////////////////// +-(instancetype)init { + if(self = [super init]) { + self.isRunning = YES; + [self start]; + } + return self; +} +///////////////////////////////////////////////////////////////////////////// +-(void)main { + self.loop = [NSRunLoop currentRunLoop]; + while (self.isRunning) { + [self.loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; + } +} + +@end ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// @implementation JFRResponse diff --git a/SimpleTest/SimpleTest/ViewController.m b/SimpleTest/SimpleTest/ViewController.m index 84f9c91..b5674ee 100644 --- a/SimpleTest/SimpleTest/ViewController.m +++ b/SimpleTest/SimpleTest/ViewController.m @@ -22,6 +22,7 @@ - (void)viewDidLoad { self.socket = [[JFRWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://localhost:8080"] protocols:@[@"chat",@"superchat"]]; self.socket.delegate = self; [self.socket connect]; + NSLog(@"hey there, just doing some main thread stuff..."); } // pragma mark: WebSocket Delegate methods. @@ -32,7 +33,7 @@ -(void)websocketDidConnect:(JFRWebSocket*)socket { -(void)websocketDidDisconnect:(JFRWebSocket*)socket error:(NSError*)error { NSLog(@"websocket is disconnected: %@", [error localizedDescription]); - [self.socket connect]; + //[self.socket connect]; } -(void)websocket:(JFRWebSocket*)socket didReceiveMessage:(NSString*)string { @@ -50,7 +51,13 @@ - (IBAction)writeText:(UIBarButtonItem *)sender { } - (IBAction)disconnect:(UIBarButtonItem *)sender { - [self.socket disconnect]; + if(self.socket.isConnected) { + sender.title = NSLocalizedString(@"connect", nil); + [self.socket disconnect]; + } else { + sender.title = NSLocalizedString(@"disconnect", nil); + [self.socket connect]; + } } @end