Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

shared singleton thread #16

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 76 additions & 27 deletions JFRWebSocket.m
Original file line number Diff line number Diff line change
Expand Up @@ -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 ()<NSStreamDelegate>

@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;
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down Expand Up @@ -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];
}
}];
}
/////////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -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:)]) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -725,6 +733,47 @@ -(void)dealloc
/////////////////////////////////////////////////////////////////////////////
@end

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
@interface JFRThread ()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why this class isn't in it's own file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not specifically, no reason it couldn't be move into its own. I put it in the same file since it was a fairly small class. The only advantage of keeping it in the same file is it the runloop singleton couldn't be used by other classes. Not that it would be common practice to do this, but it being scoped within the file it isn't possible for this mistake to be made.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I get it. It's a problem that is obviated by changing + to - in sharedLoop :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true dat 😄


@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
Expand Down
11 changes: 9 additions & 2 deletions SimpleTest/SimpleTest/ViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 {
Expand All @@ -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