Rework how the views observe socket state.

// FREEBIE
pull/1/head
Matthew Chen 9 years ago
parent 2171cd1d96
commit 45b947dc04

@ -5,14 +5,22 @@
#import <Foundation/Foundation.h>
#import "SRWebSocket.h"
static void *kSocketStatusObservationContext = &kSocketStatusObservationContext;
static void *SocketManagerStateObservationContext = &SocketManagerStateObservationContext;
extern NSString *const SocketOpenedNotification;
extern NSString *const SocketClosedNotification;
extern NSString *const SocketConnectingNotification;
extern NSString *const kNSNotification_SocketManagerStateDidChange;
typedef NS_ENUM(NSUInteger, SocketManagerState) {
SocketManagerStateClosed,
SocketManagerStateConnecting,
SocketManagerStateOpen,
};
@interface TSSocketManager : NSObject <SRWebSocketDelegate>
@property (nonatomic, readonly) SocketManagerState state;
+ (instancetype)sharedManager;
- (instancetype)init NS_UNAVAILABLE;
// If the app is in the foreground, we'll try to open the socket unless it's already
@ -26,6 +34,4 @@ extern NSString *const SocketConnectingNotification;
// This method can be called from any thread.
+ (void)requestSocketOpen;
+ (void)sendNotification;
@end

