瀏覽代碼

Rewrite SRWebSocketDelegate handling via SRDelegateController. (#366)

Nikita Lutsenko 9 年之前
父節點
當前提交
cc4e5dc41a

+ 28 - 0
SocketRocket.xcodeproj/project.pbxproj

@@ -26,6 +26,14 @@
 		811934BC1CDAF725003AB243 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		811934BC1CDAF725003AB243 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		811934BE1CDAF725003AB243 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		811934BE1CDAF725003AB243 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		811934C01CDAF726003AB243 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		811934C01CDAF726003AB243 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		817995861CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; };
+		817995871CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; };
+		817995881CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; };
+		817995891CE139700084DA37 /* SRDelegateController.h in Headers */ = {isa = PBXBuildFile; fileRef = 817995841CE139700084DA37 /* SRDelegateController.h */; };
+		8179958A1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; };
+		8179958B1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; };
+		8179958C1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; };
+		8179958D1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; };
 		81B31C141CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; };
 		81B31C141CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; };
 		81B31C151CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; };
 		81B31C151CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; };
 		81B31C161CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; };
 		81B31C161CDC404100D86D43 /* SRIOConsumer.h in Headers */ = {isa = PBXBuildFile; fileRef = 81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */; };
@@ -113,6 +121,8 @@
 		8105E4AD1CDD6E6200AA12DB /* SRAutobahnOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnOperation.m; sourceTree = "<group>"; };
 		8105E4AD1CDD6E6200AA12DB /* SRAutobahnOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnOperation.m; sourceTree = "<group>"; };
 		8105E5271CDD98E100AA12DB /* autobahn_configuration.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = autobahn_configuration.json; sourceTree = "<group>"; };
 		8105E5271CDD98E100AA12DB /* autobahn_configuration.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = autobahn_configuration.json; sourceTree = "<group>"; };
 		811934B11CDAF711003AB243 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		811934B11CDAF711003AB243 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		817995841CE139700084DA37 /* SRDelegateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRDelegateController.h; sourceTree = "<group>"; };
+		817995851CE139700084DA37 /* SRDelegateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRDelegateController.m; sourceTree = "<group>"; };
 		81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRIOConsumer.h; sourceTree = "<group>"; };
 		81B31C0F1CDC404100D86D43 /* SRIOConsumer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRIOConsumer.h; sourceTree = "<group>"; };
 		81B31C101CDC404100D86D43 /* SRIOConsumer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRIOConsumer.m; sourceTree = "<group>"; };
 		81B31C101CDC404100D86D43 /* SRIOConsumer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRIOConsumer.m; sourceTree = "<group>"; };
 		81B31C111CDC404100D86D43 /* SRIOConsumerPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRIOConsumerPool.h; sourceTree = "<group>"; };
 		81B31C111CDC404100D86D43 /* SRIOConsumerPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRIOConsumerPool.h; sourceTree = "<group>"; };
@@ -270,9 +280,19 @@
 			path = Resources;
 			path = Resources;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		817995831CE139540084DA37 /* Delegate */ = {
+			isa = PBXGroup;
+			children = (
+				817995841CE139700084DA37 /* SRDelegateController.h */,
+				817995851CE139700084DA37 /* SRDelegateController.m */,
+			);
+			path = Delegate;
+			sourceTree = "<group>";
+		};
 		81B31C0D1CDC404100D86D43 /* Internal */ = {
 		81B31C0D1CDC404100D86D43 /* Internal */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				817995831CE139540084DA37 /* Delegate */,
 				81B31C0E1CDC404100D86D43 /* IOConsumer */,
 				81B31C0E1CDC404100D86D43 /* IOConsumer */,
 				81B31C5C1CDC443A00D86D43 /* RunLoop */,
 				81B31C5C1CDC443A00D86D43 /* RunLoop */,
 				81B31C131CDC404100D86D43 /* Utilities */,
 				81B31C131CDC404100D86D43 /* Utilities */,
@@ -421,6 +441,7 @@
 				2D42277F1BB4365C000C1A6C /* SRWebSocket.h in Headers */,
 				2D42277F1BB4365C000C1A6C /* SRWebSocket.h in Headers */,
 				81B31C2E1CDC406B00D86D43 /* SRHash.h in Headers */,
 				81B31C2E1CDC406B00D86D43 /* SRHash.h in Headers */,
 				811934BE1CDAF725003AB243 /* SocketRocket.h in Headers */,
 				811934BE1CDAF725003AB243 /* SocketRocket.h in Headers */,
+				817995871CE139700084DA37 /* SRDelegateController.h in Headers */,
 				81B31C601CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 				81B31C601CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
@@ -434,6 +455,7 @@
 				3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */,
 				3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */,
 				81B31C301CDC406B00D86D43 /* SRHash.h in Headers */,
 				81B31C301CDC406B00D86D43 /* SRHash.h in Headers */,
 				811934C01CDAF726003AB243 /* SocketRocket.h in Headers */,
 				811934C01CDAF726003AB243 /* SocketRocket.h in Headers */,
+				817995891CE139700084DA37 /* SRDelegateController.h in Headers */,
 				81B31C621CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 				81B31C621CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
@@ -447,6 +469,7 @@
 				F668C8AA153E92F90044DBAC /* SRWebSocket.h in Headers */,
 				F668C8AA153E92F90044DBAC /* SRWebSocket.h in Headers */,
 				81B31C2F1CDC406B00D86D43 /* SRHash.h in Headers */,
 				81B31C2F1CDC406B00D86D43 /* SRHash.h in Headers */,
 				811934BC1CDAF725003AB243 /* SocketRocket.h in Headers */,
 				811934BC1CDAF725003AB243 /* SocketRocket.h in Headers */,
+				817995881CE139700084DA37 /* SRDelegateController.h in Headers */,
 				81B31C611CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 				81B31C611CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
@@ -460,6 +483,7 @@
 				F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */,
 				F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */,
 				81B31C2D1CDC406B00D86D43 /* SRHash.h in Headers */,
 				81B31C2D1CDC406B00D86D43 /* SRHash.h in Headers */,
 				555E0EB41C51E57A00E6BB92 /* SocketRocket.h in Headers */,
 				555E0EB41C51E57A00E6BB92 /* SocketRocket.h in Headers */,
+				817995861CE139700084DA37 /* SRDelegateController.h in Headers */,
 				81B31C5F1CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 				81B31C5F1CDC444900D86D43 /* SRRunLoopThread.h in Headers */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
@@ -656,6 +680,7 @@
 				81B31C211CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B31C211CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B31C641CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81B31C641CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81B31C321CDC406B00D86D43 /* SRHash.m in Sources */,
 				81B31C321CDC406B00D86D43 /* SRHash.m in Sources */,
+				8179958B1CE139700084DA37 /* SRDelegateController.m in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
@@ -668,6 +693,7 @@
 				81B31C231CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B31C231CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B31C661CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81B31C661CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81B31C341CDC406B00D86D43 /* SRHash.m in Sources */,
 				81B31C341CDC406B00D86D43 /* SRHash.m in Sources */,
+				8179958D1CE139700084DA37 /* SRDelegateController.m in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
@@ -691,6 +717,7 @@
 				81B31C221CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B31C221CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B31C651CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81B31C651CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81B31C331CDC406B00D86D43 /* SRHash.m in Sources */,
 				81B31C331CDC406B00D86D43 /* SRHash.m in Sources */,
+				8179958C1CE139700084DA37 /* SRDelegateController.m in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
@@ -703,6 +730,7 @@
 				81B31C201CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B31C201CDC404100D86D43 /* SRIOConsumerPool.m in Sources */,
 				81B31C631CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81B31C631CDC444900D86D43 /* SRRunLoopThread.m in Sources */,
 				81B31C311CDC406B00D86D43 /* SRHash.m in Sources */,
 				81B31C311CDC406B00D86D43 /* SRHash.m in Sources */,
+				8179958A1CE139700084DA37 /* SRDelegateController.m in Sources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};

+ 44 - 0
SocketRocket/Internal/Delegate/SRDelegateController.h

@@ -0,0 +1,44 @@
+//
+// Copyright (c) 2016-present, Facebook, Inc.
+// All rights reserved.
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SRWebSocket.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+struct SRDelegateAvailableMethods {
+    BOOL didOpen : 1;
+    BOOL didFailWithError : 1;
+    BOOL didCloseWithCode : 1;
+    BOOL didReceivePong : 1;
+    BOOL shouldConvertTextFrameToString : 1;
+};
+typedef struct SRDelegateAvailableMethods SRDelegateAvailableMethods;
+
+typedef void(^SRDelegateBlock)(id<SRWebSocketDelegate> _Nullable delegate, SRDelegateAvailableMethods availableMethods);
+
+@interface SRDelegateController : NSObject
+
+@property (nonatomic, weak) id<SRWebSocketDelegate> delegate;
+@property (atomic, readonly) SRDelegateAvailableMethods availableDelegateMethods;
+
+@property (nullable, nonatomic, strong) dispatch_queue_t dispatchQueue;
+@property (nullable, nonatomic, strong) NSOperationQueue *operationQueue;
+
+///--------------------------------------
+#pragma mark - Perform
+///--------------------------------------
+
+- (void)performDelegateBlock:(SRDelegateBlock)block;
+- (void)performDelegateQueueBlock:(dispatch_block_t)block;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 134 - 0
SocketRocket/Internal/Delegate/SRDelegateController.m

@@ -0,0 +1,134 @@
+//
+// Copyright (c) 2016-present, Facebook, Inc.
+// All rights reserved.
+//
+// This source code is licensed under the BSD-style license found in the
+// LICENSE file in the root directory of this source tree. An additional grant
+// of patent rights can be found in the PATENTS file in the same directory.
+//
+
+#import "SRDelegateController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SRDelegateController ()
+
+@property (nonatomic, strong, readonly) dispatch_queue_t accessQueue;
+
+@property (atomic, readwrite) SRDelegateAvailableMethods availableDelegateMethods;
+
+@end
+
+@implementation SRDelegateController
+
+@synthesize delegate = _delegate;
+@synthesize dispatchQueue = _dispatchQueue;
+@synthesize operationQueue = _operationQueue;
+
+///--------------------------------------
+#pragma mark - Init
+///--------------------------------------
+
+- (instancetype)init
+{
+    self = [super init];
+    if (!self) return self;
+
+    _accessQueue = dispatch_queue_create("com.facebook.socketrocket.delegate.access", DISPATCH_QUEUE_CONCURRENT);
+    _dispatchQueue = dispatch_get_main_queue();
+
+    return self;
+}
+
+///--------------------------------------
+#pragma mark - Accessors
+///--------------------------------------
+
+- (void)setDelegate:(id<SRWebSocketDelegate> _Nullable)delegate
+{
+    dispatch_barrier_async(self.accessQueue, ^{
+        _delegate = delegate;
+
+        self.availableDelegateMethods = (SRDelegateAvailableMethods){
+            .didOpen = [delegate respondsToSelector:@selector(webSocketDidOpen:)],
+            .didFailWithError = [delegate respondsToSelector:@selector(webSocket:didFailWithError:)],
+            .didCloseWithCode = [delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)],
+            .didReceivePong = [delegate respondsToSelector:@selector(webSocket:didReceivePong:)],
+            .shouldConvertTextFrameToString = [delegate respondsToSelector:@selector(webSocketShouldConvertTextFrameToString:)]
+        };
+    });
+}
+
+- (id<SRWebSocketDelegate> _Nullable)delegate
+{
+    __block id<SRWebSocketDelegate> delegate = nil;
+    dispatch_sync(self.accessQueue, ^{
+        delegate = _delegate;
+    });
+    return delegate;
+}
+
+- (void)setDispatchQueue:(dispatch_queue_t _Nullable)queue
+{
+    dispatch_barrier_async(self.accessQueue, ^{
+        _dispatchQueue = queue ?: dispatch_get_main_queue();
+        _operationQueue = nil;
+    });
+}
+
+- (dispatch_queue_t _Nullable)dispatchQueue
+{
+    __block dispatch_queue_t queue = nil;
+    dispatch_sync(self.accessQueue, ^{
+        queue = _dispatchQueue;
+    });
+    return queue;
+}
+
+- (void)setOperationQueue:(NSOperationQueue *_Nullable)queue
+{
+    dispatch_barrier_async(self.accessQueue, ^{
+        _dispatchQueue = queue ? nil : dispatch_get_main_queue();
+        _operationQueue = queue;
+    });
+}
+
+- (NSOperationQueue *_Nullable)operationQueue
+{
+    __block NSOperationQueue *queue = nil;
+    dispatch_sync(self.accessQueue, ^{
+        queue = _operationQueue;
+    });
+    return queue;
+}
+
+///--------------------------------------
+#pragma mark - Perform
+///--------------------------------------
+
+- (void)performDelegateBlock:(SRDelegateBlock)block
+{
+    __block __strong id<SRWebSocketDelegate> delegate = nil;
+    __block SRDelegateAvailableMethods availableMethods = {};
+    dispatch_sync(self.accessQueue, ^{
+        delegate = _delegate; // Not `OK` to go through `self`, since queue sync.
+        availableMethods = self.availableDelegateMethods; // `OK` to call through `self`, since no queue sync.
+    });
+    [self performDelegateQueueBlock:^{
+        block(delegate, availableMethods);
+    }];
+}
+
+- (void)performDelegateQueueBlock:(dispatch_block_t)block
+{
+    dispatch_queue_t dispatchQueue = self.dispatchQueue;
+    if (dispatchQueue) {
+        dispatch_async(dispatchQueue, block);
+    } else {
+        [self.operationQueue addOperationWithBlock:block];
+    }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 19 - 6
SocketRocket/SRWebSocket.h

@@ -56,12 +56,30 @@ extern NSString *const SRHTTPResponseErrorKey;
 
 
 @interface SRWebSocket : NSObject <NSStreamDelegate>
 @interface SRWebSocket : NSObject <NSStreamDelegate>
 
 
+/**
+ The delegate of the web socket.
+
+ The web socket delegate is notified on all state changes that happen to the web socket.
+ */
 @property (nonatomic, weak) id <SRWebSocketDelegate> delegate;
 @property (nonatomic, weak) id <SRWebSocketDelegate> delegate;
 
 
+/**
+ A dispatch queue for scheduling the delegate calls. The queue doesn't need be a serial queue.
+
+ If `nil` and `delegateOperationQueue` is `nil`, the socket uses main queue for performing all delegate method calls.
+ */
+@property (nonatomic, strong) dispatch_queue_t delegateDispatchQueue;
+
+/**
+ An operation queue for scheduling the delegate calls.
+
+ If `nil` and `delegateOperationQueue` is `nil`, the socket uses main queue for performing all delegate method calls.
+ */
+@property (nonatomic, strong) NSOperationQueue *delegateOperationQueue;
+
 @property (nonatomic, readonly) SRReadyState readyState;
 @property (nonatomic, readonly) SRReadyState readyState;
 @property (nonatomic, readonly, retain) NSURL *url;
 @property (nonatomic, readonly, retain) NSURL *url;
 
 
-
 @property (nonatomic, readonly) CFHTTPMessageRef receivedHTTPHeaders;
 @property (nonatomic, readonly) CFHTTPMessageRef receivedHTTPHeaders;
 
 
 // Optional array of cookies (NSHTTPCookie objects) to apply to the connections
 // Optional array of cookies (NSHTTPCookie objects) to apply to the connections
@@ -81,11 +99,6 @@ extern NSString *const SRHTTPResponseErrorKey;
 - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray<NSString *> *)protocols;
 - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray<NSString *> *)protocols;
 - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
 - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
 
 
