- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,124 @@
|
||||
#import "XMPP.h"
|
||||
#import "XMPPTime.h"
|
||||
|
||||
#define _XMPP_AUTO_TIME_H
|
||||
|
||||
@class XMPPJID;
|
||||
|
||||
/**
|
||||
* The XMPPAutoTime module monitors the time difference between our machine and the target.
|
||||
* The target may simply be the server, or a specific resource.
|
||||
*
|
||||
* The module works by sending time queries to the target, and tracking the responses.
|
||||
* The module will automatically send multiple queuries, and take into account the average RTT.
|
||||
* It will also automatically update itself on a customizable interval, and whenever the machine's clock changes.
|
||||
*
|
||||
* This module is helpful when you are using timestamps from the target.
|
||||
* For example, you may be receiving offline messages from your server.
|
||||
* However, all these offline messages are timestamped from the server's clock.
|
||||
* And the current machine's clock may vary considerably from the server's clock.
|
||||
* Timezone differences don't matter as UTC is always used in XMPP, but clocks can easily differ.
|
||||
* This may cause the user some confusion as server timestamps may reflect a time in the future,
|
||||
* or much longer ago than in reality.
|
||||
**/
|
||||
@interface XMPPAutoTime : XMPPModule
|
||||
{
|
||||
NSTimeInterval recalibrationInterval;
|
||||
XMPPJID *targetJID;
|
||||
NSTimeInterval timeDifference;
|
||||
|
||||
dispatch_time_t lastCalibrationTime;
|
||||
dispatch_source_t recalibrationTimer;
|
||||
|
||||
BOOL awaitingQueryResponse;
|
||||
XMPPTime *xmppTime;
|
||||
|
||||
NSData *lastServerAddress;
|
||||
|
||||
NSDate *systemUptimeChecked;
|
||||
NSTimeInterval systemUptime;
|
||||
}
|
||||
|
||||
/**
|
||||
* How often to recalibrate the time difference.
|
||||
*
|
||||
* The module will automatically calculate the time difference when it is activated,
|
||||
* or when it first sees the xmppStream become authenticated (whichever occurs first).
|
||||
* After that first calculation, it will update itself according to this interval.
|
||||
*
|
||||
* To temporarily disable recalibration, set the interval to zero.
|
||||
*
|
||||
* The default recalibrationInterval is 24 hours.
|
||||
**/
|
||||
@property (readwrite) NSTimeInterval recalibrationInterval;
|
||||
|
||||
/**
|
||||
* The target to query.
|
||||
*
|
||||
* If the targetJID is nil, this implies the target is the xmpp server we're connected to.
|
||||
* If the targetJID is non-nil, it must be a full JID (user@domain.tld/rsrc).
|
||||
*
|
||||
* The default targetJID is nil.
|
||||
**/
|
||||
@property (readwrite, strong) XMPPJID *targetJID;
|
||||
|
||||
/**
|
||||
* Returns the calculated time difference between our machine and the target.
|
||||
*
|
||||
* This is NOT a reference to the difference in time zones.
|
||||
* Time zone differences generally shouldn't matter as xmpp standards mandate the use of UTC.
|
||||
*
|
||||
* Rather this is the difference between our UTC time, and the remote party's UTC time.
|
||||
* If the two clocks are not synchronized, then the result represents the approximate difference.
|
||||
*
|
||||
* If our clock is earlier than the remote clock, then the value will be negative.
|
||||
* If our clock is ahead of the remote clock, then the value will be positive.
|
||||
*
|
||||
* If you later receive a timestamp from the remote party, you can simply add the diff.
|
||||
* For example:
|
||||
*
|
||||
* myTime = [givenTimeFromRemoteParty dateByAddingTimeInterval:diff];
|
||||
**/
|
||||
@property (readonly) NSTimeInterval timeDifference;
|
||||
|
||||
/**
|
||||
* Returns the date of the target based on the time difference.
|
||||
**/
|
||||
@property (readonly) NSDate *date;
|
||||
|
||||
/**
|
||||
* The last time we've completed a calibration.
|
||||
**/
|
||||
@property (readonly) dispatch_time_t lastCalibrationTime;
|
||||
|
||||
/**
|
||||
* XMPPAutoTime is used to automatically query a target for its time (and calculate the difference).
|
||||
* Sometimes the target is also sending time requests to us as well.
|
||||
* If so, you may optionally set respondsToQueries to YES to allow the module to respond to incoming time queries.
|
||||
*
|
||||
* If you create multiple instances of XMPPAutoTime or XMPPTime,
|
||||
* then only one instance should respond to queries.
|
||||
*
|
||||
* The default value is NO.
|
||||
**/
|
||||
@property (readwrite) BOOL respondsToQueries;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPAutoTimeDelegate
|
||||
@optional
|
||||
|
||||
- (void)xmppAutoTime:(XMPPAutoTime *)sender didUpdateTimeDifference:(NSTimeInterval)timeDifference;
|
||||
|
||||
@end
|
||||
|
||||
@interface XMPPStream (XMPPAutoTime)
|
||||
|
||||
- (NSTimeInterval)xmppAutoTime_timeDifferenceForTargetJID:(XMPPJID *)targetJID;
|
||||
- (NSDate *)xmppAutoTime_dateForTargetJID:(XMPPJID *)targetJID;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,488 @@
|
||||
#import "XMPPAutoTime.h"
|
||||
#import "XMPP.h"
|
||||
#import "XMPPLogging.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels: off, error, warn, info, verbose
|
||||
// Log flags: trace
|
||||
#if DEBUG
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
@interface XMPPAutoTime ()
|
||||
|
||||
@property (nonatomic, strong) NSData *lastServerAddress;
|
||||
@property (nonatomic, strong) NSDate *systemUptimeChecked;
|
||||
|
||||
- (void)updateRecalibrationTimer;
|
||||
- (void)startRecalibrationTimer;
|
||||
- (void)stopRecalibrationTimer;
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation XMPPAutoTime
|
||||
|
||||
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
|
||||
{
|
||||
if ((self = [super initWithDispatchQueue:queue]))
|
||||
{
|
||||
recalibrationInterval = (60 * 60 * 24);
|
||||
|
||||
lastCalibrationTime = DISPATCH_TIME_FOREVER;
|
||||
|
||||
xmppTime = [[XMPPTime alloc] initWithDispatchQueue:queue];
|
||||
xmppTime.respondsToQueries = NO;
|
||||
|
||||
[xmppTime addDelegate:self delegateQueue:moduleQueue];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)activate:(XMPPStream *)aXmppStream
|
||||
{
|
||||
if ([super activate:aXmppStream])
|
||||
{
|
||||
[xmppTime activate:aXmppStream];
|
||||
|
||||
self.systemUptimeChecked = [NSDate date];
|
||||
systemUptime = [[NSProcessInfo processInfo] systemUptime];
|
||||
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(systemClockDidChange:)
|
||||
name:NSSystemClockDidChangeNotification
|
||||
object:nil];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)deactivate
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
[self stopRecalibrationTimer];
|
||||
|
||||
[xmppTime deactivate];
|
||||
awaitingQueryResponse = NO;
|
||||
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
[super deactivate];
|
||||
}};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// recalibrationTimer released in [self deactivate]
|
||||
|
||||
[xmppTime removeDelegate:self];
|
||||
xmppTime = nil; // Might be referenced via [super dealloc] -> [self deactivate]
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Properties
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@synthesize lastServerAddress;
|
||||
@synthesize systemUptimeChecked;
|
||||
|
||||
- (NSTimeInterval)recalibrationInterval
|
||||
{
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
{
|
||||
return recalibrationInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block NSTimeInterval result;
|
||||
|
||||
dispatch_sync(moduleQueue, ^{
|
||||
result = recalibrationInterval;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRecalibrationInterval:(NSTimeInterval)interval
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
|
||||
if (recalibrationInterval != interval)
|
||||
{
|
||||
recalibrationInterval = interval;
|
||||
|
||||
// Update the recalibrationTimer.
|
||||
//
|
||||
// Depending on new value and current state of the recalibrationTimer,
|
||||
// this may mean starting, stoping, or simply updating the timer.
|
||||
|
||||
if (recalibrationInterval > 0)
|
||||
{
|
||||
// Remember: Only start the timer after the xmpp stream is up and authenticated
|
||||
if ([xmppStream isAuthenticated])
|
||||
[self startRecalibrationTimer];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self stopRecalibrationTimer];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (XMPPJID *)targetJID
|
||||
{
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
{
|
||||
return targetJID;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block XMPPJID *result;
|
||||
|
||||
dispatch_sync(moduleQueue, ^{
|
||||
result = targetJID;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTargetJID:(XMPPJID *)jid
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
|
||||
if (![targetJID isEqualToJID:jid])
|
||||
{
|
||||
targetJID = jid;
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (NSTimeInterval)timeDifference
|
||||
{
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
{
|
||||
return timeDifference;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block NSTimeInterval result;
|
||||
|
||||
dispatch_sync(moduleQueue, ^{
|
||||
result = timeDifference;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDate *)date
|
||||
{
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
{
|
||||
return [[NSDate date] dateByAddingTimeInterval:-timeDifference];
|
||||
}
|
||||
else
|
||||
{
|
||||
__block NSDate *result;
|
||||
|
||||
dispatch_sync(moduleQueue, ^{
|
||||
result = [[NSDate date] dateByAddingTimeInterval:-timeDifference];
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (dispatch_time_t)lastCalibrationTime
|
||||
{
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
{
|
||||
return lastCalibrationTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block dispatch_time_t result;
|
||||
|
||||
dispatch_sync(moduleQueue, ^{
|
||||
result = lastCalibrationTime;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)respondsToQueries
|
||||
{
|
||||
return xmppTime.respondsToQueries;
|
||||
}
|
||||
|
||||
- (void)setRespondsToQueries:(BOOL)respondsToQueries
|
||||
{
|
||||
xmppTime.respondsToQueries = respondsToQueries;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Notifications
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)systemClockDidChange:(NSNotification *)notification
|
||||
{
|
||||
XMPPLogTrace();
|
||||
XMPPLogVerbose(@"NSSystemClockDidChangeNotification: %@", notification);
|
||||
|
||||
if (lastCalibrationTime == DISPATCH_TIME_FOREVER)
|
||||
{
|
||||
// Doesn't matter, we haven't done a calibration yet.
|
||||
return;
|
||||
}
|
||||
|
||||
// When the system clock changes, this affects our timeDifference.
|
||||
// However, the notification doesn't tell us by how much the system clock has changed.
|
||||
// So here's how we figure it out:
|
||||
//
|
||||
// The systemUptime isn't affected by the system clock.
|
||||
// We previously recorded the system uptime, and simultaneously recoded the system clock time.
|
||||
// We can now grab the current system uptime and current system clock time.
|
||||
// Using the four data points we can calculate how much the system clock has changed.
|
||||
|
||||
NSDate *now = [NSDate date];
|
||||
NSTimeInterval sysUptime = [[NSProcessInfo processInfo] systemUptime];
|
||||
|
||||
dispatch_async(moduleQueue, ^{ @autoreleasepool {
|
||||
|
||||
// Calculate system clock change
|
||||
|
||||
NSDate *oldSysTime = systemUptimeChecked;
|
||||
NSDate *newSysTime = now;
|
||||
|
||||
NSTimeInterval oldSysUptime = systemUptime;
|
||||
NSTimeInterval newSysUptime = sysUptime;
|
||||
|
||||
NSTimeInterval sysTimeDiff = [newSysTime timeIntervalSinceDate:oldSysTime];
|
||||
NSTimeInterval sysUptimeDiff = newSysUptime - oldSysUptime;
|
||||
|
||||
NSTimeInterval sysClockChange = sysTimeDiff - sysUptimeDiff;
|
||||
|
||||
// Modify timeDifference & notify delegate
|
||||
|
||||
timeDifference += sysClockChange;
|
||||
[multicastDelegate xmppAutoTime:self didUpdateTimeDifference:timeDifference];
|
||||
|
||||
// Dont forget to update our variables
|
||||
|
||||
self.systemUptimeChecked = now;
|
||||
systemUptime = sysUptime;
|
||||
|
||||
}});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Recalibration Timer
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)handleRecalibrationTimerFire
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (awaitingQueryResponse) return;
|
||||
|
||||
awaitingQueryResponse = YES;
|
||||
|
||||
if (targetJID)
|
||||
[xmppTime sendQueryToJID:targetJID];
|
||||
else
|
||||
[xmppTime sendQueryToServer];
|
||||
}
|
||||
|
||||
- (void)updateRecalibrationTimer
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
NSAssert(recalibrationTimer != NULL, @"Broken logic (1)");
|
||||
NSAssert(recalibrationInterval > 0, @"Broken logic (2)");
|
||||
|
||||
|
||||
uint64_t interval = (recalibrationInterval * NSEC_PER_SEC);
|
||||
dispatch_time_t tt;
|
||||
|
||||
if (lastCalibrationTime == DISPATCH_TIME_FOREVER)
|
||||
tt = dispatch_time(DISPATCH_TIME_NOW, 0); // First timer fire at (NOW)
|
||||
else
|
||||
tt = dispatch_time(lastCalibrationTime, interval); // First timer fire at (lastCalibrationTime + interval)
|
||||
|
||||
dispatch_source_set_timer(recalibrationTimer, tt, interval, 0);
|
||||
}
|
||||
|
||||
- (void)startRecalibrationTimer
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (recalibrationInterval <= 0)
|
||||
{
|
||||
// Timer is disabled
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL newTimer = NO;
|
||||
|
||||
if (recalibrationTimer == NULL)
|
||||
{
|
||||
newTimer = YES;
|
||||
recalibrationTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, moduleQueue);
|
||||
|
||||
dispatch_source_set_event_handler(recalibrationTimer, ^{ @autoreleasepool {
|
||||
|
||||
[self handleRecalibrationTimerFire];
|
||||
|
||||
}});
|
||||
}
|
||||
|
||||
[self updateRecalibrationTimer];
|
||||
|
||||
if (newTimer)
|
||||
{
|
||||
dispatch_resume(recalibrationTimer);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopRecalibrationTimer
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
if (recalibrationTimer)
|
||||
{
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(recalibrationTimer);
|
||||
#endif
|
||||
recalibrationTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark XMPPTime Delegate
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)xmppTime:(XMPPTime *)sender didReceiveResponse:(XMPPIQ *)iq withRTT:(NSTimeInterval)rtt
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
awaitingQueryResponse = NO;
|
||||
|
||||
lastCalibrationTime = dispatch_time(DISPATCH_TIME_NOW, 0);
|
||||
timeDifference = [XMPPTime approximateTimeDifferenceFromResponse:iq andRTT:rtt];
|
||||
|
||||
[multicastDelegate xmppAutoTime:self didUpdateTimeDifference:timeDifference];
|
||||
}
|
||||
|
||||
- (void)xmppTime:(XMPPTime *)sender didNotReceiveResponse:(NSString *)queryID dueToTimeout:(NSTimeInterval)timeout
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
awaitingQueryResponse = NO;
|
||||
|
||||
// Nothing to do here really. Most likely the server doesn't support XEP-0202.
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark XMPPStream Delegate
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)xmppStream:(XMPPStream *)sender socketDidConnect:(GCDAsyncSocket *)socket
|
||||
{
|
||||
NSData *currentServerAddress = [socket connectedAddress];
|
||||
|
||||
if (lastServerAddress == nil)
|
||||
{
|
||||
self.lastServerAddress = currentServerAddress;
|
||||
}
|
||||
else if (![lastServerAddress isEqualToData:currentServerAddress])
|
||||
{
|
||||
XMPPLogInfo(@"%@: Connected to a different server. Resetting calibration info.", [self class]);
|
||||
|
||||
lastCalibrationTime = DISPATCH_TIME_FOREVER;
|
||||
timeDifference = 0.0;
|
||||
|
||||
self.lastServerAddress = currentServerAddress;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
|
||||
{
|
||||
[self startRecalibrationTimer];
|
||||
}
|
||||
|
||||
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
|
||||
{
|
||||
[self stopRecalibrationTimer];
|
||||
|
||||
awaitingQueryResponse = NO;
|
||||
|
||||
// We do NOT reset the lastCalibrationTime here.
|
||||
// If we reconnect to the same server, the lastCalibrationTime remains valid.
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation XMPPStream (XMPPAutoTime)
|
||||
|
||||
- (NSTimeInterval)xmppAutoTime_timeDifferenceForTargetJID:(XMPPJID *)targetJID
|
||||
{
|
||||
__block NSTimeInterval timeDifference = 0.0;
|
||||
|
||||
[self enumerateModulesOfClass:[XMPPAutoTime class] withBlock:^(XMPPModule *module, NSUInteger idx, BOOL *stop) {
|
||||
|
||||
XMPPAutoTime *autoTime = (XMPPAutoTime *)module;
|
||||
|
||||
if([targetJID isEqualToJID:autoTime.targetJID] || (!targetJID && !autoTime.targetJID))
|
||||
{
|
||||
timeDifference = autoTime.timeDifference;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
return timeDifference;
|
||||
}
|
||||
|
||||
- (NSDate *)xmppAutoTime_dateForTargetJID:(XMPPJID *)targetJID
|
||||
{
|
||||
__block NSDate *date = [NSDate date];
|
||||
|
||||
[self enumerateModulesOfClass:[XMPPAutoTime class] withBlock:^(XMPPModule *module, NSUInteger idx, BOOL *stop) {
|
||||
|
||||
XMPPAutoTime *autoTime = (XMPPAutoTime *)module;
|
||||
|
||||
if([targetJID isEqualToJID:autoTime.targetJID] || (!targetJID && !autoTime.targetJID))
|
||||
{
|
||||
date = autoTime.date;
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,90 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPP.h"
|
||||
|
||||
#define _XMPP_TIME_H
|
||||
|
||||
@class XMPPIDTracker;
|
||||
@protocol XMPPTimeDelegate;
|
||||
|
||||
|
||||
@interface XMPPTime : XMPPModule
|
||||
{
|
||||
BOOL respondsToQueries;
|
||||
XMPPIDTracker *queryTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the module should respond to incoming time queries.
|
||||
* It you create multiple instances of this module, only one instance should respond to queries.
|
||||
*
|
||||
* It is recommended you set this (if needed) before you activate the module.
|
||||
* The default value is YES.
|
||||
**/
|
||||
@property (readwrite) BOOL respondsToQueries;
|
||||
|
||||
/**
|
||||
* Send query to the server or a specific JID.
|
||||
* The disco module may be used to detect if the target supports this XEP.
|
||||
*
|
||||
* The returned string is the queryID (the elementID of the query that was sent).
|
||||
* In other words:
|
||||
*
|
||||
* SEND: <iq id="<returned_string>" type="get" .../>
|
||||
* RECV: <iq id="<returned_string>" type="result" .../>
|
||||
*
|
||||
* This may be helpful if you are sending multiple simultaneous queries to the same target.
|
||||
**/
|
||||
- (NSString *)sendQueryToServer;
|
||||
- (NSString *)sendQueryToServerWithTimeout:(NSTimeInterval)timeout;
|
||||
- (NSString *)sendQueryToJID:(XMPPJID *)jid;
|
||||
- (NSString *)sendQueryToJID:(XMPPJID *)jid withTimeout:(NSTimeInterval)timeout;
|
||||
|
||||
/**
|
||||
* Extracts the utc date from the given response/time element,
|
||||
* and returns an NSDate representation of the time in the local time zone.
|
||||
* Since the returned date is in the local time zone, it is suitable for presentation.
|
||||
**/
|
||||
+ (NSDate *)dateFromResponse:(XMPPIQ *)iq;
|
||||
|
||||
/**
|
||||
* Extracts the time zone offset from the given response/time element.
|
||||
**/
|
||||
+ (NSTimeZone *)timeZoneOffsetFromResponse:(XMPPIQ *)iq;
|
||||
|
||||
/**
|
||||
* Given the returned time response from a remote party, and the approximate round trip time,
|
||||
* calculates the difference between our clock and the remote party's clock.
|
||||
*
|
||||
* This is NOT a reference to the difference in time zones.
|
||||
* Time zone differences generally shouldn't matter as xmpp standards mandate the use of UTC.
|
||||
*
|
||||
* Rather this is the difference between our UTC time, and the remote party's UTC time.
|
||||
* If the two clocks are not synchronized, then the result represents the approximate difference.
|
||||
*
|
||||
* If our clock is earlier than the remote clock, then the result will be negative.
|
||||
* If our clock is ahead of the remote clock, then the result will be positive.
|
||||
*
|
||||
* If you later receive a timestamp from the remote party, you could add the diff.
|
||||
* For example:
|
||||
*
|
||||
* myTime = [givenTimeFromRemoteParty dateByAddingTimeInterval:diff];
|
||||
**/
|
||||
+ (NSTimeInterval)approximateTimeDifferenceFromResponse:(XMPPIQ *)iq andRTT:(NSTimeInterval)rtt;
|
||||
|
||||
/**
|
||||
* Creates and returns a time element.
|
||||
**/
|
||||
+ (NSXMLElement *)timeElement;
|
||||
+ (NSXMLElement *)timeElementFromDate:(NSDate *)date;
|
||||
|
||||
@end
|
||||
|
||||
@protocol XMPPTimeDelegate
|
||||
@optional
|
||||
|
||||
- (void)xmppTime:(XMPPTime *)sender didReceiveResponse:(XMPPIQ *)iq withRTT:(NSTimeInterval)rtt;
|
||||
- (void)xmppTime:(XMPPTime *)sender didNotReceiveResponse:(NSString *)queryID dueToTimeout:(NSTimeInterval)timeout;
|
||||
|
||||
// Note: If the xmpp stream is disconnected, no delegate methods will be called, and outstanding queries are forgotten.
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,505 @@
|
||||
#import "XMPPTime.h"
|
||||
#import "XMPPIDTracker.h"
|
||||
#import "XMPPDateTimeProfiles.h"
|
||||
#import "XMPPFramework.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 DEFAULT_TIMEOUT 30.0 // seconds
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface XMPPTimeQueryInfo : XMPPBasicTrackingInfo
|
||||
{
|
||||
NSDate *timeSent;
|
||||
}
|
||||
|
||||
@property (nonatomic, readonly) NSDate *timeSent;
|
||||
|
||||
- (NSTimeInterval)rtt;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPTime
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithDispatchQueue:NULL];
|
||||
}
|
||||
|
||||
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
|
||||
{
|
||||
if ((self = [super initWithDispatchQueue:queue]))
|
||||
{
|
||||
respondsToQueries = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)activate:(XMPPStream *)aXmppStream
|
||||
{
|
||||
if ([super activate:aXmppStream])
|
||||
{
|
||||
#ifdef _XMPP_CAPABILITIES_H
|
||||
[xmppStream autoAddDelegate:self delegateQueue:moduleQueue toModulesOfClass:[XMPPCapabilities class]];
|
||||
#endif
|
||||
|
||||
queryTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)deactivate
|
||||
{
|
||||
#ifdef _XMPP_CAPABILITIES_H
|
||||
[xmppStream removeAutoDelegate:self delegateQueue:moduleQueue fromModulesOfClass:[XMPPCapabilities class]];
|
||||
#endif
|
||||
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
[queryTracker removeAllIDs];
|
||||
queryTracker = nil;
|
||||
|
||||
}};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(moduleQueue, block);
|
||||
|
||||
[super deactivate];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)respondsToQueries
|
||||
{
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
{
|
||||
return respondsToQueries;
|
||||
}
|
||||
else
|
||||
{
|
||||
__block BOOL result;
|
||||
|
||||
dispatch_sync(moduleQueue, ^{
|
||||
result = respondsToQueries;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setRespondsToQueries:(BOOL)flag
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
|
||||
if (respondsToQueries != flag)
|
||||
{
|
||||
respondsToQueries = flag;
|
||||
|
||||
#ifdef _XMPP_CAPABILITIES_H
|
||||
@autoreleasepool {
|
||||
// Capabilities may have changed, need to notify others.
|
||||
[xmppStream resendMyPresence];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(moduleQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(moduleQueue, block);
|
||||
}
|
||||
|
||||
- (NSString *)generateQueryIDWithTimeout:(NSTimeInterval)timeout
|
||||
{
|
||||
// This method may be invoked on any thread/queue.
|
||||
|
||||
// Generate unique ID for query.
|
||||
// It's important the ID be unique as the ID is the
|
||||
// only thing that distinguishes multiple queries from each other.
|
||||
|
||||
NSString *queryID = [xmppStream generateUUID];
|
||||
|
||||
dispatch_async(moduleQueue, ^{ @autoreleasepool {
|
||||
|
||||
XMPPTimeQueryInfo *queryInfo = [[XMPPTimeQueryInfo alloc] initWithTarget:self
|
||||
selector:@selector(handleResponse:withInfo:)
|
||||
timeout:timeout];
|
||||
|
||||
[queryTracker addID:queryID trackingInfo:queryInfo];
|
||||
|
||||
}});
|
||||
|
||||
return queryID;
|
||||
}
|
||||
|
||||
- (NSString *)sendQueryToServer
|
||||
{
|
||||
// This is a public method.
|
||||
// It may be invoked on any thread/queue.
|
||||
|
||||
return [self sendQueryToServerWithTimeout:DEFAULT_TIMEOUT];
|
||||
}
|
||||
|
||||
- (NSString *)sendQueryToServerWithTimeout:(NSTimeInterval)timeout
|
||||
{
|
||||
// This is a public method.
|
||||
// It may be invoked on any thread/queue.
|
||||
|
||||
NSString *queryID = [self generateQueryIDWithTimeout:timeout];
|
||||
|
||||
// Send ping packet
|
||||
//
|
||||
// <iq type="get" to="domain" id="queryID">
|
||||
// <time xmlns="urn:xmpp:time"/>
|
||||
// </iq>
|
||||
//
|
||||
// Note: Sometimes the to attribute is required. (ejabberd)
|
||||
|
||||
NSXMLElement *time = [NSXMLElement elementWithName:@"time" xmlns:@"urn:xmpp:time"];
|
||||
XMPPJID *domainJID = [[xmppStream myJID] domainJID];
|
||||
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:domainJID elementID:queryID child:time];
|
||||
|
||||
[xmppStream sendElement:iq];
|
||||
|
||||
return queryID;
|
||||
}
|
||||
|
||||
- (NSString *)sendQueryToJID:(XMPPJID *)jid
|
||||
{
|
||||
// This is a public method.
|
||||
// It may be invoked on any thread/queue.
|
||||
|
||||
return [self sendQueryToJID:jid withTimeout:DEFAULT_TIMEOUT];
|
||||
}
|
||||
|
||||
- (NSString *)sendQueryToJID:(XMPPJID *)jid withTimeout:(NSTimeInterval)timeout
|
||||
{
|
||||
// This is a public method.
|
||||
// It may be invoked on any thread/queue.
|
||||
|
||||
NSString *queryID = [self generateQueryIDWithTimeout:timeout];
|
||||
|
||||
// Send ping element
|
||||
//
|
||||
// <iq type="get" to="fullJID" id="abc123">
|
||||
// <time xmlns="urn:xmpp:time"/>
|
||||
// </iq>
|
||||
|
||||
NSXMLElement *time = [NSXMLElement elementWithName:@"time" xmlns:@"urn:xmpp:time"];
|
||||
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:jid elementID:queryID child:time];
|
||||
|
||||
[xmppStream sendElement:iq];
|
||||
|
||||
return queryID;
|
||||
}
|
||||
|
||||
- (void)handleResponse:(XMPPIQ *)iq withInfo:(XMPPTimeQueryInfo *)queryInfo
|
||||
{
|
||||
if (iq)
|
||||
{
|
||||
if ([[iq type] isEqualToString:@"result"])
|
||||
[multicastDelegate xmppTime:self didReceiveResponse:iq withRTT:[queryInfo rtt]];
|
||||
else
|
||||
[multicastDelegate xmppTime:self didNotReceiveResponse:[queryInfo elementID] dueToTimeout:-1.0];
|
||||
}
|
||||
else
|
||||
{
|
||||
[multicastDelegate xmppTime:self didNotReceiveResponse:[queryInfo elementID] dueToTimeout:[queryInfo timeout]];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq
|
||||
{
|
||||
// This method is invoked on the moduleQueue.
|
||||
|
||||
NSString *type = [iq type];
|
||||
|
||||
if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"])
|
||||
{
|
||||
// Examples:
|
||||
//
|
||||
// <iq type="result" from="robbie@voalte.com/office" to="robbie@deusty.com/home" id="abc123">
|
||||
// <time xmlns="urn:xmpp:time">
|
||||
// <tzo>-06:00</tzo>
|
||||
// <utc>2006-12-19T17:58:35Z</utc>
|
||||
// </time>
|
||||
// </iq>
|
||||
//
|
||||
// <iq type="error" from="robbie@voalte.com/office" to="robbie@deusty.com/home" id="abc123">
|
||||
// <time xmlns="urn:xmpp:time"/>
|
||||
// <error code="501" type="cancel">
|
||||
// <feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
|
||||
// </error>
|
||||
// </iq>
|
||||
|
||||
return [queryTracker invokeForID:[iq elementID] withObject:iq];
|
||||
}
|
||||
else if (respondsToQueries && [type isEqualToString:@"get"])
|
||||
{
|
||||
// Example:
|
||||
//
|
||||
// <iq type="get" from="robbie@deusty.com/home" to="robbie@voalte.com/office" id="abc123">
|
||||
// <time xmlns="urn:xmpp:time"/>
|
||||
// </iq>
|
||||
|
||||
NSXMLElement *time = [iq elementForName:@"time" xmlns:@"urn:xmpp:time"];
|
||||
if (time)
|
||||
{
|
||||
NSXMLElement *currentTime = [[self class] timeElement];
|
||||
|
||||
XMPPIQ *response = [XMPPIQ iqWithType:@"result" to:[iq from] elementID:[iq elementID]];
|
||||
[response addChild:currentTime];
|
||||
|
||||
[sender sendElement:response];
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
|
||||
{
|
||||
[queryTracker removeAllIDs];
|
||||
}
|
||||
|
||||
#ifdef _XMPP_CAPABILITIES_H
|
||||
/**
|
||||
* If an XMPPCapabilites instance is used we want to advertise our support for XEP-0202.
|
||||
**/
|
||||
- (void)xmppCapabilities:(XMPPCapabilities *)sender collectingMyCapabilities:(NSXMLElement *)query
|
||||
{
|
||||
// This method is invoked on the moduleQueue.
|
||||
|
||||
if (respondsToQueries)
|
||||
{
|
||||
// <query xmlns="http://jabber.org/protocol/disco#info">
|
||||
// ...
|
||||
// <feature var="urn:xmpp:time"/>
|
||||
// ...
|
||||
// </query>
|
||||
|
||||
NSXMLElement *feature = [NSXMLElement elementWithName:@"feature"];
|
||||
[feature addAttributeWithName:@"var" stringValue:@"urn:xmpp:time"];
|
||||
|
||||
[query addChild:feature];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
+ (NSDate *)dateFromResponse:(XMPPIQ *)iq
|
||||
{
|
||||
// <iq type="result" from="robbie@voalte.com/office" to="robbie@deusty.com/home" id="abc123">
|
||||
// <time xmlns="urn:xmpp:time">
|
||||
// <tzo>-06:00</tzo>
|
||||
// <utc>2006-12-19T17:58:35Z</utc>
|
||||
// </time>
|
||||
// </iq>
|
||||
|
||||
NSXMLElement *time = [iq elementForName:@"time" xmlns:@"urn:xmpp:time"];
|
||||
if (time == nil) return nil;
|
||||
|
||||
NSString *utc = [[time elementForName:@"utc"] stringValue];
|
||||
if (utc == nil) return nil;
|
||||
|
||||
// Note:
|
||||
//
|
||||
// NSDate is a very simple class, but can be confusing at times.
|
||||
// NSDate simply stores an NSTimeInterval internally,
|
||||
// which is just a double representing the number of seconds since the reference date.
|
||||
// Since it's a double, it can yield sub-millisecond precision.
|
||||
//
|
||||
// In addition to this, it stores the values in UTC.
|
||||
// However, if you print the value using NSLog via "%@",
|
||||
// it will automatically print the date in the local timezone:
|
||||
//
|
||||
// NSDate *refDate = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0];
|
||||
//
|
||||
// NSLog(@"%f", [refDate timeIntervalSinceReferenceDate]); // Prints: 0.0
|
||||
// NSLog(@"%@", refDate); // Prints: 2000-12-31 19:00:00 -05:00
|
||||
// NSLog(@"%@", [utcDateFormatter stringFromDate:refDate]); // Prints: 2001-01-01 00:00:00 +00:00
|
||||
//
|
||||
// Now the value we've received from XMPPDateTimeProfiles is correct.
|
||||
// If we print it out using a utcDateFormatter we would see it is correct.
|
||||
// If we printed it out generically using NSLog, then we would see it converted into our local time zone.
|
||||
|
||||
return [XMPPDateTimeProfiles parseDateTime:utc];
|
||||
}
|
||||
|
||||
+ (NSTimeZone *)timeZoneOffsetFromResponse:(XMPPIQ *)iq
|
||||
{
|
||||
// <iq type="result" from="robbie@voalte.com/office" to="robbie@deusty.com/home" id="abc123">
|
||||
// <time xmlns="urn:xmpp:time">
|
||||
// <tzo>-06:00</tzo>
|
||||
// <utc>2006-12-19T17:58:35Z</utc>
|
||||
// </time>
|
||||
// </iq>
|
||||
|
||||
NSXMLElement *time = [iq elementForName:@"time" xmlns:@"urn:xmpp:time"];
|
||||
if (time == nil) return 0;
|
||||
|
||||
NSString *tzo = [[time elementForName:@"tzo"] stringValue];
|
||||
if (tzo == nil) return 0;
|
||||
|
||||
return [XMPPDateTimeProfiles parseTimeZoneOffset:tzo];
|
||||
}
|
||||
|
||||
+ (NSTimeInterval)approximateTimeDifferenceFromResponse:(XMPPIQ *)iq andRTT:(NSTimeInterval)rtt
|
||||
{
|
||||
// First things first, get the current date and time
|
||||
|
||||
NSDate *localDate = [NSDate date];
|
||||
|
||||
// Then worry about the calculations
|
||||
|
||||
NSXMLElement *time = [iq elementForName:@"time" xmlns:@"urn:xmpp:time"];
|
||||
if (time == nil) return 0.0;
|
||||
|
||||
NSString *utc = [[time elementForName:@"utc"] stringValue];
|
||||
if (utc == nil) return 0.0;
|
||||
|
||||
NSDate *remoteDate = [XMPPDateTimeProfiles parseDateTime:utc];
|
||||
if (remoteDate == nil) return 0.0;
|
||||
|
||||
NSTimeInterval localTI = [localDate timeIntervalSinceReferenceDate];
|
||||
NSTimeInterval remoteTI = [remoteDate timeIntervalSinceReferenceDate] - (rtt / 2.0);
|
||||
|
||||
// Did the response contain millisecond precision?
|
||||
// This is an important consideration.
|
||||
// Imagine if both computers are perfectly synced,
|
||||
// but the remote response doesn't contain milliseconds.
|
||||
// This could possibly cause us to think the difference is close to a full second.
|
||||
//
|
||||
// DateTime examples (from XMPPDateTimeProfiles documentation):
|
||||
//
|
||||
// 1969-07-21T02:56:15
|
||||
// 1969-07-21T02:56:15Z
|
||||
// 1969-07-20T21:56:15-05:00
|
||||
// 1969-07-21T02:56:15.123
|
||||
// 1969-07-21T02:56:15.123Z
|
||||
// 1969-07-20T21:56:15.123-05:00
|
||||
|
||||
BOOL hasMilliseconds = ([utc length] > 19) && ([utc characterAtIndex:19] == '.');
|
||||
|
||||
if (hasMilliseconds)
|
||||
{
|
||||
return remoteTI - localTI;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No milliseconds. What to do?
|
||||
//
|
||||
// We could simply truncate the milliseconds from our time...
|
||||
// But this could make things much worse.
|
||||
// For example:
|
||||
//
|
||||
// local = 14:22:36.750
|
||||
// remote = 14:22:37
|
||||
//
|
||||
// If we truncate the result now we calculate a diff of 1.000 (a full second).
|
||||
// Considering the remote's milliseconds could have been anything from 000 to 999,
|
||||
// this means our calculations are:
|
||||
//
|
||||
// perfect : 0.1% chance
|
||||
// diff too big : 75.0% chance
|
||||
// diff too small : 24.9% chance
|
||||
//
|
||||
// Perhaps a better solution would give us a more even spread.
|
||||
// We can do this by calculating the range:
|
||||
//
|
||||
// 37.000 - 36.750 = 0.25
|
||||
// 37.999 - 36.750 = 1.249
|
||||
//
|
||||
// So a better guess of the diff is 0.750 (3/4 of a second):
|
||||
//
|
||||
// perfect : 0.1% chance
|
||||
// diff too big : 50.0% chance
|
||||
// diff too small : 49.9% chance
|
||||
|
||||
NSTimeInterval diff1 = localTI - (remoteTI + 0.000);
|
||||
NSTimeInterval diff2 = localTI - (remoteTI + 0.999);
|
||||
|
||||
return ((diff1 + diff2) / 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSXMLElement *)timeElement
|
||||
{
|
||||
return [self timeElementFromDate:[NSDate date]];
|
||||
}
|
||||
|
||||
+ (NSXMLElement *)timeElementFromDate:(NSDate *)date
|
||||
{
|
||||
// <time xmlns="urn:xmpp:time">
|
||||
// <tzo>-06:00</tzo>
|
||||
// <utc>2006-12-19T17:58:35Z</utc>
|
||||
// </time>
|
||||
|
||||
NSDateFormatter *df = [[NSDateFormatter alloc] init];
|
||||
[df setFormatterBehavior:NSDateFormatterBehavior10_4]; // Use unicode patterns (as opposed to 10_3)
|
||||
[df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
|
||||
[df setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
|
||||
[df setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
|
||||
|
||||
NSString *utcValue = [df stringFromDate:date];
|
||||
|
||||
|
||||
NSInteger tzoInSeconds = [[NSTimeZone systemTimeZone] secondsFromGMTForDate:date];
|
||||
|
||||
NSInteger tzoH = tzoInSeconds / (60 * 60);
|
||||
NSInteger tzoS = tzoInSeconds % (60 * 60);
|
||||
|
||||
NSString *tzoValue = [NSString stringWithFormat:@"%+03li:%02li", (long)tzoH, (long)tzoS];
|
||||
|
||||
NSXMLElement *tzo = [NSXMLElement elementWithName:@"tzo" stringValue:tzoValue];
|
||||
NSXMLElement *utc = [NSXMLElement elementWithName:@"utc" stringValue:utcValue];
|
||||
|
||||
NSXMLElement *time = [NSXMLElement elementWithName:@"time" xmlns:@"urn:xmpp:time"];
|
||||
[time addChild:tzo];
|
||||
[time addChild:utc];
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPTimeQueryInfo
|
||||
|
||||
@synthesize timeSent;
|
||||
|
||||
- (id)initWithTarget:(id)aTarget selector:(SEL)aSelector timeout:(NSTimeInterval)aTimeout
|
||||
{
|
||||
if ((self = [super initWithTarget:aTarget selector:aSelector timeout:aTimeout]))
|
||||
{
|
||||
timeSent = [[NSDate alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)rtt
|
||||
{
|
||||
return [timeSent timeIntervalSinceNow] * -1.0;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user