@ -14,12 +14,6 @@
#import "TSStorageManager+keyingMaterial.h"
#import "Threading.h"
typedef enum : NSUInteger {
kSocketStatusOpen,
kSocketStatusClosed,
kSocketStatusConnecting,
} SocketStatus;
static const CGFloat kSocketHeartbeatPeriodSeconds = 30.f;
static const CGFloat kSocketReconnectDelaySeconds = 5.f;
@ -31,9 +25,7 @@ static const CGFloat kBackgroundOpenSocketDurationSeconds = 25.f;
// b) It has received a message over the socket in the last 15 seconds.
static const CGFloat kBackgroundKeepSocketAliveDurationSeconds = 15.f;
NSString *const SocketOpenedNotification = @"SocketOpenedNotification";
NSString *const SocketClosedNotification = @"SocketClosedNotification";
NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_SocketManagerStateDidChange";
// TSSocketManager's properties should only be accessed from the main thread.
@interface TSSocketManager ()
@ -50,7 +42,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
#pragma mark -
// The second tier is the status property. We initiate changes
// The second tier is the state property. We initiate changes
// to the websocket by changing this property's value, and delegate
// events from the websocket also update this value as the websocket's
// state changes.
@ -58,7 +50,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
// Due to concurrency, this property can fall out of sync with the
// websocket's actual state, so we're defensive and distrustful of
// this property.
@property (nonatomic) SocketStatus status;
@property (nonatomic) SocketManagerState state;
#pragma mark -
@ -102,11 +94,9 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
OWSAssert([NSThread isMainThread]);
_signalService = [OWSSignalService sharedInstance];
_status = kSocketStatusClosed;
_state = SocketManagerStateClosed;
_fetchingTaskIdentifier = UIBackgroundTaskInvalid;
[self addObserver:self forKeyPath:@"status" options:0 context:kSocketStatusObservationContext];
OWSSingletonAssert();
return self;
@ -167,11 +157,11 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
switch ([self.websocket readyState]) {
case SR_OPEN:
DDLogVerbose(@"WebSocket already open on connection request");
self.status = kSocketStatusOpen;
self.state = SocketManagerStateOpen;
return;
case SR_CONNECTING:
DDLogVerbose(@"WebSocket is already connecting");
self.status = kSocketStatusConnecting;
self.state = SocketManagerStateConnecting;
return;
default:
break;
@ -186,18 +176,19 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
// The websocket delegate methods are invoked _after_ the websocket
// state changes, so we may be just learning about a socket failure
// or close event now.
self.status = kSocketStatusClosed;
self.state = SocketManagerStateClosed;
// Now open a new socket.
self.status = kSocketStatusConnecting;
self.state = SocketManagerStateConnecting;
}
- (NSString *)stringFromSocketStatus:(SocketStatus)status {
switch (status) {
case kSocketStatusClosed:
- (NSString *)stringFromSocketManagerState:(SocketManagerState)state
{
switch (state) {
case SocketManagerStateClosed:
return @"Closed";
case kSocketStatusOpen:
case SocketManagerStateOpen:
return @"Open";
case kSocketStatusConnecting:
case SocketManagerStateConnecting:
return @"Connecting";
}
}
@ -216,46 +207,47 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
// it's not safe to assume we'll learn of changes to websocket state
// in the websocket delegate methods.
//
// Therefore, we use the [setStatus:] setter to ensure alignment between
// Therefore, we use the [setState:] setter to ensure alignment between
// websocket state and class state.
- (void)setStatus:(SocketStatus)status {
- (void)setState:(SocketManagerState)state
{
OWSAssert([NSThread isMainThread]);
// If this status update is redundant, verify that
// If this state update is redundant, verify that
// class state and socket state are aligned.
//
// Note: it's not safe to check the socket's readyState here as
// it may have been just updated on another thread. If so,
// we'll learn of that state change soon.
if (_status == status) {
switch (status) {
case kSocketStatusClosed:
if (_state == state) {
switch (state) {
case SocketManagerStateClosed:
OWSAssert(!self.websocket);
break;
case kSocketStatusOpen:
case SocketManagerStateOpen:
OWSAssert(self.websocket);
break;
case kSocketStatusConnecting:
case SocketManagerStateConnecting:
OWSAssert(self.websocket);
break;
}
return;
}
DDLogWarn(@"%@ Socket status change: %@ -> %@",
self.tag,
[self stringFromSocketStatus:_status],
[self stringFromSocketStatus:status]);
// If this status update is _not_ redundant,
// update class state to reflect the new status.
switch (status) {
case kSocketStatusClosed: {
DDLogWarn(@"%@ Socket state change: %@ -> %@",
self.tag,
[self stringFromSocketManagerState:_state],
[self stringFromSocketManagerState:state]);
// If this state update is _not_ redundant,
// update class state to reflect the new state.
switch (state) {
case SocketManagerStateClosed: {
[self resetSocket];
break;
}
case kSocketStatusOpen: {
OWSAssert(self.status == kSocketStatusConnecting);
case SocketManagerStateOpen: {
OWSAssert(self.state == SocketManagerStateConnecting);
self.heartbeatTimer = [NSTimer timerWithTimeInterval:kSocketHeartbeatPeriodSeconds
target:self
@ -270,7 +262,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
[self clearReconnect];
break;
}
case kSocketStatusConnecting: {
case SocketManagerStateConnecting: {
// Discard the old socket which is already closed or is closing.
[self resetSocket];
@ -286,17 +278,28 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
[self setWebsocket:socket];
// [SRWebSocket open] could hypothetically call a delegate method (e.g. if
// the socket failed immediately for some reason), so we update the status
// the socket failed immediately for some reason), so we update the state
// _before_ calling it, not after.
_status = status;
_state = state;
[socket open];
return;
}
}
_status = status;
_state = state;
[self notifyStatusChange];
}
- (void)notifyStatusChange
{
[[NSNotificationCenter defaultCenter] postNotificationName:kNSNotification_SocketManagerStateDidChange
object:nil
userInfo:nil];
}
#pragma mark -
- (void)resetSocket {
OWSAssert([NSThread isMainThread]);
@ -315,7 +318,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
DDLogWarn(@"%@ closeWebSocket.", self.tag);
}
self.status = kSocketStatusClosed;
self.state = SocketManagerStateClosed;
}
#pragma mark - Delegate methods
@ -328,7 +331,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
return;
}
self.status = kSocketStatusOpen;
self.state = SocketManagerStateOpen;
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
@ -480,42 +483,6 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
[TSStorageManager serverAuthToken]];
}
#pragma mark UI Delegates
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == kSocketStatusObservationContext) {
[self notifyStatusChange];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)notifyStatusChange
{
switch (self.status) {
case kSocketStatusOpen:
[[NSNotificationCenter defaultCenter] postNotificationName:SocketOpenedNotification object:self];
break;
case kSocketStatusClosed:
[[NSNotificationCenter defaultCenter] postNotificationName:SocketClosedNotification object:self];
break;
case kSocketStatusConnecting:
[[NSNotificationCenter defaultCenter] postNotificationName:SocketConnectingNotification object:self];
break;
default:
break;
}
}
+ (void)sendNotification
{
[[self sharedManager] notifyStatusChange];
}
#pragma mark - Socket LifeCycle
- (BOOL)shouldSocketBeOpen
@ -626,7 +593,7 @@ NSString *const SocketConnectingNotification = @"SocketConnectingNotification";
OWSAssert([NSThread isMainThread]);
if ([self shouldSocketBeOpen]) {
if (self.status != kSocketStatusOpen) {
if (self.state != SocketManagerStateOpen) {
// If we want the socket to be open and it's not open,
// start up the reconnect timer immediately (don't wait for an error).
// There's little harm in it and this will make us more robust to edge

Loading…
Cancel
Save