-// Delegate queue will be dispatch_main_queue by default.
-// You cannot set both OperationQueue and dispatch_queue.
-- (void)setDelegateOperationQueue:(NSOperationQueue*) queue;
-- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue;
-
 // By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes.
 // By default, it will schedule itself on +[NSRunLoop SR_networkRunLoop] using defaultModes.
 - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
 - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
 - (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
 - (void)unscheduleFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;

+ 64 - 60
SocketRocket/SRWebSocket.m

@@ -27,6 +27,7 @@
 
 
 #import <Security/SecRandom.h>
 #import <Security/SecRandom.h>
 
 
+#import "SRDelegateController.h"
 #import "SRIOConsumer.h"
 #import "SRIOConsumer.h"
 #import "SRIOConsumerPool.h"
 #import "SRIOConsumerPool.h"
 #import "SRHash.h"
 #import "SRHash.h"
@@ -87,24 +88,20 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 
 
 @property (nonatomic) SRReadyState readyState;
 @property (nonatomic) SRReadyState readyState;
 
 
-@property (nonatomic) NSOperationQueue *delegateOperationQueue;
-@property (nonatomic) dispatch_queue_t delegateDispatchQueue;
-
 // Specifies whether SSL trust chain should NOT be evaluated.
 // Specifies whether SSL trust chain should NOT be evaluated.
 // By default this flag is set to NO, meaning only secure SSL connections are allowed.
 // By default this flag is set to NO, meaning only secure SSL connections are allowed.
 // For DEBUG builds this flag is ignored, and SSL connections are allowed regardless
 // For DEBUG builds this flag is ignored, and SSL connections are allowed regardless
 // of the certificate trust configuration
 // of the certificate trust configuration
 @property (nonatomic, readwrite) BOOL allowsUntrustedSSLCertificates;
 @property (nonatomic, readwrite) BOOL allowsUntrustedSSLCertificates;
 
 
+@property (nonatomic, strong, readonly) SRDelegateController *delegateController;
+
 @end
 @end
 
 
 
 
 @implementation SRWebSocket {
 @implementation SRWebSocket {
     NSInteger _webSocketVersion;
     NSInteger _webSocketVersion;
     
     
-    NSOperationQueue *_delegateOperationQueue;
-    dispatch_queue_t _delegateDispatchQueue;
-    
     dispatch_queue_t _workQueue;
     dispatch_queue_t _workQueue;
     NSMutableArray<SRIOConsumer *> *_consumers;
     NSMutableArray<SRIOConsumer *> *_consumers;
 
 
@@ -197,8 +194,7 @@ static __strong NSData *CRLFCRLF;
     // Going to set a specific on the queue so we can validate we're on the work queue
     // Going to set a specific on the queue so we can validate we're on the work queue
     dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL);
     dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL);
 
 
-    _delegateDispatchQueue = dispatch_get_main_queue();
-    sr_dispatch_retain(_delegateDispatchQueue);
+    _delegateController = [[SRDelegateController alloc] init];
 
 
     _readBuffer = [[NSMutableData alloc] init];
     _readBuffer = [[NSMutableData alloc] init];
     _outputBuffer = [[NSMutableData alloc] init];
     _outputBuffer = [[NSMutableData alloc] init];
@@ -264,11 +260,6 @@ static __strong NSData *CRLFCRLF;
         CFRelease(_receivedHTTPHeaders);
         CFRelease(_receivedHTTPHeaders);
         _receivedHTTPHeaders = NULL;
         _receivedHTTPHeaders = NULL;
     }
     }
