- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,183 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <SystemConfiguration/SystemConfiguration.h>
|
||||
#import "XMPPModule.h"
|
||||
|
||||
#define _XMPP_RECONNECT_H
|
||||
|
||||
#define DEFAULT_XMPP_RECONNECT_DELAY 2.0
|
||||
|
||||
#define DEFAULT_XMPP_RECONNECT_TIMER_INTERVAL 20.0
|
||||
|
||||
|
||||
@protocol XMPPReconnectDelegate;
|
||||
|
||||
/**
|
||||
* XMPPReconnect handles automatically reconnecting to the xmpp server due to accidental disconnections.
|
||||
* That is, a disconnection that is not the result of calling disconnect on the xmpp stream.
|
||||
*
|
||||
* Accidental disconnections may happen for a variety of reasons.
|
||||
* The most common are general connectivity issues such as disconnection from a WiFi access point.
|
||||
*
|
||||
* However, there are several of issues that occasionaly occur.
|
||||
* There are some routers on the market that disconnect TCP streams after a period of inactivity.
|
||||
* In addition to this, there have been iPhone revisions where the OS networking stack would pull the same crap.
|
||||
* These issue have been largely overcome due to the keepalive implementation in XMPPStream.
|
||||
*
|
||||
* Regardless of how the disconnect happens, the XMPPReconnect class can help to automatically re-establish
|
||||
* the xmpp stream so as to have minimum impact on the user (and hopefully they don't even notice).
|
||||
*
|
||||
* Once a stream has been opened and authenticated, this class will detect any accidental disconnections.
|
||||
* If one occurs, an attempt will be made to automatically reconnect after a short delay.
|
||||
* This delay is configurable via the reconnectDelay property.
|
||||
* At the same time the class will begin monitoring the network for reachability changes.
|
||||
* When the reachability of the xmpp host has changed, a reconnect may be tried again.
|
||||
* In addition to all this, a timer may optionally be used to attempt a reconnect periodically.
|
||||
* The timer is started if the initial reconnect fails.
|
||||
* This reconnect timer is fully configurable (may be enabled/disabled, and it's timeout may be changed).
|
||||
*
|
||||
* In all cases, prior to attempting a reconnect,
|
||||
* this class will invoke the shouldAttemptAutoReconnect delegate method.
|
||||
* The delegate may use this opportunity to optionally decline the auto reconnect attempt.
|
||||
*
|
||||
* Auto reconnect may be disabled at any time via the autoReconnect property.
|
||||
*
|
||||
* Note that auto reconnect will only occur for a stream that has been opened and authenticated.
|
||||
* So it will do nothing, for example, if there is no internet connectivity when your application
|
||||
* first launches, and the xmpp stream is unable to connect to the host.
|
||||
* In cases such as this it may be desireable to start monitoring the network for reachability changes.
|
||||
* This way when internet connectivity is restored, one can immediately connect the xmpp stream.
|
||||
* This is possible via the manualStart method,
|
||||
* which will trigger the class into action just as if an accidental disconnect occurred.
|
||||
**/
|
||||
|
||||
@interface XMPPReconnect : XMPPModule
|
||||
{
|
||||
Byte flags;
|
||||
Byte config;
|
||||
NSTimeInterval reconnectDelay;
|
||||
|
||||
dispatch_source_t reconnectTimer;
|
||||
NSTimeInterval reconnectTimerInterval;
|
||||
|
||||
SCNetworkReachabilityRef reachability;
|
||||
|
||||
int reconnectTicket;
|
||||
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_5
|
||||
SCNetworkConnectionFlags previousReachabilityFlags;
|
||||
#else
|
||||
SCNetworkReachabilityFlags previousReachabilityFlags;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether auto reconnect is enabled or disabled.
|
||||
*
|
||||
* The default value is YES (enabled).
|
||||
*
|
||||
* Note: Altering this property will only affect future accidental disconnections.
|
||||
* For example, if autoReconnect was true, and you disable this property after an accidental disconnection,
|
||||
* this will not stop the current reconnect process.
|
||||
* In order to stop a current reconnect process use the stop method.
|
||||
*
|
||||
* Similarly, if autoReconnect was false, and you enable this property after an accidental disconnection,
|
||||
* this will not start a reconnect process.
|
||||
* In order to start a reconnect process use the manualStart method.
|
||||
**/
|
||||
@property (nonatomic, assign) BOOL autoReconnect;
|
||||
|
||||
/**
|
||||
* When the accidental disconnection first happens,
|
||||
* a short delay may be used before attempting the reconnection.
|
||||
*
|
||||
* The default value is DEFAULT_XMPP_RECONNECT_DELAY (defined at the top of this file).
|
||||
*
|
||||
* To disable this feature, set the value to zero.
|
||||
*
|
||||
* Note: NSTimeInterval is a double that specifies the time in seconds.
|
||||
**/
|
||||
@property (nonatomic, assign) NSTimeInterval reconnectDelay;
|
||||
|
||||
/**
|
||||
* A reconnect timer may optionally be used to attempt a reconnect periodically.
|
||||
* The timer will be started after the initial reconnect delay.
|
||||
*
|
||||
* The default value is DEFAULT_XMPP_RECONNECT_TIMER_INTERVAL (defined at the top of this file).
|
||||
*
|
||||
* To disable this feature, set the value to zero.
|
||||
*
|
||||
* Note: NSTimeInterval is a double that specifies the time in seconds.
|
||||
**/
|
||||
@property (nonatomic, assign) NSTimeInterval reconnectTimerInterval;
|
||||
|
||||
/**
|
||||
* Whether you want to reconnect using the legacy method -[XMPPStream oldSchoolSecureConnectWithTimeout:error:]
|
||||
* instead of the standard -[XMPPStream connect:].
|
||||
*
|
||||
* If you initially connect using -oldSchoolSecureConnectWithTimeout:error:, set this to YES to reconnect the same way.
|
||||
*
|
||||
* The default value is NO (disabled).
|
||||
*/
|
||||
@property (nonatomic, assign) BOOL usesOldSchoolSecureConnect;
|
||||
|
||||
/**
|
||||
* As opposed to using autoReconnect, this method may be used to manually start the reconnect process.
|
||||
* This may be useful, for example, if one needs network monitoring in order to setup the inital xmpp connection.
|
||||
* Or if one wants autoReconnect but only in very limited situations which they prefer to control manually.
|
||||
*
|
||||
* After invoking this method one can expect the class to act as if an accidental disconnect just occurred.
|
||||
* That is, a reconnect attempt will be tried after reconnectDelay seconds,
|
||||
* and the class will begin monitoring the network for changes in reachability to the xmpp host.
|
||||
*
|
||||
* A manual start of the reconnect process will effectively end once the xmpp stream has been opened.
|
||||
* That is, if you invoke manualStart and the xmpp stream is later opened,
|
||||
* then future disconnections will not result in an auto reconnect process (unless the autoReconnect property applies).
|
||||
*
|
||||
* This method does nothing if the xmpp stream is not disconnected.
|
||||
**/
|
||||
- (void)manualStart;
|
||||
|
||||
/**
|
||||
* Stops the current reconnect process.
|
||||
*
|
||||
* This method will stop the current reconnect process regardless of whether the
|
||||
* reconnect process was started due to the autoReconnect property or due to a call to manualStart.
|
||||
*
|
||||
* Stopping the reconnect process does NOT prevent future auto reconnects if the property is enabled.
|
||||
* That is, if the autoReconnect property is still enabled, and the xmpp stream is later opened, authenticated and
|
||||
* accidentally disconnected, this class will still attempt an automatic reconnect.
|
||||
*
|
||||
* Stopping the reconnect process does NOT prevent future calls to manualStart from working.
|
||||
*
|
||||
* It only stops the CURRENT reconnect process.
|
||||
**/
|
||||
- (void)stop;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPReconnectDelegate
|
||||
@optional
|
||||
|
||||
/**
|
||||
* This method may be used to fine tune when we
|
||||
* should and should not attempt an auto reconnect.
|
||||
*
|
||||
* For example, if on the iPhone, one may want to prevent auto reconnect when WiFi is not available.
|
||||
**/
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_5
|
||||
|
||||
- (void)xmppReconnect:(XMPPReconnect *)sender didDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags;
|
||||
- (BOOL)xmppReconnect:(XMPPReconnect *)sender shouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags;
|
||||
|
||||
#else
|
||||
|
||||
- (void)xmppReconnect:(XMPPReconnect *)sender didDetectAccidentalDisconnect:(SCNetworkReachabilityFlags)connectionFlags;
|
||||
- (BOOL)xmppReconnect:(XMPPReconnect *)sender shouldAttemptAutoReconnect:(SCNetworkReachabilityFlags)reachabilityFlags;
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,651 @@
|
||||
#import "XMPPReconnect.h"
|
||||
#import "XMPPStream.h"
|
||||
#import "XMPPLogging.h"
|
||||
#import "NSXMLElement+XMPP.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
#define IMPOSSIBLE_REACHABILITY_FLAGS 0xFFFFFFFF
|
||||
|
||||
// Log levels: off, error, warn, info, verbose
|
||||
#if DEBUG
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
enum XMPPReconnectFlags
|
||||
{
|
||||
kShouldReconnect = 1 << 0, // If set, disconnection was accidental, and autoReconnect may be used
|
||||
kMultipleChanges = 1 << 1, // If set, there have been reachability changes during a connection attempt
|
||||
kManuallyStarted = 1 << 2, // If set, we were started manually via manualStart method
|
||||
kQueryingDelegates = 1 << 3, // If set, we are awaiting response(s) from the delegate(s)
|
||||
};
|
||||
|
||||
enum XMPPReconnectConfig
|
||||
{
|
||||
kAutoReconnect = 1 << 0, // If set, automatically attempts to reconnect after a disconnection
|
||||
};
|
||||
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_5 && !TARGET_OS_IPHONE
|
||||
// SCNetworkConnectionFlags was renamed to SCNetworkReachabilityFlags in 10.6
|
||||
typedef SCNetworkConnectionFlags SCNetworkReachabilityFlags;
|
||||
#endif
|
||||
|
||||
@interface XMPPReconnect (PrivateAPI)
|
||||
|
||||
- (void)setupReconnectTimer;
|
||||
- (void)teardownReconnectTimer;
|
||||
|
||||
- (void)setupNetworkMonitoring;
|
||||
- (void)teardownNetworkMonitoring;
|
||||
|
||||
- (void)maybeAttemptReconnect;
|
||||
- (void)maybeAttemptReconnectWithTicket:(int)ticket;
|
||||
- (void)maybeAttemptReconnectWithReachabilityFlags:(SCNetworkReachabilityFlags)reachabilityFlags;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPReconnect
|
||||
|
||||
@dynamic autoReconnect;
|
||||
@synthesize reconnectDelay;
|
||||
@synthesize reconnectTimerInterval;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithDispatchQueue:NULL];
|
||||
}
|
||||
|
||||
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
|
||||
{
|
||||
if ((self = [super initWithDispatchQueue:queue]))
|
||||
{
|
||||
flags = 0;
|
||||
config = kAutoReconnect;
|
||||
|
||||
reconnectDelay = DEFAULT_XMPP_RECONNECT_DELAY;
|
||||
reconnectTimerInterval = DEFAULT_XMPP_RECONNECT_TIMER_INTERVAL;
|
||||
|
||||
reconnectTicket = 0;
|
||||
|
||||
previousReachabilityFlags = IMPOSSIBLE_REACHABILITY_FLAGS;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
[self teardownReconnectTimer];
|
||||
[self teardownNetworkMonitoring];
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(moduleQueue, block);
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Configuration and Flags
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (BOOL)autoReconnect
|
||||
{
|
||||
__block BOOL result = NO;
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
result = (config & kAutoReconnect) ? YES : NO;
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(moduleQueue, block);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setAutoReconnect:(BOOL)flag
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
if (flag)
|
||||
config |= kAutoReconnect;
|
||||
else
|
||||
config &= ~kAutoReconnect;
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (BOOL)shouldReconnect
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue");
|
||||
|
||||
return (flags & kShouldReconnect) ? YES : NO;
|
||||
}
|
||||
|
||||
- (void)setShouldReconnect:(BOOL)flag
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue");
|
||||
|
||||
if (flag)
|
||||
flags |= kShouldReconnect;
|
||||
else
|
||||
flags &= ~kShouldReconnect;
|
||||
}
|
||||
|
||||
- (BOOL)multipleReachabilityChanges
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue");
|
||||
|
||||
return (flags & kMultipleChanges) ? YES : NO;
|
||||
}
|
||||
|
||||
- (void)setMultipleReachabilityChanges:(BOOL)flag
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue");
|
||||
|
||||
if (flag)
|
||||
flags |= kMultipleChanges;
|
||||
else
|
||||
flags &= ~kMultipleChanges;
|
||||
}
|
||||
|
||||
- (BOOL)manuallyStarted
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue");
|
||||
|
||||
return (flags & kManuallyStarted) ? YES : NO;
|
||||
}
|
||||
|
||||
- (void)setManuallyStarted:(BOOL)flag
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue");
|
||||
|
||||
if (flag)
|
||||
flags |= kManuallyStarted;
|
||||
else
|
||||
flags &= ~kManuallyStarted;
|
||||
}
|
||||
|
||||
- (BOOL)queryingDelegates
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue");
|
||||
|
||||
return (flags & kQueryingDelegates) ? YES : NO;
|
||||
}
|
||||
|
||||
- (void)setQueryingDelegates:(BOOL)flag
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag), @"Invoked private method outside moduleQueue");
|
||||
|
||||
if (flag)
|
||||
flags |= kQueryingDelegates;
|
||||
else
|
||||
flags &= ~kQueryingDelegates;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Manual Manipulation
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)manualStart
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
if ([xmppStream isDisconnected] && [self manuallyStarted] == NO)
|
||||
{
|
||||
[self setManuallyStarted:YES];
|
||||
|
||||
[self setupReconnectTimer];
|
||||
[self setupNetworkMonitoring];
|
||||
}
|
||||
}};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
// Clear all flags to disable any further reconnect attemts regardless of the state we're in.
|
||||
|
||||
flags = 0;
|
||||
|
||||
// Stop any planned reconnect attempts and stop monitoring the network.
|
||||
|
||||
reconnectTicket++;
|
||||
|
||||
[self teardownReconnectTimer];
|
||||
[self teardownNetworkMonitoring];
|
||||
|
||||
}};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark XMPPStream Delegate
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)xmppStreamDidConnect:(XMPPStream *)sender
|
||||
{
|
||||
// This method is executed on our moduleQueue.
|
||||
|
||||
// The stream is up so we can stop our reconnect attempts now.
|
||||
//
|
||||
// We essentially want to do the same thing as the stop method with one exception:
|
||||
// We do not want to clear the shouldReconnect flag.
|
||||
//
|
||||
// Remember the shouldReconnect flag gets set upon authentication.
|
||||
// A combination of this flag and the autoReconnect flag controls the auto reconnect mechanism.
|
||||
//
|
||||
// It is possible for us to get accidentally disconnected after
|
||||
// the stream opens but prior to authentication completing.
|
||||
// If this happens we still want to abide by the previous shouldReconnect setting.
|
||||
|
||||
[self setMultipleReachabilityChanges:NO];
|
||||
[self setManuallyStarted:NO];
|
||||
|
||||
reconnectTicket++;
|
||||
|
||||
[self teardownReconnectTimer];
|
||||
[self teardownNetworkMonitoring];
|
||||
}
|
||||
|
||||
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
|
||||
{
|
||||
// This method is executed on our moduleQueue.
|
||||
|
||||
// We're now connected and properly authenticated.
|
||||
// Should we get accidentally disconnected we should automatically reconnect (if autoReconnect is set).
|
||||
[self setShouldReconnect:YES];
|
||||
}
|
||||
|
||||
- (void)xmppStream:(XMPPStream *)sender didReceiveError:(NSXMLElement *)element
|
||||
{
|
||||
// This method is executed on our moduleQueue.
|
||||
|
||||
// <stream:error>
|
||||
// <conflict xmlns="urn:ietf:params:xml:ns:xmpp-streams"/>
|
||||
// <text xmlns="urn:ietf:params:xml:ns:xmpp-streams" xml:lang="">Replaced by new connection</text>
|
||||
// </stream:error>
|
||||
//
|
||||
// If our connection ever gets replaced, we shouldn't attempt a reconnect,
|
||||
// because the user has logged in on another device.
|
||||
// If we still applied the reconnect logic,
|
||||
// the two devices may get into an infinite loop of kicking each other off the system.
|
||||
|
||||
NSString *elementName = [element name];
|
||||
|
||||
if ([elementName isEqualToString:@"stream:error"] || [elementName isEqualToString:@"error"])
|
||||
{
|
||||
NSXMLElement *conflict = [element elementForName:@"conflict" xmlns:@"urn:ietf:params:xml:ns:xmpp-streams"];
|
||||
if (conflict)
|
||||
{
|
||||
[self setShouldReconnect:NO];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)xmppStreamWasToldToDisconnect:(XMPPStream *)sender
|
||||
{
|
||||
// This method is executed on our moduleQueue.
|
||||
|
||||
// We should not automatically attempt to reconnect when the connection closes.
|
||||
[self stop];
|
||||
}
|
||||
|
||||
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
|
||||
{
|
||||
// This method is executed on our moduleQueue.
|
||||
|
||||
if ([self autoReconnect] && [self shouldReconnect])
|
||||
{
|
||||
[self setupReconnectTimer];
|
||||
[self setupNetworkMonitoring];
|
||||
|
||||
SCNetworkReachabilityFlags reachabilityFlags = 0;
|
||||
SCNetworkReachabilityGetFlags(reachability, &reachabilityFlags);
|
||||
|
||||
[multicastDelegate xmppReconnect:self didDetectAccidentalDisconnect:reachabilityFlags];
|
||||
}
|
||||
|
||||
if ([self multipleReachabilityChanges])
|
||||
{
|
||||
// While the previous connection attempt was in progress, the reachability of the xmpp host changed.
|
||||
// This means that while the previous attempt failed, an attempt now might succeed.
|
||||
|
||||
int ticket = ++reconnectTicket;
|
||||
|
||||
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (0.1 * NSEC_PER_SEC));
|
||||
dispatch_after(tt, moduleQueue, ^{ @autoreleasepool {
|
||||
|
||||
[self maybeAttemptReconnectWithTicket:ticket];
|
||||
|
||||
}});
|
||||
|
||||
// Note: We delay the method call.
|
||||
// This allows the other delegates to be notified of the closed stream prior to our reconnect attempt.
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Reachability
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void XMPPReconnectReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *info)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
XMPPReconnect *instance = (__bridge XMPPReconnect *)info;
|
||||
[instance maybeAttemptReconnectWithReachabilityFlags:flags];
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Logic
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)setupReconnectTimer
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
|
||||
|
||||
if (reconnectTimer == NULL)
|
||||
{
|
||||
if ((reconnectDelay <= 0.0) && (reconnectTimerInterval <= 0.0))
|
||||
{
|
||||
// All timed reconnect attempts are disabled
|
||||
return;
|
||||
}
|
||||
|
||||
reconnectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, moduleQueue);
|
||||
|
||||
dispatch_source_set_event_handler(reconnectTimer, ^{ @autoreleasepool {
|
||||
|
||||
[self maybeAttemptReconnect];
|
||||
|
||||
}});
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_source_t theReconnectTimer = reconnectTimer;
|
||||
|
||||
dispatch_source_set_cancel_handler(reconnectTimer, ^{
|
||||
XMPPLogVerbose(@"dispatch_release(reconnectTimer)");
|
||||
dispatch_release(theReconnectTimer);
|
||||
});
|
||||
#endif
|
||||
|
||||
dispatch_time_t startTime;
|
||||
if (reconnectDelay > 0.0)
|
||||
startTime = dispatch_time(DISPATCH_TIME_NOW, (reconnectDelay * NSEC_PER_SEC));
|
||||
else
|
||||
startTime = dispatch_time(DISPATCH_TIME_NOW, (reconnectTimerInterval * NSEC_PER_SEC));
|
||||
|
||||
uint64_t intervalTime;
|
||||
if (reconnectTimerInterval > 0.0)
|
||||
intervalTime = reconnectTimerInterval * NSEC_PER_SEC;
|
||||
else
|
||||
intervalTime = 0.0;
|
||||
|
||||
dispatch_source_set_timer(reconnectTimer, startTime, intervalTime, 0.25);
|
||||
dispatch_resume(reconnectTimer);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)teardownReconnectTimer
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
|
||||
|
||||
if (reconnectTimer)
|
||||
{
|
||||
dispatch_source_cancel(reconnectTimer);
|
||||
reconnectTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setupNetworkMonitoring
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
|
||||
|
||||
if (reachability == NULL)
|
||||
{
|
||||
NSString *domain = xmppStream.hostName;
|
||||
if (domain == nil)
|
||||
{
|
||||
domain = @"apple.com";
|
||||
}
|
||||
|
||||
reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
|
||||
|
||||
if (reachability)
|
||||
{
|
||||
SCNetworkReachabilityContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
|
||||
SCNetworkReachabilitySetCallback(reachability, XMPPReconnectReachabilityCallback, &context);
|
||||
|
||||
if (moduleQueue)
|
||||
{
|
||||
SCNetworkReachabilitySetDispatchQueue(reachability,moduleQueue);
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPLogWarn(@"%@: %@ - No xmpp moduleQueue!", THIS_FILE, THIS_METHOD);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)teardownNetworkMonitoring
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
|
||||
|
||||
if (reachability)
|
||||
{
|
||||
if (moduleQueue)
|
||||
{
|
||||
SCNetworkReachabilitySetDispatchQueue(reachability,NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPLogWarn(@"%@: %@ - No xmpp moduleQueue!", THIS_FILE, THIS_METHOD);
|
||||
}
|
||||
|
||||
SCNetworkReachabilitySetCallback(reachability, NULL, NULL);
|
||||
CFRelease(reachability);
|
||||
reachability = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method may be invoked by the reconnectTimer.
|
||||
*
|
||||
* During auto reconnection it is invoked reconnectDelay seconds after an accidental disconnection.
|
||||
* After that, it is then invoked every reconnectTimerInterval seconds.
|
||||
*
|
||||
* This handles disconnections that were not the result of an internet connectivity issue.
|
||||
**/
|
||||
- (void)maybeAttemptReconnect
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
|
||||
|
||||
if (reachability)
|
||||
{
|
||||
SCNetworkReachabilityFlags reachabilityFlags;
|
||||
if (SCNetworkReachabilityGetFlags(reachability, &reachabilityFlags))
|
||||
{
|
||||
[self maybeAttemptReconnectWithReachabilityFlags:reachabilityFlags];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is invoked (after a short delay) if the reachability changed while
|
||||
* a reconnection attempt was in progress.
|
||||
**/
|
||||
- (void)maybeAttemptReconnectWithTicket:(int)ticket
|
||||
{
|
||||
NSAssert(dispatch_get_specific(moduleQueueTag) , @"Invoked on incorrect queue");
|
||||
|
||||
if (ticket != reconnectTicket)
|
||||
{
|
||||
// The dispatched task was cancelled.
|
||||
return;
|
||||
}
|
||||
|
||||
if (reachability)
|
||||
{
|
||||
SCNetworkReachabilityFlags reachabilityFlags;
|
||||
if (SCNetworkReachabilityGetFlags(reachability, &reachabilityFlags))
|
||||
{
|
||||
[self maybeAttemptReconnectWithReachabilityFlags:reachabilityFlags];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)maybeAttemptReconnectWithReachabilityFlags:(SCNetworkReachabilityFlags)reachabilityFlags
|
||||
{
|
||||
if (!dispatch_get_specific(moduleQueueTag))
|
||||
{
|
||||
dispatch_async(moduleQueue, ^{ @autoreleasepool {
|
||||
|
||||
[self maybeAttemptReconnectWithReachabilityFlags:reachabilityFlags];
|
||||
|
||||
}});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (([self manuallyStarted]) || ([self autoReconnect] && [self shouldReconnect]))
|
||||
{
|
||||
if ([xmppStream isDisconnected] && ([self queryingDelegates] == NO))
|
||||
{
|
||||
// The xmpp stream is disconnected, and is not attempting reconnection
|
||||
|
||||
// Delegate rules:
|
||||
//
|
||||
// If ALL of the delegates return YES, then the result is YES.
|
||||
// If ANY of the delegates return NO, then the result is NO.
|
||||
// If there are no delegates, the default answer is YES.
|
||||
|
||||
GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator];
|
||||
|
||||
id del;
|
||||
dispatch_queue_t dq;
|
||||
|
||||
SEL selector = @selector(xmppReconnect:shouldAttemptAutoReconnect:);
|
||||
|
||||
NSUInteger delegateCount = [delegateEnumerator countForSelector:selector];
|
||||
|
||||
dispatch_semaphore_t delSemaphore = dispatch_semaphore_create(0);
|
||||
dispatch_group_t delGroup = dispatch_group_create();
|
||||
|
||||
while ([delegateEnumerator getNextDelegate:&del delegateQueue:&dq forSelector:selector])
|
||||
{
|
||||
dispatch_group_async(delGroup, dq, ^{ @autoreleasepool {
|
||||
|
||||
if (![del xmppReconnect:self shouldAttemptAutoReconnect:reachabilityFlags])
|
||||
{
|
||||
dispatch_semaphore_signal(delSemaphore);
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
[self setQueryingDelegates:YES];
|
||||
|
||||
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
|
||||
dispatch_async(concurrentQueue, ^{ @autoreleasepool {
|
||||
|
||||
dispatch_group_wait(delGroup, DISPATCH_TIME_FOREVER);
|
||||
|
||||
// What was the delegate response?
|
||||
|
||||
BOOL shouldAttemptReconnect;
|
||||
if (delegateCount == 0)
|
||||
{
|
||||
shouldAttemptReconnect = YES;
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldAttemptReconnect = (dispatch_semaphore_wait(delSemaphore, DISPATCH_TIME_NOW) != 0);
|
||||
}
|
||||
|
||||
dispatch_async(moduleQueue, ^{ @autoreleasepool {
|
||||
|
||||
[self setQueryingDelegates:NO];
|
||||
|
||||
if (shouldAttemptReconnect)
|
||||
{
|
||||
[self setMultipleReachabilityChanges:NO];
|
||||
previousReachabilityFlags = reachabilityFlags;
|
||||
|
||||
if (self.usesOldSchoolSecureConnect)
|
||||
{
|
||||
[xmppStream oldSchoolSecureConnectWithTimeout:XMPPStreamTimeoutNone error:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
[xmppStream connectWithTimeout:XMPPStreamTimeoutNone error:nil];
|
||||
}
|
||||
}
|
||||
else if ([self multipleReachabilityChanges])
|
||||
{
|
||||
[self setMultipleReachabilityChanges:NO];
|
||||
previousReachabilityFlags = IMPOSSIBLE_REACHABILITY_FLAGS;
|
||||
|
||||
[self maybeAttemptReconnect];
|
||||
}
|
||||
else
|
||||
{
|
||||
previousReachabilityFlags = IMPOSSIBLE_REACHABILITY_FLAGS;
|
||||
}
|
||||
|
||||
}});
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(delSemaphore);
|
||||
dispatch_release(delGroup);
|
||||
#endif
|
||||
}});
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// The xmpp stream is already attempting a connection.
|
||||
|
||||
if (reachabilityFlags != previousReachabilityFlags)
|
||||
{
|
||||
// It seems that the reachability of our xmpp host has changed in the middle of either
|
||||
// a reconnection attempt or while querying our delegates for permission to attempt reconnect.
|
||||
//
|
||||
// This may mean that the current attempt will fail,
|
||||
// but an another attempt after the failure will succeed.
|
||||
//
|
||||
// We make a note of the multiple changes,
|
||||
// and if the current attempt fails, we'll try again after a short delay.
|
||||
|
||||
[self setMultipleReachabilityChanges:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user