From 09c3d986a0e96c14b7e7330769962e5dda2e18ad Mon Sep 17 00:00:00 2001 From: Piotr Wegrzynek Date: Fri, 27 Oct 2017 11:46:03 +0200 Subject: [PATCH 1/3] Add a helper class to trace GCDMulticastDelegate invocation context --- Utilities/GCDMulticastDelegate.h | 41 ++++++++++++++++- Utilities/GCDMulticastDelegate.m | 76 +++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/Utilities/GCDMulticastDelegate.h b/Utilities/GCDMulticastDelegate.h index 3c48a7ee72..18ae32677d 100644 --- a/Utilities/GCDMulticastDelegate.h +++ b/Utilities/GCDMulticastDelegate.h @@ -1,6 +1,6 @@ #import -@class GCDMulticastDelegateEnumerator; +@class GCDMulticastDelegateEnumerator, GCDMulticastDelegateInvocationContext; /** * This class provides multicast delegate functionality. That is: @@ -54,5 +54,44 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL)getNextDelegate:(id _Nullable * _Nonnull)delPtr delegateQueue:(dispatch_queue_t _Nullable * _Nonnull)dqPtr ofClass:(Class)aClass; - (BOOL)getNextDelegate:(id _Nullable * _Nonnull)delPtr delegateQueue:(dispatch_queue_t _Nullable * _Nonnull)dqPtr forSelector:(SEL)aSelector; +@end + +/** + * A helper class for propagating custom context across multicast delegate invocations. + * + * This class serves 2 main purposes: + * - provides an auxiliary path of custom data delivery to the invoked delegate methods + * - makes it possible to track the delegate method invocation completion + * + * The context propagates along the cascade of invocations, i.e. when a delegate method calls another multicast delegate, + * that subsequent invocation belongs to the same context. This is particularly relevant w.r.t. the xmpp framework + * architecture where a scenario with two layers of delegation is common: stream -> module and module -> application. + * The propagating context is what enables the framework to deliver stream event-related information across modules + * to the application callbacks. + * + * The default context propagation junctions are invocation forwarding and delegate enumerator creation. As long as + * they are executed under an existing context, the propagation is automatic. + * + * A manual propagation scenario (e.g. asynchronous message processing within a module) would consist of the following steps: + * 1. Capturing the context object while still in the delegate callback context with @c currentContext. + * 2. Entering the captured context's @c continuityGroup. + * 3. Restoring the context on an arbitrary queue with @c becomeCurrentOnQueue:forActionWithBlock: + * 4. Leaving the @c continuityGroup within the action block submitted to @c becomeCurrentOnQueue:forActionWithBlock: + * + * Steps 2. and 4. are only required if @c becomeCurrentOnQueue:forActionWithBlock: itself is invoked asynchronously + * (e.g. in a network or disk IO completion callback). + */ +@interface GCDMulticastDelegateInvocationContext : NSObject + +@property (nonatomic, strong, readonly) id value; +@property (nonatomic, strong, readonly) dispatch_group_t continuityGroup; + ++ (instancetype)currentContext; + +- (instancetype)initWithValue:(id)value; +- (instancetype)init NS_UNAVAILABLE; + +- (void)becomeCurrentOnQueue:(dispatch_queue_t)queue forActionWithBlock:(dispatch_block_t)block; + @end NS_ASSUME_NONNULL_END diff --git a/Utilities/GCDMulticastDelegate.m b/Utilities/GCDMulticastDelegate.m index 1785c274d4..4d9f5f2ae0 100644 --- a/Utilities/GCDMulticastDelegate.m +++ b/Utilities/GCDMulticastDelegate.m @@ -9,6 +9,10 @@ #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif +static void * const GCDMulticastDelegateInvocationContextKey = (void *)&GCDMulticastDelegateInvocationContextKey; + +static void GCDMulticastDelegateInvocationContextLeave(void *contextPtr); + /** * How does this class work? * @@ -76,6 +80,12 @@ - (id)initFromDelegateNodes:(NSMutableArray *)inDelegateNodes; @end +@interface GCDMulticastDelegateInvocationContext () + +- (dispatch_queue_t)transferContextToTargetQueue:(dispatch_queue_t)targetQueue; + +@end + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -227,7 +237,22 @@ - (BOOL)hasDelegateThatRespondsToSelector:(SEL)aSelector - (GCDMulticastDelegateEnumerator *)delegateEnumerator { - return [[GCDMulticastDelegateEnumerator alloc] initFromDelegateNodes:delegateNodes]; + NSMutableArray *actualDelegateNodes; + + GCDMulticastDelegateInvocationContext *currentInvocationContext = [GCDMulticastDelegateInvocationContext currentContext]; + if (currentInvocationContext) { + actualDelegateNodes = [[NSMutableArray alloc] init]; + for (GCDMulticastDelegateNode *node in delegateNodes) { + dispatch_queue_t contextTransferQueue = [currentInvocationContext transferContextToTargetQueue:node.delegateQueue]; + GCDMulticastDelegateNode *contextTransferNode = [[GCDMulticastDelegateNode alloc] initWithDelegate:node.delegate + delegateQueue:contextTransferQueue]; + [actualDelegateNodes addObject:contextTransferNode]; + } + } else { + actualDelegateNodes = delegateNodes; + } + + return [[GCDMulticastDelegateEnumerator alloc] initFromDelegateNodes:actualDelegateNodes]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector @@ -276,7 +301,10 @@ - (void)forwardInvocation:(NSInvocation *)origInvocation NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation]; - dispatch_async(node.delegateQueue, ^{ @autoreleasepool { + GCDMulticastDelegateInvocationContext *currentContext = [GCDMulticastDelegateInvocationContext currentContext]; + dispatch_queue_t invocationQueue = [currentContext transferContextToTargetQueue:node.delegateQueue] ?: node.delegateQueue; + + dispatch_async(invocationQueue, ^{ @autoreleasepool { [dupInvocation invokeWithTarget:nodeDelegate]; @@ -652,3 +680,47 @@ - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr for } @end + +@implementation GCDMulticastDelegateInvocationContext + ++ (instancetype)currentContext +{ + void *contextPtr = dispatch_get_specific(GCDMulticastDelegateInvocationContextKey); + return contextPtr ? CFBridgingRelease(CFRetain(contextPtr)) : nil; +} + +- (instancetype)initWithValue:(id)value +{ + self = [super init]; + if (self) { + _continuityGroup = dispatch_group_create(); + _value = value; + } + return self; +} + +- (void)becomeCurrentOnQueue:(dispatch_queue_t)queue forActionWithBlock:(dispatch_block_t)block +{ + dispatch_async([self transferContextToTargetQueue:queue], block); +} + +- (dispatch_queue_t)transferContextToTargetQueue:(dispatch_queue_t)targetQueue +{ + dispatch_group_enter(self.continuityGroup); + + dispatch_queue_t contextTransferQueue = dispatch_queue_create_with_target("GCDMulticastDelegateInvocationContext.contextTransferQueue", nil, targetQueue); + dispatch_queue_set_specific(contextTransferQueue, + GCDMulticastDelegateInvocationContextKey, + (void *)CFBridgingRetain(self), + GCDMulticastDelegateInvocationContextLeave); + + return contextTransferQueue; +} + +@end + +static void GCDMulticastDelegateInvocationContextLeave(void *contextPtr) +{ + GCDMulticastDelegateInvocationContext *context = CFBridgingRelease(contextPtr); + dispatch_group_leave(context.continuityGroup); +} From 3327290bd6303c6a6ff2b64d9beea0c1849e721c Mon Sep 17 00:00:00 2001 From: Piotr Wegrzynek Date: Thu, 17 Aug 2017 12:06:17 +0200 Subject: [PATCH 2/3] Introduce traceable stanza send/receive events in XMPPStream --- Core/XMPPInternal.h | 1 + Core/XMPPStream.h | 115 ++++++++- Core/XMPPStream.m | 398 ++++++++++++++++++++----------- Utilities/GCDMulticastDelegate.m | 3 +- 4 files changed, 381 insertions(+), 136 deletions(-) diff --git a/Core/XMPPInternal.h b/Core/XMPPInternal.h index 1a1bc50c14..f4dba9754d 100644 --- a/Core/XMPPInternal.h +++ b/Core/XMPPInternal.h @@ -99,6 +99,7 @@ extern NSString *const XMPPStreamDidChangeMyJIDNotification; * This is an advanced technique, but makes for some interesting possibilities. **/ - (void)injectElement:(NSXMLElement *)element; +- (void)injectElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID; /** * The XMPP standard only supports , and stanzas (excluding session setup stuff). diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index e73e068f16..70dbea6212 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @class XMPPModule; @class XMPPElement; @class XMPPElementReceipt; +@class XMPPElementEvent; @protocol XMPPStreamDelegate; #if TARGET_OS_IPHONE @@ -615,8 +616,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; - (void)sendElement:(NSXMLElement *)element; /** - * Just like the sendElement: method above, - * but allows you to receive a receipt that can later be used to verify the element has been sent. + * Just like the sendElement: method above, but allows you to: + * - Receive a receipt that can later be used to verify the element has been sent. + * - Provide an event ID that can later be used to trace the element in delegate callbacks. * * If you later want to check to see if the element has been sent: * @@ -644,6 +646,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * Even if you close the xmpp stream after this point, the OS will still do everything it can to send the data. **/ - (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt * _Nullable * _Nullable)receiptPtr; +- (void)sendElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID andGetReceipt:(XMPPElementReceipt * _Nullable * _Nullable)receiptPtr; /** * Fetches and resends the myPresence element (if available) in a single atomic operation. @@ -733,6 +736,19 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; **/ - (void)enumerateModulesOfClass:(Class)aClass withBlock:(void (^)(XMPPModule *module, NSUInteger idx, BOOL *stop))block; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Element Event Context +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Returns the stream metadata corresponding to the currently processed XMPP stanza. + * + * Event information is only available in the context of @c didSendXXX/didFailToSendXXX/didReceiveXXX delegate callbacks. + * This method returns nil if called outside of those callbacks. + * For more details, please refer to @c XMPPElementEvent documentation. + */ +- (nullable XMPPElementEvent *)currentElementEvent; + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Utilities //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -785,6 +801,79 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; @end +/** + * A handle that allows identifying elements sent or received in the stream across different delegates + * and tracking their processing progress. + * + * While the core XMPP specification does not require stanzas to be uniquely identifiable, you may still want to + * identify them internally across different modules or trace the sent ones to the respective send result delegate callbacks. + * + * An instance of this class is provided in the context of execution of any of the @c didSendXXX/didFailToSendXXX/didReceiveXXX + * stream delegate methods. It is retrieved by calling the @c currentElementEvent method on the calling stream. + * The delegates can then use it to: + * - identify the corresponding XMPP stanzas. + * - be notified of asynchronous processing completion for a given XMPP stanza. + * + * Using @c XMPPElementEvent handles is a more robust approach than relying on pointer equality of @c XMPPElement instances. + */ +@interface XMPPElementEvent : NSObject + +/// The universally unique identifier of the event that provides the internal identity of the corresponding XMPP stanza. +@property (nonatomic, copy, readonly) NSString *uniqueID; + +/// The value of the stream's @c myJID property at the time when the event occured. +@property (nonatomic, strong, readonly, nullable) XMPPJID *myJID; + +/// The local device time when the event occured. +@property (nonatomic, strong, readonly) NSDate *timestamp; + +/** + * A flag indicating whether all delegates are done processing the given event. + * + * Supports Key-Value Observing. Change notifications are emitted on the stream queue. + * + * @see beginDelayedProcessing + * @see endDelayedProcessingWithToken + */ +@property (nonatomic, assign, readonly, getter=isProcessingCompleted) BOOL processingCompleted; + +// Instances are created by the stream only. +- (instancetype)init NS_UNAVAILABLE; + +/** + * Marks the event as being asynchronously processed by a delegate and returns a completion token. + * + * Event processing is completed after every @c beginDelayedProcessing call has been followed + * by @c endDelayedProcessingWithToken: with a matching completion token. + * + * Unpaired invocations may lead to undefined behavior or stalled events. + * + * Events that are not marked for asynchronous processing by any of the delegates complete immediately + * after control returns from all callbacks. + * + * @see endDelayedProcessingWithToken: + * @see processingCompleted + */ +- (id)beginDelayedProcessing; + +/** + * Marks an end of the previously initiated asynchronous delegate processing. + * + * Event processing is completed after every @c beginDelayedProcessing call has been followed + * by @c endDelayedProcessingWithToken: with a matching completion token. + * + * Unpaired invocations may lead to undefined behavior or stalled events. + * + * Events that are not marked for asynchronous processing by any of the delegates complete immediately + * after control returns from all callbacks. + * + * @see beginDelayedProcessing + * @see processingCompleted + */ +- (void)endDelayedProcessingWithToken:(id)delayedProcessingToken; + +@end + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -987,6 +1076,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * As documented in NSXML / KissXML, elements are read-access thread-safe, but write-access thread-unsafe. * If you have need to modify an element for any reason, * you should copy the element first, and then modify and use the copy. + * + * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * from within these callbacks. For more details, please refer to @c XMPPElementEvent documentation. **/ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq; - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message; @@ -1032,6 +1124,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * These methods are called after their respective XML elements are sent over the stream. * These methods may be used to listen for certain events (such as an unavailable presence having been sent), * or for general logging purposes. (E.g. a central history logging mechanism). + * + * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * from within these callbacks. For more details, please refer to @c XMPPElementEvent documentation. **/ - (void)xmppStream:(XMPPStream *)sender didSendIQ:(XMPPIQ *)iq; - (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message; @@ -1040,11 +1135,24 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; /** * These methods are called after failing to send the respective XML elements over the stream. * This occurs when the stream gets disconnected before the element can get sent out. + * + * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * from within these callbacks. + * Note that if these methods are called, the event context is incomplete, e.g. the stream might have not been connected + * and the actual myJID value is not determined. For more details, please refer to @c XMPPElementEvent documentation. **/ - (void)xmppStream:(XMPPStream *)sender didFailToSendIQ:(XMPPIQ *)iq error:(NSError *)error; - (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error; - (void)xmppStream:(XMPPStream *)sender didFailToSendPresence:(XMPPPresence *)presence error:(NSError *)error; +/** + * This method is called after all delegates are done processing the given event. + * + * For more details, please refer to @c XMPPElementEvent documentation. + */ +- (void)xmppStream:(XMPPStream *)sender didFinishProcessingElementEvent:(XMPPElementEvent *)event +NS_SWIFT_NAME(xmppStream(_:didFinishProcessing:)); + /** * This method is called if the XMPP Stream's jid changes. **/ @@ -1136,6 +1244,9 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * If you're using custom elements, you must register the custom element name(s). * Otherwise the xmppStream will treat non-XMPP elements as errors (xmppStream:didReceiveError:). * + * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * from within these callbacks. For more details, please refer to @c XMPPElementEvent documentation. + * * @see registerCustomElementNames (in XMPPInternal.h) **/ - (void)xmppStream:(XMPPStream *)sender didSendCustomElement:(NSXMLElement *)element; diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index 8a738719ae..d5f8011e66 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -153,6 +153,19 @@ - (void)signalFailure; @end +@interface XMPPElementEvent () + +@property (nonatomic, unsafe_unretained, readonly) XMPPStream *xmppStream; +@property (nonatomic, assign, readwrite, getter=isProcessingCompleted) BOOL processingCompleted; + +@end + +@interface XMPPElementEvent (PrivateAPI) + +- (instancetype)initWithStream:(XMPPStream *)xmppStream uniqueID:(NSString *)uniqueID myJID:(XMPPJID *)myJID timestamp:(NSDate *)timestamp; + +@end + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -2253,7 +2266,7 @@ - (float)serverXmppStreamVersionNumber } } -- (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag +- (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2268,7 +2281,7 @@ - (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag // None of the delegates implement the method. // Use a shortcut. - [self continueSendIQ:iq withTag:tag]; + [self continueSendIQ:iq withTag:tag registeredEvent:[self generateElementEventWithID:eventID]]; } else { @@ -2314,10 +2327,12 @@ - (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag { dispatch_async(xmppQueue, ^{ @autoreleasepool { + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + if (state == STATE_XMPP_CONNECTED) { - [self continueSendIQ:modifiedIQ withTag:tag]; + [self continueSendIQ:modifiedIQ withTag:tag registeredEvent:event]; } else { - [self failToSendIQ:modifiedIQ]; + [self failToSendIQ:modifiedIQ withRegisteredEvent:event]; } }}); } @@ -2325,7 +2340,7 @@ - (void)sendIQ:(XMPPIQ *)iq withTag:(long)tag } } -- (void)sendMessage:(XMPPMessage *)message withTag:(long)tag +- (void)sendMessage:(XMPPMessage *)message withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2340,7 +2355,7 @@ - (void)sendMessage:(XMPPMessage *)message withTag:(long)tag // None of the delegates implement the method. // Use a shortcut. - [self continueSendMessage:message withTag:tag]; + [self continueSendMessage:message withTag:tag registeredEvent:[self generateElementEventWithID:eventID]]; } else { @@ -2386,11 +2401,13 @@ - (void)sendMessage:(XMPPMessage *)message withTag:(long)tag { dispatch_async(xmppQueue, ^{ @autoreleasepool { + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + if (state == STATE_XMPP_CONNECTED) { - [self continueSendMessage:modifiedMessage withTag:tag]; + [self continueSendMessage:modifiedMessage withTag:tag registeredEvent:event]; } else { - [self failToSendMessage:modifiedMessage]; + [self failToSendMessage:modifiedMessage withRegisteredEvent:event]; } }}); } @@ -2398,7 +2415,7 @@ - (void)sendMessage:(XMPPMessage *)message withTag:(long)tag } } -- (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag +- (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2413,7 +2430,7 @@ - (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag // None of the delegates implement the method. // Use a shortcut. - [self continueSendPresence:presence withTag:tag]; + [self continueSendPresence:presence withTag:tag registeredEvent:[self generateElementEventWithID:eventID]]; } else { @@ -2459,10 +2476,12 @@ - (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag { dispatch_async(xmppQueue, ^{ @autoreleasepool { + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + if (state == STATE_XMPP_CONNECTED) { - [self continueSendPresence:modifiedPresence withTag:tag]; + [self continueSendPresence:modifiedPresence withTag:tag registeredEvent:event]; } else { - [self failToSendPresence:modifiedPresence]; + [self failToSendPresence:modifiedPresence withRegisteredEvent:event]; } }}); } @@ -2470,7 +2489,7 @@ - (void)sendPresence:(XMPPPresence *)presence withTag:(long)tag } } -- (void)continueSendIQ:(XMPPIQ *)iq withTag:(long)tag +- (void)continueSendIQ:(XMPPIQ *)iq withTag:(long)tag registeredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2485,10 +2504,12 @@ - (void)continueSendIQ:(XMPPIQ *)iq withTag:(long)tag withTimeout:TIMEOUT_XMPP_WRITE tag:tag]; - [multicastDelegate xmppStream:self didSendIQ:iq]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didSendIQ:iq]; + }]; } -- (void)continueSendMessage:(XMPPMessage *)message withTag:(long)tag +- (void)continueSendMessage:(XMPPMessage *)message withTag:(long)tag registeredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2503,10 +2524,12 @@ - (void)continueSendMessage:(XMPPMessage *)message withTag:(long)tag withTimeout:TIMEOUT_XMPP_WRITE tag:tag]; - [multicastDelegate xmppStream:self didSendMessage:message]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didSendMessage:message]; + }]; } -- (void)continueSendPresence:(XMPPPresence *)presence withTag:(long)tag +- (void)continueSendPresence:(XMPPPresence *)presence withTag:(long)tag registeredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2536,10 +2559,12 @@ - (void)continueSendPresence:(XMPPPresence *)presence withTag:(long)tag } } - [multicastDelegate xmppStream:self didSendPresence:presence]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didSendPresence:presence]; + }]; } -- (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag +- (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2556,7 +2581,11 @@ - (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag if ([customElementNames countForObject:[element name]]) { - [multicastDelegate xmppStream:self didSendCustomElement:element]; + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didSendCustomElement:element]; + }]; } } @@ -2564,22 +2593,22 @@ - (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag * Private method. * Presencts a common method for the various public sendElement methods. **/ -- (void)sendElement:(NSXMLElement *)element withTag:(long)tag +- (void)sendElement:(NSXMLElement *)element withTag:(long)tag registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); if ([element isKindOfClass:[XMPPIQ class]]) { - [self sendIQ:(XMPPIQ *)element withTag:tag]; + [self sendIQ:(XMPPIQ *)element withTag:tag registeringEventWithID:eventID]; } else if ([element isKindOfClass:[XMPPMessage class]]) { - [self sendMessage:(XMPPMessage *)element withTag:tag]; + [self sendMessage:(XMPPMessage *)element withTag:tag registeringEventWithID:eventID]; } else if ([element isKindOfClass:[XMPPPresence class]]) { - [self sendPresence:(XMPPPresence *)element withTag:tag]; + [self sendPresence:(XMPPPresence *)element withTag:tag registeringEventWithID:eventID]; } else { @@ -2587,19 +2616,19 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag if ([elementName isEqualToString:@"iq"]) { - [self sendIQ:[XMPPIQ iqFromElement:element] withTag:tag]; + [self sendIQ:[XMPPIQ iqFromElement:element] withTag:tag registeringEventWithID:eventID]; } else if ([elementName isEqualToString:@"message"]) { - [self sendMessage:[XMPPMessage messageFromElement:element] withTag:tag]; + [self sendMessage:[XMPPMessage messageFromElement:element] withTag:tag registeringEventWithID:eventID]; } else if ([elementName isEqualToString:@"presence"]) { - [self sendPresence:[XMPPPresence presenceFromElement:element] withTag:tag]; + [self sendPresence:[XMPPPresence presenceFromElement:element] withTag:tag registeringEventWithID:eventID]; } else { - [self continueSendElement:element withTag:tag]; + [self continueSendElement:element withTag:tag registeringEventWithID:eventID]; } } } @@ -2610,24 +2639,12 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag **/ - (void)sendElement:(NSXMLElement *)element { - if (element == nil) return; - - dispatch_block_t block = ^{ @autoreleasepool { - - if (state == STATE_XMPP_CONNECTED) - { - [self sendElement:element withTag:TAG_XMPP_WRITE_STREAM]; - } - else - { - [self failToSendElement:element]; - } - }}; - - if (dispatch_get_specific(xmppQueueTag)) - block(); - else - dispatch_async(xmppQueue, block); + [self sendElement:element registeringEventWithID:[self generateUUID] andGetReceipt:nil]; +} + +- (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt **)receiptPtr +{ + [self sendElement:element registeringEventWithID:[self generateUUID] andGetReceipt:receiptPtr]; } /** @@ -2637,57 +2654,62 @@ - (void)sendElement:(NSXMLElement *)element * After the element has been successfully sent, * the xmppStream:didSendElementWithTag: delegate method is called. **/ -- (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt **)receiptPtr +- (void)sendElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID andGetReceipt:(XMPPElementReceipt **)receiptPtr { if (element == nil) return; - if (receiptPtr == nil) - { - [self sendElement:element]; - } - else - { - __block XMPPElementReceipt *receipt = nil; - - dispatch_block_t block = ^{ @autoreleasepool { - - if (state == STATE_XMPP_CONNECTED) - { - receipt = [[XMPPElementReceipt alloc] init]; - [receipts addObject:receipt]; - - [self sendElement:element withTag:TAG_XMPP_WRITE_RECEIPT]; - } + BOOL isReceiptRequested = receiptPtr != nil; + __block XMPPElementReceipt *receipt; + + dispatch_block_t block = ^{ @autoreleasepool { + if (state == STATE_XMPP_CONNECTED) + { + if (isReceiptRequested) + { + receipt = [[XMPPElementReceipt alloc] init]; + [receipts addObject:receipt]; + + [self sendElement:element withTag:TAG_XMPP_WRITE_RECEIPT registeringEventWithID:eventID]; + } else { - [self failToSendElement:element]; + [self sendElement:element withTag:TAG_XMPP_WRITE_STREAM registeringEventWithID:eventID]; } - }}; - - if (dispatch_get_specific(xmppQueueTag)) - block(); - else - dispatch_sync(xmppQueue, block); - + } + else + { + [self failToSendElement:element registeringEventWithID:eventID]; + } + }}; + + if (dispatch_get_specific(xmppQueueTag)) + block(); + else if (receiptPtr) + dispatch_sync(xmppQueue, block); + else + dispatch_async(xmppQueue, block); + + if (receiptPtr) *receiptPtr = receipt; - } } -- (void)failToSendElement:(NSXMLElement *)element +- (void)failToSendElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; + if ([element isKindOfClass:[XMPPIQ class]]) { - [self failToSendIQ:(XMPPIQ *)element]; + [self failToSendIQ:(XMPPIQ *)element withRegisteredEvent:event]; } else if ([element isKindOfClass:[XMPPMessage class]]) { - [self failToSendMessage:(XMPPMessage *)element]; + [self failToSendMessage:(XMPPMessage *)element withRegisteredEvent:event]; } else if ([element isKindOfClass:[XMPPPresence class]]) { - [self failToSendPresence:(XMPPPresence *)element]; + [self failToSendPresence:(XMPPPresence *)element withRegisteredEvent:event]; } else { @@ -2695,20 +2717,20 @@ - (void)failToSendElement:(NSXMLElement *)element if ([elementName isEqualToString:@"iq"]) { - [self failToSendIQ:[XMPPIQ iqFromElement:element]]; + [self failToSendIQ:[XMPPIQ iqFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"message"]) { - [self failToSendMessage:[XMPPMessage messageFromElement:element]]; + [self failToSendMessage:[XMPPMessage messageFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"presence"]) { - [self failToSendPresence:[XMPPPresence presenceFromElement:element]]; + [self failToSendPresence:[XMPPPresence presenceFromElement:element] withRegisteredEvent:event]; } } } -- (void)failToSendIQ:(XMPPIQ *)iq +- (void)failToSendIQ:(XMPPIQ *)iq withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); @@ -2716,10 +2738,12 @@ - (void)failToSendIQ:(XMPPIQ *)iq code:XMPPStreamInvalidState userInfo:nil]; - [multicastDelegate xmppStream:self didFailToSendIQ:iq error:error]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didFailToSendIQ:iq error:error]; + }]; } -- (void)failToSendMessage:(XMPPMessage *)message +- (void)failToSendMessage:(XMPPMessage *)message withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); @@ -2727,10 +2751,12 @@ - (void)failToSendMessage:(XMPPMessage *)message code:XMPPStreamInvalidState userInfo:nil]; - [multicastDelegate xmppStream:self didFailToSendMessage:message error:error]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didFailToSendMessage:message error:error]; + }]; } -- (void)failToSendPresence:(XMPPPresence *)presence +- (void)failToSendPresence:(XMPPPresence *)presence withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); @@ -2738,7 +2764,9 @@ - (void)failToSendPresence:(XMPPPresence *)presence code:XMPPStreamInvalidState userInfo:nil]; - [multicastDelegate xmppStream:self didFailToSendPresence:presence error:error]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didFailToSendPresence:presence error:error]; + }]; } /** @@ -2831,7 +2859,7 @@ - (void)sendBindElement:(NSXMLElement *)element dispatch_async(xmppQueue, block); } -- (void)receiveIQ:(XMPPIQ *)iq +- (void)receiveIQ:(XMPPIQ *)iq withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2853,14 +2881,14 @@ - (void)receiveIQ:(XMPPIQ *)iq dispatch_async(willReceiveStanzaQueue, ^{ dispatch_async(xmppQueue, ^{ @autoreleasepool { if (state == STATE_XMPP_CONNECTED) { - [self continueReceiveIQ:iq]; + [self continueReceiveIQ:iq withRegisteredEvent:event]; } }}); }); } else { - [self continueReceiveIQ:iq]; + [self continueReceiveIQ:iq withRegisteredEvent:event]; } } else @@ -2896,7 +2924,7 @@ - (void)receiveIQ:(XMPPIQ *)iq if (state == STATE_XMPP_CONNECTED) { if (modifiedIQ) - [self continueReceiveIQ:modifiedIQ]; + [self continueReceiveIQ:modifiedIQ withRegisteredEvent:event]; else [multicastDelegate xmppStreamDidFilterStanza:self]; } @@ -2905,7 +2933,7 @@ - (void)receiveIQ:(XMPPIQ *)iq } } -- (void)receiveMessage:(XMPPMessage *)message +- (void)receiveMessage:(XMPPMessage *)message withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -2928,14 +2956,14 @@ - (void)receiveMessage:(XMPPMessage *)message dispatch_async(xmppQueue, ^{ @autoreleasepool { if (state == STATE_XMPP_CONNECTED) { - [self continueReceiveMessage:message]; + [self continueReceiveMessage:message withRegisteredEvent:event]; } }}); }); } else { - [self continueReceiveMessage:message]; + [self continueReceiveMessage:message withRegisteredEvent:event]; } } else @@ -2971,7 +2999,7 @@ - (void)receiveMessage:(XMPPMessage *)message if (state == STATE_XMPP_CONNECTED) { if (modifiedMessage) - [self continueReceiveMessage:modifiedMessage]; + [self continueReceiveMessage:modifiedMessage withRegisteredEvent:event]; else [multicastDelegate xmppStreamDidFilterStanza:self]; } @@ -2980,7 +3008,7 @@ - (void)receiveMessage:(XMPPMessage *)message } } -- (void)receivePresence:(XMPPPresence *)presence +- (void)receivePresence:(XMPPPresence *)presence withRegisteredEvent:(XMPPElementEvent *)event { NSAssert(dispatch_get_specific(xmppQueueTag), @"Invoked on incorrect queue"); NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state"); @@ -3003,14 +3031,14 @@ - (void)receivePresence:(XMPPPresence *)presence dispatch_async(xmppQueue, ^{ @autoreleasepool { if (state == STATE_XMPP_CONNECTED) { - [self continueReceivePresence:presence]; + [self continueReceivePresence:presence withRegisteredEvent:event]; } }}); }); } else { - [self continueReceivePresence:presence]; + [self continueReceivePresence:presence withRegisteredEvent:event]; } } else @@ -3046,7 +3074,7 @@ - (void)receivePresence:(XMPPPresence *)presence if (state == STATE_XMPP_CONNECTED) { if (modifiedPresence) - [self continueReceivePresence:presence]; + [self continueReceivePresence:presence withRegisteredEvent:event]; else [multicastDelegate xmppStreamDidFilterStanza:self]; } @@ -3055,7 +3083,7 @@ - (void)receivePresence:(XMPPPresence *)presence } } -- (void)continueReceiveIQ:(XMPPIQ *)iq +- (void)continueReceiveIQ:(XMPPIQ *)iq withRegisteredEvent:(XMPPElementEvent *)event { if ([iq requiresResponse]) { @@ -3066,27 +3094,32 @@ - (void)continueReceiveIQ:(XMPPIQ *)iq // So we notifiy all interested delegates and modules about the received IQ, // keeping track of whether or not any of them have handled it. - GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator]; - - id del; - dispatch_queue_t dq; - - SEL selector = @selector(xmppStream:didReceiveIQ:); - 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 xmppStream:self didReceiveIQ:iq]) - { - dispatch_semaphore_signal(delSemaphore); - } - - }}); - } + dispatch_group_enter(delGroup); + id didReceiveIqProcessingToken = [event beginDelayedProcessing]; + + [self performDelegateActionWithElementEvent:event block:^{ + + GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator]; + id delegate; + dispatch_queue_t delegateQueue; + + while ([delegateEnumerator getNextDelegate:&delegate delegateQueue:&delegateQueue forSelector:@selector(xmppStream:didReceiveIQ:)]) + { + dispatch_group_async(delGroup, delegateQueue, ^{ @autoreleasepool { + + if ([delegate xmppStream:self didReceiveIQ:iq]) + { + dispatch_semaphore_signal(delSemaphore); + } + + }}); + } + + dispatch_group_leave(delGroup); + }]; dispatch_async(didReceiveIqQueue, ^{ @autoreleasepool { @@ -3144,6 +3177,7 @@ - (void)continueReceiveIQ:(XMPPIQ *)iq dispatch_release(delGroup); #endif + [event endDelayedProcessingWithToken:didReceiveIqProcessingToken]; }}); } else @@ -3151,18 +3185,24 @@ - (void)continueReceiveIQ:(XMPPIQ *)iq // The IQ doesn't require a response. // So we can just fire the delegate method and ignore the responses. - [multicastDelegate xmppStream:self didReceiveIQ:iq]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceiveIQ:iq]; + }]; } } -- (void)continueReceiveMessage:(XMPPMessage *)message +- (void)continueReceiveMessage:(XMPPMessage *)message withRegisteredEvent:(XMPPElementEvent *)event { - [multicastDelegate xmppStream:self didReceiveMessage:message]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceiveMessage:message]; + }]; } -- (void)continueReceivePresence:(XMPPPresence *)presence +- (void)continueReceivePresence:(XMPPPresence *)presence withRegisteredEvent:(XMPPElementEvent *)event { - [multicastDelegate xmppStream:self didReceivePresence:presence]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceivePresence:presence]; + }]; } /** @@ -3170,6 +3210,11 @@ - (void)continueReceivePresence:(XMPPPresence *)presence * This is an advanced technique, but makes for some interesting possibilities. **/ - (void)injectElement:(NSXMLElement *)element +{ + [self injectElement:element registeringEventWithID:[self generateUUID]]; +} + +- (void)injectElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID { if (element == nil) return; @@ -3179,18 +3224,20 @@ - (void)injectElement:(NSXMLElement *)element { return_from_block; } + + XMPPElementEvent *event = [self generateElementEventWithID:eventID]; if ([element isKindOfClass:[XMPPIQ class]]) { - [self receiveIQ:(XMPPIQ *)element]; + [self receiveIQ:(XMPPIQ *)element withRegisteredEvent:event]; } else if ([element isKindOfClass:[XMPPMessage class]]) { - [self receiveMessage:(XMPPMessage *)element]; + [self receiveMessage:(XMPPMessage *)element withRegisteredEvent:event]; } else if ([element isKindOfClass:[XMPPPresence class]]) { - [self receivePresence:(XMPPPresence *)element]; + [self receivePresence:(XMPPPresence *)element withRegisteredEvent:event]; } else { @@ -3198,19 +3245,21 @@ - (void)injectElement:(NSXMLElement *)element if ([elementName isEqualToString:@"iq"]) { - [self receiveIQ:[XMPPIQ iqFromElement:element]]; + [self receiveIQ:[XMPPIQ iqFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"message"]) { - [self receiveMessage:[XMPPMessage messageFromElement:element]]; + [self receiveMessage:[XMPPMessage messageFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"presence"]) { - [self receivePresence:[XMPPPresence presenceFromElement:element]]; + [self receivePresence:[XMPPPresence presenceFromElement:element] withRegisteredEvent:event]; } else if ([customElementNames countForObject:elementName]) { - [multicastDelegate xmppStream:self didReceiveCustomElement:element]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceiveCustomElement:element]; + }]; } else { @@ -4630,17 +4679,19 @@ - (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element } else { + XMPPElementEvent *event = [self generateElementEventWithID:[self generateUUID]]; + if ([elementName isEqualToString:@"iq"]) { - [self receiveIQ:[XMPPIQ iqFromElement:element]]; + [self receiveIQ:[XMPPIQ iqFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"message"]) { - [self receiveMessage:[XMPPMessage messageFromElement:element]]; + [self receiveMessage:[XMPPMessage messageFromElement:element] withRegisteredEvent:event]; } else if ([elementName isEqualToString:@"presence"]) { - [self receivePresence:[XMPPPresence presenceFromElement:element]]; + [self receivePresence:[XMPPPresence presenceFromElement:element] withRegisteredEvent:event]; } else if ([self isP2P] && ([elementName isEqualToString:@"stream:features"] || [elementName isEqualToString:@"features"])) @@ -4649,7 +4700,9 @@ - (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element } else if ([customElementNames countForObject:elementName]) { - [multicastDelegate xmppStream:self didReceiveCustomElement:element]; + [self performDelegateActionWithElementEvent:event block:^{ + [multicastDelegate xmppStream:self didReceiveCustomElement:element]; + }]; } else { @@ -5059,6 +5112,16 @@ - (void)enumerateModulesOfClass:(Class)aClass withBlock:(void (^)(XMPPModule *mo }]; } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#pragma mark Element Event Context +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +- (XMPPElementEvent *)currentElementEvent +{ + XMPPElementEvent *event = [GCDMulticastDelegateInvocationContext currentContext].value; + return event.xmppStream == self ? event : nil; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Utilities //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -5080,6 +5143,23 @@ - (GCDAsyncSocket*) newSocket { return socket; } +- (XMPPElementEvent *)generateElementEventWithID:(NSString *)eventID +{ + return [[XMPPElementEvent alloc] initWithStream:self uniqueID:eventID myJID:self.myJID timestamp:[NSDate date]]; +} + +- (void)performDelegateActionWithElementEvent:(XMPPElementEvent *)event block:(dispatch_block_t)block +{ + GCDMulticastDelegateInvocationContext *eventProcessingDelegateInvocationContext = [[GCDMulticastDelegateInvocationContext alloc] initWithValue:event]; + + [eventProcessingDelegateInvocationContext becomeCurrentOnQueue:self.xmppQueue forActionWithBlock:block]; + + dispatch_group_notify(eventProcessingDelegateInvocationContext.continuityGroup, self.xmppQueue, ^{ + event.processingCompleted = YES; + [multicastDelegate xmppStream:self didFinishProcessingElementEvent:event]; + }); +} + @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -5165,3 +5245,55 @@ - (void)dealloc } @end + +@implementation XMPPElementEvent + +- (instancetype)initWithStream:(XMPPStream *)xmppStream uniqueID:(NSString *)uniqueID myJID:(XMPPJID *)myJID timestamp:(NSDate *)timestamp +{ + self = [super init]; + if (self) { + _xmppStream = xmppStream; + _uniqueID = [uniqueID copy]; + _myJID = myJID; + _timestamp = timestamp; + } + return self; +} + +- (BOOL)isProcessingCompleted +{ + __block BOOL result; + + dispatch_block_t block = ^{ + result = _processingCompleted; + }; + + if (dispatch_get_specific(self.xmppStream.xmppQueueTag)) + block(); + else + dispatch_sync(self.xmppStream.xmppQueue, block); + + return result; +} + +- (id)beginDelayedProcessing +{ + GCDMulticastDelegateInvocationContext *currentContext = [GCDMulticastDelegateInvocationContext currentContext]; + NSAssert(currentContext.value == self, @"Delayed processing can only be initiated in the context matching the current event"); + + dispatch_group_enter(currentContext.continuityGroup); + + return currentContext; +} + +- (void)endDelayedProcessingWithToken:(id)delayedProcessingToken +{ + NSAssert([delayedProcessingToken isKindOfClass:[GCDMulticastDelegateInvocationContext class]], @"Invalid delayed processing token"); + + GCDMulticastDelegateInvocationContext *originalContext = delayedProcessingToken; + NSAssert(originalContext.value == self, @"Delayed processing token mismatch"); + + dispatch_group_leave(originalContext.continuityGroup); +} + +@end diff --git a/Utilities/GCDMulticastDelegate.m b/Utilities/GCDMulticastDelegate.m index 4d9f5f2ae0..5f6f3ca6b4 100644 --- a/Utilities/GCDMulticastDelegate.m +++ b/Utilities/GCDMulticastDelegate.m @@ -708,7 +708,8 @@ - (dispatch_queue_t)transferContextToTargetQueue:(dispatch_queue_t)targetQueue { dispatch_group_enter(self.continuityGroup); - dispatch_queue_t contextTransferQueue = dispatch_queue_create_with_target("GCDMulticastDelegateInvocationContext.contextTransferQueue", nil, targetQueue); + dispatch_queue_t contextTransferQueue = dispatch_queue_create("GCDMulticastDelegateInvocationContext.contextTransferQueue", NULL); + dispatch_set_target_queue(contextTransferQueue, targetQueue); dispatch_queue_set_specific(contextTransferQueue, GCDMulticastDelegateInvocationContextKey, (void *)CFBridgingRetain(self), From 613183ac475cf99fbdc0a3f07dbd0fff2a672465 Mon Sep 17 00:00:00 2001 From: Piotr Wegrzynek Date: Sat, 18 Nov 2017 14:04:16 +0100 Subject: [PATCH 3/3] Apply pull request review feedback Squashed commits: [73bf7ae] Rename XMPPElementEvent processingCompleted property [d1bbfce] Move XMPPElementEvent interface declaration to a separate header file [b7ac0a7] Use property syntax for XMPPStream currentElementEvent [ba381c4] Reorder XMPPStream sendElement method parameters --- Core/XMPP.h | 1 + Core/XMPPElementEvent.h | 80 ++++++++++++++++++++++ Core/XMPPStream.h | 89 +++---------------------- Core/XMPPStream.m | 12 ++-- XMPPFramework.xcodeproj/project.pbxproj | 8 +++ 5 files changed, 103 insertions(+), 87 deletions(-) create mode 100644 Core/XMPPElementEvent.h diff --git a/Core/XMPP.h b/Core/XMPP.h index 736e7a508e..6d4e4ffc9c 100644 --- a/Core/XMPP.h +++ b/Core/XMPP.h @@ -9,6 +9,7 @@ #import "XMPPMessage.h" #import "XMPPPresence.h" #import "XMPPModule.h" +#import "XMPPElementEvent.h" // // Authentication diff --git a/Core/XMPPElementEvent.h b/Core/XMPPElementEvent.h new file mode 100644 index 0000000000..dc4feddc81 --- /dev/null +++ b/Core/XMPPElementEvent.h @@ -0,0 +1,80 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPJID; + +/** + * A handle that allows identifying elements sent or received in the stream across different delegates + * and tracking their processing progress. + * + * While the core XMPP specification does not require stanzas to be uniquely identifiable, you may still want to + * identify them internally across different modules or trace the sent ones to the respective send result delegate callbacks. + * + * An instance of this class is provided in the context of execution of any of the @c didSendXXX/didFailToSendXXX/didReceiveXXX + * stream delegate methods. It is retrieved from the @c currentElementEvent property on the calling stream. + * The delegates can then use it to: + * - identify the corresponding XMPP stanzas. + * - be notified of asynchronous processing completion for a given XMPP stanza. + * + * Using @c XMPPElementEvent handles is a more robust approach than relying on pointer equality of @c XMPPElement instances. + */ +@interface XMPPElementEvent : NSObject + +/// The universally unique identifier of the event that provides the internal identity of the corresponding XMPP stanza. +@property (nonatomic, copy, readonly) NSString *uniqueID; + +/// The value of the stream's @c myJID property at the time when the event occured. +@property (nonatomic, strong, readonly, nullable) XMPPJID *myJID; + +/// The local device time when the event occured. +@property (nonatomic, strong, readonly) NSDate *timestamp; + +/** + * A flag indicating whether all delegates are done processing the given event. + * + * Supports Key-Value Observing. Change notifications are emitted on the stream queue. + * + * @see beginDelayedProcessing + * @see endDelayedProcessingWithToken + */ +@property (nonatomic, assign, readonly) BOOL isProcessingCompleted; + +// Instances are created by the stream only. +- (instancetype)init NS_UNAVAILABLE; + +/** + * Marks the event as being asynchronously processed by a delegate and returns a completion token. + * + * Event processing is completed after every @c beginDelayedProcessing call has been followed + * by @c endDelayedProcessingWithToken: with a matching completion token. + * + * Unpaired invocations may lead to undefined behavior or stalled events. + * + * Events that are not marked for asynchronous processing by any of the delegates complete immediately + * after control returns from all callbacks. + * + * @see endDelayedProcessingWithToken: + * @see isProcessingCompleted + */ +- (id)beginDelayedProcessing; + +/** + * Marks an end of the previously initiated asynchronous delegate processing. + * + * Event processing is completed after every @c beginDelayedProcessing call has been followed + * by @c endDelayedProcessingWithToken: with a matching completion token. + * + * Unpaired invocations may lead to undefined behavior or stalled events. + * + * Events that are not marked for asynchronous processing by any of the delegates complete immediately + * after control returns from all callbacks. + * + * @see beginDelayedProcessing + * @see isProcessingCompleted + */ +- (void)endDelayedProcessingWithToken:(id)delayedProcessingToken; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Core/XMPPStream.h b/Core/XMPPStream.h index 70dbea6212..84c7dbd005 100644 --- a/Core/XMPPStream.h +++ b/Core/XMPPStream.h @@ -646,7 +646,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * Even if you close the xmpp stream after this point, the OS will still do everything it can to send the data. **/ - (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt * _Nullable * _Nullable)receiptPtr; -- (void)sendElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID andGetReceipt:(XMPPElementReceipt * _Nullable * _Nullable)receiptPtr; +- (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt * _Nullable * _Nullable)receiptPtr registeringEventWithID:(NSString *)eventID; /** * Fetches and resends the myPresence element (if available) in a single atomic operation. @@ -741,13 +741,13 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** - * Returns the stream metadata corresponding to the currently processed XMPP stanza. + * The stream metadata corresponding to the currently processed XMPP stanza. * * Event information is only available in the context of @c didSendXXX/didFailToSendXXX/didReceiveXXX delegate callbacks. - * This method returns nil if called outside of those callbacks. + * This property is nil if accessed outside of those callbacks. * For more details, please refer to @c XMPPElementEvent documentation. */ -- (nullable XMPPElementEvent *)currentElementEvent; +@property (nonatomic, readonly, nullable) XMPPElementEvent *currentElementEvent; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Utilities @@ -801,79 +801,6 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; @end -/** - * A handle that allows identifying elements sent or received in the stream across different delegates - * and tracking their processing progress. - * - * While the core XMPP specification does not require stanzas to be uniquely identifiable, you may still want to - * identify them internally across different modules or trace the sent ones to the respective send result delegate callbacks. - * - * An instance of this class is provided in the context of execution of any of the @c didSendXXX/didFailToSendXXX/didReceiveXXX - * stream delegate methods. It is retrieved by calling the @c currentElementEvent method on the calling stream. - * The delegates can then use it to: - * - identify the corresponding XMPP stanzas. - * - be notified of asynchronous processing completion for a given XMPP stanza. - * - * Using @c XMPPElementEvent handles is a more robust approach than relying on pointer equality of @c XMPPElement instances. - */ -@interface XMPPElementEvent : NSObject - -/// The universally unique identifier of the event that provides the internal identity of the corresponding XMPP stanza. -@property (nonatomic, copy, readonly) NSString *uniqueID; - -/// The value of the stream's @c myJID property at the time when the event occured. -@property (nonatomic, strong, readonly, nullable) XMPPJID *myJID; - -/// The local device time when the event occured. -@property (nonatomic, strong, readonly) NSDate *timestamp; - -/** - * A flag indicating whether all delegates are done processing the given event. - * - * Supports Key-Value Observing. Change notifications are emitted on the stream queue. - * - * @see beginDelayedProcessing - * @see endDelayedProcessingWithToken - */ -@property (nonatomic, assign, readonly, getter=isProcessingCompleted) BOOL processingCompleted; - -// Instances are created by the stream only. -- (instancetype)init NS_UNAVAILABLE; - -/** - * Marks the event as being asynchronously processed by a delegate and returns a completion token. - * - * Event processing is completed after every @c beginDelayedProcessing call has been followed - * by @c endDelayedProcessingWithToken: with a matching completion token. - * - * Unpaired invocations may lead to undefined behavior or stalled events. - * - * Events that are not marked for asynchronous processing by any of the delegates complete immediately - * after control returns from all callbacks. - * - * @see endDelayedProcessingWithToken: - * @see processingCompleted - */ -- (id)beginDelayedProcessing; - -/** - * Marks an end of the previously initiated asynchronous delegate processing. - * - * Event processing is completed after every @c beginDelayedProcessing call has been followed - * by @c endDelayedProcessingWithToken: with a matching completion token. - * - * Unpaired invocations may lead to undefined behavior or stalled events. - * - * Events that are not marked for asynchronous processing by any of the delegates complete immediately - * after control returns from all callbacks. - * - * @see beginDelayedProcessing - * @see processingCompleted - */ -- (void)endDelayedProcessingWithToken:(id)delayedProcessingToken; - -@end - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1077,7 +1004,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * If you have need to modify an element for any reason, * you should copy the element first, and then modify and use the copy. * - * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * Delegates can obtain event metadata associated with the respective element by accessing @c currentElementEvent on @c sender * from within these callbacks. For more details, please refer to @c XMPPElementEvent documentation. **/ - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq; @@ -1125,7 +1052,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * These methods may be used to listen for certain events (such as an unavailable presence having been sent), * or for general logging purposes. (E.g. a central history logging mechanism). * - * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * Delegates can obtain event metadata associated with the respective element by accessing @c currentElementEvent on @c sender * from within these callbacks. For more details, please refer to @c XMPPElementEvent documentation. **/ - (void)xmppStream:(XMPPStream *)sender didSendIQ:(XMPPIQ *)iq; @@ -1136,7 +1063,7 @@ extern const NSTimeInterval XMPPStreamTimeoutNone; * These methods are called after failing to send the respective XML elements over the stream. * This occurs when the stream gets disconnected before the element can get sent out. * - * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * Delegates can obtain event metadata associated with the respective element by accessing @c currentElementEvent on @c sender * from within these callbacks. * Note that if these methods are called, the event context is incomplete, e.g. the stream might have not been connected * and the actual myJID value is not determined. For more details, please refer to @c XMPPElementEvent documentation. @@ -1244,7 +1171,7 @@ NS_SWIFT_NAME(xmppStream(_:didFinishProcessing:)); * If you're using custom elements, you must register the custom element name(s). * Otherwise the xmppStream will treat non-XMPP elements as errors (xmppStream:didReceiveError:). * - * Delegates can obtain event metadata associated with the respective element by calling @c currentElementEvent on @c sender + * Delegates can obtain event metadata associated with the respective element by accessing @c currentElementEvent on @c sender * from within these callbacks. For more details, please refer to @c XMPPElementEvent documentation. * * @see registerCustomElementNames (in XMPPInternal.h) diff --git a/Core/XMPPStream.m b/Core/XMPPStream.m index d5f8011e66..465b7724f5 100644 --- a/Core/XMPPStream.m +++ b/Core/XMPPStream.m @@ -156,7 +156,7 @@ - (void)signalFailure; @interface XMPPElementEvent () @property (nonatomic, unsafe_unretained, readonly) XMPPStream *xmppStream; -@property (nonatomic, assign, readwrite, getter=isProcessingCompleted) BOOL processingCompleted; +@property (nonatomic, assign, readwrite) BOOL isProcessingCompleted; @end @@ -2639,12 +2639,12 @@ - (void)sendElement:(NSXMLElement *)element withTag:(long)tag registeringEventWi **/ - (void)sendElement:(NSXMLElement *)element { - [self sendElement:element registeringEventWithID:[self generateUUID] andGetReceipt:nil]; + [self sendElement:element andGetReceipt:nil registeringEventWithID:[self generateUUID]]; } - (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt **)receiptPtr { - [self sendElement:element registeringEventWithID:[self generateUUID] andGetReceipt:receiptPtr]; + [self sendElement:element andGetReceipt:receiptPtr registeringEventWithID:[self generateUUID]]; } /** @@ -2654,7 +2654,7 @@ - (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt ** * After the element has been successfully sent, * the xmppStream:didSendElementWithTag: delegate method is called. **/ -- (void)sendElement:(NSXMLElement *)element registeringEventWithID:(NSString *)eventID andGetReceipt:(XMPPElementReceipt **)receiptPtr +- (void)sendElement:(NSXMLElement *)element andGetReceipt:(XMPPElementReceipt **)receiptPtr registeringEventWithID:(NSString *)eventID { if (element == nil) return; @@ -5155,7 +5155,7 @@ - (void)performDelegateActionWithElementEvent:(XMPPElementEvent *)event block:(d [eventProcessingDelegateInvocationContext becomeCurrentOnQueue:self.xmppQueue forActionWithBlock:block]; dispatch_group_notify(eventProcessingDelegateInvocationContext.continuityGroup, self.xmppQueue, ^{ - event.processingCompleted = YES; + event.isProcessingCompleted = YES; [multicastDelegate xmppStream:self didFinishProcessingElementEvent:event]; }); } @@ -5265,7 +5265,7 @@ - (BOOL)isProcessingCompleted __block BOOL result; dispatch_block_t block = ^{ - result = _processingCompleted; + result = _isProcessingCompleted; }; if (dispatch_get_specific(self.xmppStream.xmppQueueTag)) diff --git a/XMPPFramework.xcodeproj/project.pbxproj b/XMPPFramework.xcodeproj/project.pbxproj index a6aed100a5..dc5c318795 100644 --- a/XMPPFramework.xcodeproj/project.pbxproj +++ b/XMPPFramework.xcodeproj/project.pbxproj @@ -77,6 +77,9 @@ D95367E61FB90F0E0075502D /* XMPPBookmarksStorageElement.m in Sources */ = {isa = PBXBuildFile; fileRef = D95367E21FB90F0E0075502D /* XMPPBookmarksStorageElement.m */; }; D95367E71FB90F0E0075502D /* XMPPBookmarksStorageElement.m in Sources */ = {isa = PBXBuildFile; fileRef = D95367E21FB90F0E0075502D /* XMPPBookmarksStorageElement.m */; }; D95367E81FB90F0E0075502D /* XMPPBookmarksStorageElement.m in Sources */ = {isa = PBXBuildFile; fileRef = D95367E21FB90F0E0075502D /* XMPPBookmarksStorageElement.m */; }; + 249704751FC0680900D25EC6 /* XMPPElementEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 249704731FC0680900D25EC6 /* XMPPElementEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 249704761FC0680900D25EC6 /* XMPPElementEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 249704731FC0680900D25EC6 /* XMPPElementEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 249704771FC0680900D25EC6 /* XMPPElementEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 249704731FC0680900D25EC6 /* XMPPElementEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; D96D6E7D1F8D9701006DEC58 /* XMPPPushModule.m in Sources */ = {isa = PBXBuildFile; fileRef = D96D6E7C1F8D9701006DEC58 /* XMPPPushModule.m */; }; D96D6E7E1F8D9701006DEC58 /* XMPPPushModule.m in Sources */ = {isa = PBXBuildFile; fileRef = D96D6E7C1F8D9701006DEC58 /* XMPPPushModule.m */; }; D96D6E7F1F8D9701006DEC58 /* XMPPPushModule.m in Sources */ = {isa = PBXBuildFile; fileRef = D96D6E7C1F8D9701006DEC58 /* XMPPPushModule.m */; }; @@ -1489,6 +1492,7 @@ D95367DA1FB90DA00075502D /* XMPPBookmark.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XMPPBookmark.m; sourceTree = ""; }; D95367E11FB90F0E0075502D /* XMPPBookmarksStorageElement.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XMPPBookmarksStorageElement.h; sourceTree = ""; }; D95367E21FB90F0E0075502D /* XMPPBookmarksStorageElement.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XMPPBookmarksStorageElement.m; sourceTree = ""; }; + 249704731FC0680900D25EC6 /* XMPPElementEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XMPPElementEvent.h; sourceTree = ""; }; D96D6E6C1F8D9700006DEC58 /* XMPPPushModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XMPPPushModule.h; sourceTree = ""; }; D96D6E7C1F8D9701006DEC58 /* XMPPPushModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XMPPPushModule.m; sourceTree = ""; }; D96D6E981F8D9D7E006DEC58 /* XMPPMessage+XEP_0359.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XMPPMessage+XEP_0359.h"; sourceTree = ""; }; @@ -1924,6 +1928,7 @@ 0D44BB0A1E5370ED000930E0 /* XMPPPresence.m */, 0D44BB0B1E5370ED000930E0 /* XMPPStream.h */, 0D44BB0C1E5370ED000930E0 /* XMPPStream.m */, + 249704731FC0680900D25EC6 /* XMPPElementEvent.h */, ); path = Core; sourceTree = SOURCE_ROOT; @@ -2959,6 +2964,7 @@ D9DCD3241E6250930010D1C7 /* XMPPMessage+XEP_0334.h in Headers */, D96D6E801F8D970E006DEC58 /* XMPPPushModule.h in Headers */, D9DCD3281E6250930010D1C7 /* NSXMLElement+XEP_0352.h in Headers */, + 249704751FC0680900D25EC6 /* XMPPElementEvent.h in Headers */, D9DCD2541E6250930010D1C7 /* XMPPOutgoingFileTransfer.h in Headers */, D9DCD2561E6250930010D1C7 /* XMPPGoogleSharedStatus.h in Headers */, D9DCD2B61E6250930010D1C7 /* XMPPvCardTemp.h in Headers */, @@ -3155,6 +3161,7 @@ D9DCD4A51E6256D90010D1C7 /* XMPPMessage+XEP_0334.h in Headers */, D96D6E811F8D970E006DEC58 /* XMPPPushModule.h in Headers */, D9DCD4A61E6256D90010D1C7 /* NSXMLElement+XEP_0352.h in Headers */, + 249704761FC0680900D25EC6 /* XMPPElementEvent.h in Headers */, D9DCD4A71E6256D90010D1C7 /* XMPPOutgoingFileTransfer.h in Headers */, D9DCD4A81E6256D90010D1C7 /* XMPPGoogleSharedStatus.h in Headers */, D9DCD4A91E6256D90010D1C7 /* XMPPvCardTemp.h in Headers */, @@ -3327,6 +3334,7 @@ D9DCD6081E6258CF0010D1C7 /* XMPPMessage+XEP_0334.h in Headers */, D96D6E821F8D970F006DEC58 /* XMPPPushModule.h in Headers */, D9DCD6091E6258CF0010D1C7 /* NSXMLElement+XEP_0352.h in Headers */, + 249704771FC0680900D25EC6 /* XMPPElementEvent.h in Headers */, D9DCD60A1E6258CF0010D1C7 /* XMPPOutgoingFileTransfer.h in Headers */, D9DCD60B1E6258CF0010D1C7 /* XMPPGoogleSharedStatus.h in Headers */, D9DCD60C1E6258CF0010D1C7 /* XMPPvCardTemp.h in Headers */,