-    
-    if (_delegateDispatchQueue) {
-        sr_dispatch_release(_delegateDispatchQueue);
-        _delegateDispatchQueue = NULL;
-    }
 }
 }
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
@@ -303,30 +294,6 @@ static __strong NSData *CRLFCRLF;
     [self openConnection];
     [self openConnection];
 }
 }
 
 
-// Calls block on delegate queue
-- (void)_performDelegateBlock:(dispatch_block_t)block;
-{
-    if (_delegateOperationQueue) {
-        [_delegateOperationQueue addOperationWithBlock:block];
-    } else {
-        assert(_delegateDispatchQueue);
-        dispatch_async(_delegateDispatchQueue, block);
-    }
-}
-
-- (void)setDelegateDispatchQueue:(dispatch_queue_t)queue;
-{
-    if (queue) {
-        sr_dispatch_retain(queue);
-    }
-    
-    if (_delegateDispatchQueue) {
-        sr_dispatch_release(_delegateDispatchQueue);
-    }
-    
-    _delegateDispatchQueue = queue;
-}
-
 - (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage;
 - (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage;
 {
 {
     NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept")));
     NSString *acceptHeader = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(httpMessage, CFSTR("Sec-WebSocket-Accept")));
@@ -373,10 +340,10 @@ static __strong NSData *CRLFCRLF;
         [self _readFrameNew];
         [self _readFrameNew];
     }
     }
 
 
-    [self _performDelegateBlock:^{
-        if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) {
-            [self.delegate webSocketDidOpen:self];
-        };
+    [self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate>  _Nullable delegate, SRDelegateAvailableMethods availableMethods) {
+        if (availableMethods.didOpen) {
+            [delegate webSocketDidOpen:self];
+        }
     }];
     }];
 }
 }
 
 
@@ -642,7 +609,7 @@ static __strong NSData *CRLFCRLF;
 - (void)_closeWithProtocolError:(NSString *)message;
 - (void)_closeWithProtocolError:(NSString *)message;
 {
 {
     // Need to shunt this on the _callbackQueue first to see if they received any messages 
     // Need to shunt this on the _callbackQueue first to see if they received any messages 
-    [self _performDelegateBlock:^{
+    [self.delegateController performDelegateQueueBlock:^{
         [self closeWithCode:SRStatusCodeProtocolError reason:message];
         [self closeWithCode:SRStatusCodeProtocolError reason:message];
         dispatch_async(_workQueue, ^{
         dispatch_async(_workQueue, ^{
             [self closeConnection];
             [self closeConnection];
@@ -655,16 +622,16 @@ static __strong NSData *CRLFCRLF;
     dispatch_async(_workQueue, ^{
     dispatch_async(_workQueue, ^{
         if (self.readyState != SR_CLOSED) {
         if (self.readyState != SR_CLOSED) {
             _failed = YES;
             _failed = YES;
-            [self _performDelegateBlock:^{
-                if ([self.delegate respondsToSelector:@selector(webSocket:didFailWithError:)]) {
-                    [self.delegate webSocket:self didFailWithError:error];
+            [self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate>  _Nullable delegate, SRDelegateAvailableMethods availableMethods) {
+                if (availableMethods.didFailWithError) {
+                    [delegate webSocket:self didFailWithError:error];
                 }
                 }
             }];
             }];
 
 
             self.readyState = SR_CLOSED;
             self.readyState = SR_CLOSED;
 
 
             SRFastLog(@"Failing with error %@", error.localizedDescription);
             SRFastLog(@"Failing with error %@", error.localizedDescription);
-            
+
             [self closeConnection];
             [self closeConnection];
             [self _scheduleCleanup];
             [self _scheduleCleanup];
         }
         }
@@ -672,11 +639,11 @@ static __strong NSData *CRLFCRLF;
 }
 }
 
 
 - (void)_writeData:(NSData *)data;
 - (void)_writeData:(NSData *)data;
-{    
+{
     [self assertOnWorkQueue];
     [self assertOnWorkQueue];
 
 
     if (_closeWhenFinishedWriting) {
     if (_closeWhenFinishedWriting) {
-            return;
+        return;
     }
     }
     [_outputBuffer appendData:data];
     [_outputBuffer appendData:data];
     [self _pumpWriting];
     [self _pumpWriting];
@@ -713,7 +680,7 @@ static __strong NSData *CRLFCRLF;
 - (void)handlePing:(NSData *)pingData;
 - (void)handlePing:(NSData *)pingData;
 {
 {
     // Need to pingpong this off _callbackQueue first to make sure messages happen in order
     // Need to pingpong this off _callbackQueue first to make sure messages happen in order
-    [self _performDelegateBlock:^{
+    [self.delegateController performDelegateQueueBlock:^{
         dispatch_async(_workQueue, ^{
         dispatch_async(_workQueue, ^{
             [self _sendFrameWithOpcode:SROpCodePong data:pingData];
             [self _sendFrameWithOpcode:SROpCodePong data:pingData];
         });
         });
@@ -723,9 +690,9 @@ static __strong NSData *CRLFCRLF;
 - (void)handlePong:(NSData *)pongData;
 - (void)handlePong:(NSData *)pongData;
 {
 {
     SRFastLog(@"Received pong");
     SRFastLog(@"Received pong");
-    [self _performDelegateBlock:^{
-        if ([self.delegate respondsToSelector:@selector(webSocket:didReceivePong:)]) {
-            [self.delegate webSocket:self didReceivePong:pongData];
+    [self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate>  _Nullable delegate, SRDelegateAvailableMethods availableMethods) {
+        if (availableMethods.didReceivePong) {
+            [delegate webSocket:self didReceivePong:pongData];
         }
         }
     }];
     }];
 }
 }
@@ -733,8 +700,8 @@ static __strong NSData *CRLFCRLF;
 - (void)_handleMessage:(id)message
 - (void)_handleMessage:(id)message
 {
 {
     SRFastLog(@"Received message");
     SRFastLog(@"Received message");
-    [self _performDelegateBlock:^{
-        [self.delegate webSocket:self didReceiveMessage:message];
+    [self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate>  _Nullable delegate, SRDelegateAvailableMethods availableMethods) {
+        [delegate webSocket:self didReceiveMessage:message];
     }];
     }];
 }
 }
 
 
@@ -1091,9 +1058,9 @@ static const uint8_t SRPayloadLenMask   = 0x7F;
         }
         }
         
         
         if (!_failed) {
         if (!_failed) {
-            [self _performDelegateBlock:^{
-                if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
-                    [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES];
+            [self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate>  _Nullable delegate, SRDelegateAvailableMethods availableMethods) {
+                if (availableMethods.didCloseWithCode) {
+                    [delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES];
                 }
                 }
             }];
             }];
         }
         }
@@ -1492,9 +1459,12 @@ static const size_t SRFrameHeaderOverhead = 32;
                         if (!_sentClose && !_failed) {
                         if (!_sentClose && !_failed) {
                             _sentClose = YES;
                             _sentClose = YES;
                             // If we get closed in this state it's probably not clean because we should be sending this when we send messages
                             // If we get closed in this state it's probably not clean because we should be sending this when we send messages
-                            [self _performDelegateBlock:^{
-                                if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
-                                    [self.delegate webSocket:self didCloseWithCode:SRStatusCodeGoingAway reason:@"Stream end encountered" wasClean:NO];
+                            [self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate>  _Nullable delegate, SRDelegateAvailableMethods availableMethods) {
+                                if (availableMethods.didCloseWithCode) {
+                                    [delegate webSocket:self
+                                       didCloseWithCode:SRStatusCodeGoingAway
+                                                 reason:@"Stream end encountered"
+                                               wasClean:NO];
                                 }
                                 }
                             }];
                             }];
                         }
                         }
@@ -1538,6 +1508,40 @@ static const size_t SRFrameHeaderOverhead = 32;
         }
         }
 }
 }
 
 
+///--------------------------------------
+#pragma mark - Delegate
+///--------------------------------------
+
+- (id<SRWebSocketDelegate> _Nullable)delegate
+{
+    return self.delegateController.delegate;
+}
+
+- (void)setDelegate:(id<SRWebSocketDelegate> _Nullable)delegate
+{
+    self.delegateController.delegate = delegate;
+}
+
+- (void)setDelegateDispatchQueue:(dispatch_queue_t _Nullable)queue
+{
+    self.delegateController.dispatchQueue = queue;
+}
+
+- (dispatch_queue_t _Nullable)delegateDispatchQueue
+{
+    return self.delegateController.dispatchQueue;
+}
+
+- (void)setDelegateOperationQueue:(NSOperationQueue *_Nullable)queue
+{
+    self.delegateController.operationQueue = queue;
+}
+
+- (NSOperationQueue *_Nullable)delegateOperationQueue
+{
+    return self.delegateController.operationQueue;
+}
+
 @end
 @end
 
 
 @implementation  NSURLRequest (SRCertificateAdditions)
 @implementation  NSURLRequest (SRCertificateAdditions)