- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,61 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class DDListEnumerator;
|
||||
|
||||
struct DDListNode {
|
||||
void * element;
|
||||
struct DDListNode * prev;
|
||||
struct DDListNode * next;
|
||||
};
|
||||
typedef struct DDListNode DDListNode;
|
||||
|
||||
|
||||
/**
|
||||
* The DDList class is designed as a simple list class.
|
||||
* It can store objective-c objects as well as non-objective-c pointers.
|
||||
* It does not retain objective-c objects as it treats all elements as simple pointers.
|
||||
*
|
||||
* Example usages:
|
||||
* - Storing a list of delegates, where there is a desire to not retain the individual delegates.
|
||||
* - Storing a list of dispatch timers, which are not NSObjects, and cannot be stored in NSCollection classes.
|
||||
*
|
||||
* This class is NOT thread-safe.
|
||||
* It is designed to be used within a thread-safe context (e.g. within a single dispatch_queue).
|
||||
**/
|
||||
@interface DDList : NSObject <NSFastEnumeration>
|
||||
{
|
||||
DDListNode *listHead;
|
||||
DDListNode *listTail;
|
||||
}
|
||||
|
||||
- (void)add:(void *)element;
|
||||
- (void)remove:(void *)element;
|
||||
- (void)removeAll:(void *)element;
|
||||
- (void)removeAllElements;
|
||||
|
||||
- (BOOL)contains:(void *)element;
|
||||
|
||||
- (NSUInteger)count;
|
||||
|
||||
/**
|
||||
* The enumerators return a snapshot of the list that can be enumerated.
|
||||
* The list can later be altered (elements added/removed) without affecting enumerator snapshots.
|
||||
**/
|
||||
- (DDListEnumerator *)listEnumerator;
|
||||
- (DDListEnumerator *)reverseListEnumerator;
|
||||
|
||||
@end
|
||||
|
||||
@interface DDListEnumerator : NSObject
|
||||
{
|
||||
NSUInteger numElements;
|
||||
NSUInteger currentElementIndex;
|
||||
void **elements;
|
||||
}
|
||||
|
||||
- (NSUInteger)numTotal;
|
||||
- (NSUInteger)numLeft;
|
||||
|
||||
- (void *)nextElement;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,297 @@
|
||||
#import "DDList.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
|
||||
@interface DDListEnumerator (PrivateAPI)
|
||||
|
||||
- (id)initWithList:(DDListNode *)list reverse:(BOOL)reverse;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation DDList
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
listHead = NULL;
|
||||
listTail = NULL;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)add:(void *)element
|
||||
{
|
||||
if(element == NULL) return;
|
||||
|
||||
DDListNode *node = malloc(sizeof(DDListNode));
|
||||
|
||||
node->element = element;
|
||||
|
||||
// Remember: The list is a linked list of DDListNode objects.
|
||||
// Each node object is allocated and placed in the list.
|
||||
// It is not deallocated until it is later removed from the linked list.
|
||||
|
||||
if (listTail == NULL)
|
||||
{
|
||||
node->next = NULL;
|
||||
node->prev = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
node->next = NULL;
|
||||
node->prev = listTail;
|
||||
node->prev->next = node;
|
||||
}
|
||||
|
||||
listTail = node;
|
||||
|
||||
if (listHead == NULL)
|
||||
listHead = node;
|
||||
}
|
||||
|
||||
- (void)remove:(void *)element allInstances:(BOOL)allInstances
|
||||
{
|
||||
if(element == NULL) return;
|
||||
|
||||
DDListNode *node = listHead;
|
||||
while (node != NULL)
|
||||
{
|
||||
DDListNode *nextNode = node->next;
|
||||
if (element == node->element)
|
||||
{
|
||||
// Remove the node from the list.
|
||||
// This is done by editing the pointers of the node's neighbors to skip it.
|
||||
//
|
||||
// In other words:
|
||||
// node->prev->next = node->next
|
||||
// node->next->prev = node->prev
|
||||
//
|
||||
// We also want to properly update our list pointer,
|
||||
// which always points to the "first" element in the list. (Most recently added.)
|
||||
|
||||
if (node->prev != NULL)
|
||||
node->prev->next = node->next;
|
||||
else
|
||||
listHead = node->next;
|
||||
|
||||
if (node->next != NULL)
|
||||
node->next->prev = node->prev;
|
||||
else
|
||||
listTail = node->prev;
|
||||
|
||||
free(node);
|
||||
|
||||
if (!allInstances) break;
|
||||
}
|
||||
node = nextNode;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)remove:(void *)element
|
||||
{
|
||||
[self remove:element allInstances:NO];
|
||||
}
|
||||
|
||||
- (void)removeAll:(void *)element
|
||||
{
|
||||
[self remove:element allInstances:YES];
|
||||
}
|
||||
|
||||
- (void)removeAllElements
|
||||
{
|
||||
DDListNode *node = listHead;
|
||||
while (node != NULL)
|
||||
{
|
||||
DDListNode *next = node->next;
|
||||
|
||||
free(node);
|
||||
node = next;
|
||||
}
|
||||
|
||||
listHead = NULL;
|
||||
listTail = NULL;
|
||||
}
|
||||
|
||||
- (BOOL)contains:(void *)element
|
||||
{
|
||||
DDListNode *node;
|
||||
for (node = listHead; node != NULL; node = node->next)
|
||||
{
|
||||
if (node->element == element)
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)count
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
|
||||
DDListNode *node;
|
||||
for (node = listHead; node != NULL; node = node->next)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
|
||||
objects:(id __unsafe_unretained [])buffer
|
||||
count:(NSUInteger)len
|
||||
{
|
||||
DDListNode *currentNode;
|
||||
|
||||
if (state->extra[0] == 1)
|
||||
return 0;
|
||||
|
||||
if (state->state == 0)
|
||||
currentNode = listHead;
|
||||
else
|
||||
currentNode = (DDListNode *)state->state;
|
||||
|
||||
NSUInteger batchCount = 0;
|
||||
while (currentNode != NULL && batchCount < len)
|
||||
{
|
||||
buffer[batchCount] = (__bridge id)currentNode->element;
|
||||
currentNode = currentNode->next;
|
||||
batchCount++;
|
||||
}
|
||||
|
||||
state->state = (unsigned long)currentNode;
|
||||
state->itemsPtr = buffer;
|
||||
state->mutationsPtr = (__bridge void *)self;
|
||||
|
||||
if (currentNode == NULL)
|
||||
state->extra[0] = 1;
|
||||
else
|
||||
state->extra[0] = 0;
|
||||
|
||||
return batchCount;
|
||||
}
|
||||
|
||||
- (DDListEnumerator *)listEnumerator
|
||||
{
|
||||
return [[DDListEnumerator alloc] initWithList:listHead reverse:NO];
|
||||
}
|
||||
|
||||
- (DDListEnumerator *)reverseListEnumerator
|
||||
{
|
||||
return [[DDListEnumerator alloc] initWithList:listTail reverse:YES];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self removeAllElements];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation DDListEnumerator
|
||||
|
||||
- (id)initWithList:(DDListNode *)list reverse:(BOOL)reverse
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
numElements = 0;
|
||||
currentElementIndex = 0;
|
||||
|
||||
// First get a count of the number of elements in the given list.
|
||||
|
||||
if (reverse)
|
||||
{
|
||||
for (DDListNode *node = list; node != NULL; node = node->prev)
|
||||
{
|
||||
numElements++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (DDListNode *node = list; node != NULL; node = node->next)
|
||||
{
|
||||
numElements++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Now copy the list into a C array.
|
||||
|
||||
if (numElements > 0)
|
||||
{
|
||||
elements = malloc(numElements * sizeof(void *));
|
||||
|
||||
DDListNode *node = list;
|
||||
|
||||
if (reverse)
|
||||
{
|
||||
for (NSUInteger i = 0; i < numElements; i++)
|
||||
{
|
||||
elements[i] = node->element;
|
||||
node = node->prev;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (NSUInteger i = 0; i < numElements; i++)
|
||||
{
|
||||
elements[i] = node->element;
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)numTotal
|
||||
{
|
||||
return numElements;
|
||||
}
|
||||
|
||||
- (NSUInteger)numLeft
|
||||
{
|
||||
if (currentElementIndex < numElements)
|
||||
return numElements - currentElementIndex;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void *)nextElement
|
||||
{
|
||||
if (currentElementIndex < numElements)
|
||||
{
|
||||
void *element = elements[currentElementIndex];
|
||||
currentElementIndex++;
|
||||
|
||||
return element;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (elements)
|
||||
{
|
||||
free(elements);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,56 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class GCDMulticastDelegateEnumerator;
|
||||
|
||||
/**
|
||||
* This class provides multicast delegate functionality. That is:
|
||||
* - it provides a means for managing a list of delegates
|
||||
* - any method invocations to an instance of this class are automatically forwarded to all delegates
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* // Make this method call on every added delegate (there may be several)
|
||||
* [multicastDelegate cog:self didFindThing:thing];
|
||||
*
|
||||
* This allows multiple delegates to be added to an xmpp stream or any xmpp module,
|
||||
* which in turn makes development easier as there can be proper separation of logically different code sections.
|
||||
*
|
||||
* In addition, this makes module development easier,
|
||||
* as multiple delegates can usually be handled in a manner similar to the traditional single delegate paradigm.
|
||||
*
|
||||
* This class also provides proper support for GCD queues.
|
||||
* So each delegate specifies which queue they would like their delegate invocations to be dispatched onto.
|
||||
*
|
||||
* All delegate dispatching is done asynchronously (which is a critically important architectural design).
|
||||
**/
|
||||
|
||||
@interface GCDMulticastDelegate : NSObject
|
||||
|
||||
- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
|
||||
- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
|
||||
- (void)removeDelegate:(id)delegate;
|
||||
|
||||
- (void)removeAllDelegates;
|
||||
|
||||
- (NSUInteger)count;
|
||||
- (NSUInteger)countOfClass:(Class)aClass;
|
||||
- (NSUInteger)countForSelector:(SEL)aSelector;
|
||||
|
||||
- (BOOL)hasDelegateThatRespondsToSelector:(SEL)aSelector;
|
||||
|
||||
- (GCDMulticastDelegateEnumerator *)delegateEnumerator;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface GCDMulticastDelegateEnumerator : NSObject
|
||||
|
||||
- (NSUInteger)count;
|
||||
- (NSUInteger)countOfClass:(Class)aClass;
|
||||
- (NSUInteger)countForSelector:(SEL)aSelector;
|
||||
|
||||
- (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr;
|
||||
- (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr ofClass:(Class)aClass;
|
||||
- (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr forSelector:(SEL)aSelector;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,654 @@
|
||||
#import "GCDMulticastDelegate.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
#import <AppKit/AppKit.h>
|
||||
#endif
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
/**
|
||||
* How does this class work?
|
||||
*
|
||||
* In theory, this class is very straight-forward.
|
||||
* It provides a way for multiple delegates to be called, each on its own delegate queue.
|
||||
*
|
||||
* In other words, any delegate method call to this class
|
||||
* will get forwarded (dispatch_async'd) to each added delegate.
|
||||
*
|
||||
* Important note concerning thread-safety:
|
||||
*
|
||||
* This class is designed to be used from within a single dispatch queue.
|
||||
* In other words, it is NOT thread-safe, and should only be used from within the external dedicated dispatch_queue.
|
||||
**/
|
||||
|
||||
@interface GCDMulticastDelegateNode : NSObject {
|
||||
@private
|
||||
|
||||
#if __has_feature(objc_arc_weak)
|
||||
__weak id delegate;
|
||||
#if !TARGET_OS_IPHONE
|
||||
__unsafe_unretained id unsafeDelegate; // Some classes don't support weak references yet (e.g. NSWindowController)
|
||||
#endif
|
||||
#else
|
||||
__unsafe_unretained id delegate;
|
||||
#endif
|
||||
|
||||
dispatch_queue_t delegateQueue;
|
||||
}
|
||||
|
||||
- (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
|
||||
|
||||
#if __has_feature(objc_arc_weak)
|
||||
@property (/* atomic */ readwrite, weak) id delegate;
|
||||
#if !TARGET_OS_IPHONE
|
||||
@property (/* atomic */ readwrite, unsafe_unretained) id unsafeDelegate;
|
||||
#endif
|
||||
#else
|
||||
@property (/* atomic */ readwrite, unsafe_unretained) id delegate;
|
||||
#endif
|
||||
|
||||
@property (nonatomic, readonly) dispatch_queue_t delegateQueue;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface GCDMulticastDelegate ()
|
||||
{
|
||||
NSMutableArray *delegateNodes;
|
||||
}
|
||||
|
||||
- (NSInvocation *)duplicateInvocation:(NSInvocation *)origInvocation;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface GCDMulticastDelegateEnumerator ()
|
||||
{
|
||||
NSUInteger numNodes;
|
||||
NSUInteger currentNodeIndex;
|
||||
NSArray *delegateNodes;
|
||||
}
|
||||
|
||||
- (id)initFromDelegateNodes:(NSMutableArray *)inDelegateNodes;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation GCDMulticastDelegate
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
delegateNodes = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
|
||||
{
|
||||
if (delegate == nil) return;
|
||||
if (delegateQueue == NULL) return;
|
||||
|
||||
GCDMulticastDelegateNode *node =
|
||||
[[GCDMulticastDelegateNode alloc] initWithDelegate:delegate delegateQueue:delegateQueue];
|
||||
|
||||
[delegateNodes addObject:node];
|
||||
}
|
||||
|
||||
- (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue
|
||||
{
|
||||
if (delegate == nil) return;
|
||||
|
||||
NSUInteger i;
|
||||
for (i = [delegateNodes count]; i > 0; i--)
|
||||
{
|
||||
GCDMulticastDelegateNode *node = delegateNodes[i - 1];
|
||||
|
||||
id nodeDelegate = node.delegate;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if (delegate == nodeDelegate)
|
||||
{
|
||||
if ((delegateQueue == NULL) || (delegateQueue == node.delegateQueue))
|
||||
{
|
||||
// Recall that this node may be retained by a GCDMulticastDelegateEnumerator.
|
||||
// The enumerator is a thread-safe snapshot of the delegate list at the moment it was created.
|
||||
// To properly remove this node from list, and from the list(s) of any enumerators,
|
||||
// we nullify the delegate via the atomic property.
|
||||
//
|
||||
// However, the delegateQueue is not modified.
|
||||
// The thread-safety is hinged on the atomic delegate property.
|
||||
// The delegateQueue is expected to properly exist until the node is deallocated.
|
||||
|
||||
node.delegate = nil;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
node.unsafeDelegate = nil;
|
||||
#endif
|
||||
|
||||
[delegateNodes removeObjectAtIndex:(i-1)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeDelegate:(id)delegate
|
||||
{
|
||||
[self removeDelegate:delegate delegateQueue:NULL];
|
||||
}
|
||||
|
||||
- (void)removeAllDelegates
|
||||
{
|
||||
for (GCDMulticastDelegateNode *node in delegateNodes)
|
||||
{
|
||||
node.delegate = nil;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
node.unsafeDelegate = nil;
|
||||
#endif
|
||||
}
|
||||
|
||||
[delegateNodes removeAllObjects];
|
||||
}
|
||||
|
||||
- (NSUInteger)count
|
||||
{
|
||||
return [delegateNodes count];
|
||||
}
|
||||
|
||||
- (NSUInteger)countOfClass:(Class)aClass
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
|
||||
for (GCDMulticastDelegateNode *node in delegateNodes)
|
||||
{
|
||||
id nodeDelegate = node.delegate;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if ([nodeDelegate isKindOfClass:aClass])
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)countForSelector:(SEL)aSelector
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
|
||||
for (GCDMulticastDelegateNode *node in delegateNodes)
|
||||
{
|
||||
id nodeDelegate = node.delegate;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if ([nodeDelegate respondsToSelector:aSelector])
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
- (BOOL)hasDelegateThatRespondsToSelector:(SEL)aSelector
|
||||
{
|
||||
for (GCDMulticastDelegateNode *node in delegateNodes)
|
||||
{
|
||||
id nodeDelegate = node.delegate;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if ([nodeDelegate respondsToSelector:aSelector])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (GCDMulticastDelegateEnumerator *)delegateEnumerator
|
||||
{
|
||||
return [[GCDMulticastDelegateEnumerator alloc] initFromDelegateNodes:delegateNodes];
|
||||
}
|
||||
|
||||
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
|
||||
{
|
||||
for (GCDMulticastDelegateNode *node in delegateNodes)
|
||||
{
|
||||
id nodeDelegate = node.delegate;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
NSMethodSignature *result = [nodeDelegate methodSignatureForSelector:aSelector];
|
||||
|
||||
if (result != nil)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// This causes a crash...
|
||||
// return [super methodSignatureForSelector:aSelector];
|
||||
|
||||
// This also causes a crash...
|
||||
// return nil;
|
||||
|
||||
return [[self class] instanceMethodSignatureForSelector:@selector(doNothing)];
|
||||
}
|
||||
|
||||
- (void)forwardInvocation:(NSInvocation *)origInvocation
|
||||
{
|
||||
SEL selector = [origInvocation selector];
|
||||
BOOL foundNilDelegate = NO;
|
||||
|
||||
for (GCDMulticastDelegateNode *node in delegateNodes)
|
||||
{
|
||||
id nodeDelegate = node.delegate;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if ([nodeDelegate respondsToSelector:selector])
|
||||
{
|
||||
// All delegates MUST be invoked ASYNCHRONOUSLY.
|
||||
|
||||
NSInvocation *dupInvocation = [self duplicateInvocation:origInvocation];
|
||||
|
||||
dispatch_async(node.delegateQueue, ^{ @autoreleasepool {
|
||||
|
||||
[dupInvocation invokeWithTarget:nodeDelegate];
|
||||
|
||||
}});
|
||||
}
|
||||
else if (nodeDelegate == nil)
|
||||
{
|
||||
foundNilDelegate = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundNilDelegate)
|
||||
{
|
||||
// At lease one weak delegate reference disappeared.
|
||||
// Remove nil delegate nodes from the list.
|
||||
//
|
||||
// This is expected to happen very infrequently.
|
||||
// This is why we handle it separately (as it requires allocating an indexSet).
|
||||
|
||||
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
|
||||
|
||||
NSUInteger i = 0;
|
||||
for (GCDMulticastDelegateNode *node in delegateNodes)
|
||||
{
|
||||
id nodeDelegate = node.delegate;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if (nodeDelegate == nil)
|
||||
{
|
||||
[indexSet addIndex:i];
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
[delegateNodes removeObjectsAtIndexes:indexSet];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)doesNotRecognizeSelector:(SEL)aSelector
|
||||
{
|
||||
// Prevent NSInvalidArgumentException
|
||||
}
|
||||
|
||||
- (void)doNothing {}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self removeAllDelegates];
|
||||
}
|
||||
|
||||
- (NSInvocation *)duplicateInvocation:(NSInvocation *)origInvocation
|
||||
{
|
||||
NSMethodSignature *methodSignature = [origInvocation methodSignature];
|
||||
|
||||
NSInvocation *dupInvocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
||||
[dupInvocation setSelector:[origInvocation selector]];
|
||||
|
||||
NSUInteger i, count = [methodSignature numberOfArguments];
|
||||
for (i = 2; i < count; i++)
|
||||
{
|
||||
const char *type = [methodSignature getArgumentTypeAtIndex:i];
|
||||
|
||||
if (*type == *@encode(BOOL))
|
||||
{
|
||||
BOOL value;
|
||||
[origInvocation getArgument:&value atIndex:i];
|
||||
[dupInvocation setArgument:&value atIndex:i];
|
||||
}
|
||||
else if (*type == *@encode(char) || *type == *@encode(unsigned char))
|
||||
{
|
||||
char value;
|
||||
[origInvocation getArgument:&value atIndex:i];
|
||||
[dupInvocation setArgument:&value atIndex:i];
|
||||
}
|
||||
else if (*type == *@encode(short) || *type == *@encode(unsigned short))
|
||||
{
|
||||
short value;
|
||||
[origInvocation getArgument:&value atIndex:i];
|
||||
[dupInvocation setArgument:&value atIndex:i];
|
||||
}
|
||||
else if (*type == *@encode(int) || *type == *@encode(unsigned int))
|
||||
{
|
||||
int value;
|
||||
[origInvocation getArgument:&value atIndex:i];
|
||||
[dupInvocation setArgument:&value atIndex:i];
|
||||
}
|
||||
else if (*type == *@encode(long) || *type == *@encode(unsigned long))
|
||||
{
|
||||
long value;
|
||||
[origInvocation getArgument:&value atIndex:i];
|
||||
[dupInvocation setArgument:&value atIndex:i];
|
||||
}
|
||||
else if (*type == *@encode(long long) || *type == *@encode(unsigned long long))
|
||||
{
|
||||
long long value;
|
||||
[origInvocation getArgument:&value atIndex:i];
|
||||
[dupInvocation setArgument:&value atIndex:i];
|
||||
}
|
||||
else if (*type == *@encode(double))
|
||||
{
|
||||
double value;
|
||||
[origInvocation getArgument:&value atIndex:i];
|
||||
[dupInvocation setArgument:&value atIndex:i];
|
||||
}
|
||||
else if (*type == *@encode(float))
|
||||
{
|
||||
float value;
|
||||
[origInvocation getArgument:&value atIndex:i];
|
||||
[dupInvocation setArgument:&value atIndex:i];
|
||||
}
|
||||
else if (*type == '@')
|
||||
{
|
||||
void *value;
|
||||
[origInvocation getArgument:&value atIndex:i];
|
||||
[dupInvocation setArgument:&value atIndex:i];
|
||||
}
|
||||
else if (*type == '^')
|
||||
{
|
||||
void *block;
|
||||
[origInvocation getArgument:&block atIndex:i];
|
||||
[dupInvocation setArgument:&block atIndex:i];
|
||||
}
|
||||
else
|
||||
{
|
||||
NSString *selectorStr = NSStringFromSelector([origInvocation selector]);
|
||||
|
||||
NSString *format = @"Argument %lu to method %@ - Type(%c) not supported";
|
||||
NSString *reason = [NSString stringWithFormat:format, (unsigned long)(i - 2), selectorStr, *type];
|
||||
|
||||
[[NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil] raise];
|
||||
}
|
||||
}
|
||||
|
||||
[dupInvocation retainArguments];
|
||||
|
||||
return dupInvocation;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation GCDMulticastDelegateNode
|
||||
|
||||
@synthesize delegate; // atomic
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
@synthesize unsafeDelegate; // atomic
|
||||
#endif
|
||||
@synthesize delegateQueue; // non-atomic
|
||||
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
static BOOL SupportsWeakReferences(id delegate)
|
||||
{
|
||||
// From Apple's documentation:
|
||||
//
|
||||
// > Which classes don’t support weak references?
|
||||
// >
|
||||
// > You cannot currently create weak references to instances of the following classes:
|
||||
// >
|
||||
// > NSATSTypesetter, NSColorSpace, NSFont, NSFontManager, NSFontPanel, NSImage, NSMenuView,
|
||||
// > NSParagraphStyle, NSSimpleHorizontalTypesetter, NSTableCellView, NSTextView, NSViewController,
|
||||
// > NSWindow, and NSWindowController.
|
||||
// >
|
||||
// > In addition, in OS X no classes in the AV Foundation framework support weak references.
|
||||
//
|
||||
// NSMenuView is deprecated (and not available to 64-bit applications).
|
||||
// NSSimpleHorizontalTypesetter is an internal class.
|
||||
|
||||
if ([delegate isKindOfClass:[NSATSTypesetter class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSColorSpace class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSFont class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSFontManager class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSFontPanel class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSImage class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSParagraphStyle class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSTableCellView class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSTextView class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSViewController class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSWindow class]]) return NO;
|
||||
if ([delegate isKindOfClass:[NSWindowController class]]) return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (id)initWithDelegate:(id)inDelegate delegateQueue:(dispatch_queue_t)inDelegateQueue
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
{
|
||||
if (SupportsWeakReferences(inDelegate))
|
||||
{
|
||||
delegate = inDelegate;
|
||||
delegateQueue = inDelegateQueue;
|
||||
}
|
||||
else
|
||||
{
|
||||
delegate = [NSNull null];
|
||||
|
||||
unsafeDelegate = inDelegate;
|
||||
delegateQueue = inDelegateQueue;
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
delegate = inDelegate;
|
||||
delegateQueue = inDelegateQueue;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (delegateQueue)
|
||||
dispatch_retain(delegateQueue);
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (delegateQueue)
|
||||
dispatch_release(delegateQueue);
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation GCDMulticastDelegateEnumerator
|
||||
|
||||
- (id)initFromDelegateNodes:(NSMutableArray *)inDelegateNodes
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
delegateNodes = [inDelegateNodes copy];
|
||||
|
||||
numNodes = [delegateNodes count];
|
||||
currentNodeIndex = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSUInteger)count
|
||||
{
|
||||
return numNodes;
|
||||
}
|
||||
|
||||
- (NSUInteger)countOfClass:(Class)aClass
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
|
||||
for (GCDMulticastDelegateNode *node in delegateNodes)
|
||||
{
|
||||
id nodeDelegate = node.delegate;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if ([nodeDelegate isKindOfClass:aClass])
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
- (NSUInteger)countForSelector:(SEL)aSelector
|
||||
{
|
||||
NSUInteger count = 0;
|
||||
|
||||
for (GCDMulticastDelegateNode *node in delegateNodes)
|
||||
{
|
||||
id nodeDelegate = node.delegate;
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if ([nodeDelegate respondsToSelector:aSelector])
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
- (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr
|
||||
{
|
||||
while (currentNodeIndex < numNodes)
|
||||
{
|
||||
GCDMulticastDelegateNode *node = delegateNodes[currentNodeIndex];
|
||||
currentNodeIndex++;
|
||||
|
||||
id nodeDelegate = node.delegate; // snapshot atomic property
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if (nodeDelegate)
|
||||
{
|
||||
if (delPtr) *delPtr = nodeDelegate;
|
||||
if (dqPtr) *dqPtr = node.delegateQueue;
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr ofClass:(Class)aClass
|
||||
{
|
||||
while (currentNodeIndex < numNodes)
|
||||
{
|
||||
GCDMulticastDelegateNode *node = delegateNodes[currentNodeIndex];
|
||||
currentNodeIndex++;
|
||||
|
||||
id nodeDelegate = node.delegate; // snapshot atomic property
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if ([nodeDelegate isKindOfClass:aClass])
|
||||
{
|
||||
if (delPtr) *delPtr = nodeDelegate;
|
||||
if (dqPtr) *dqPtr = node.delegateQueue;
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr forSelector:(SEL)aSelector
|
||||
{
|
||||
while (currentNodeIndex < numNodes)
|
||||
{
|
||||
GCDMulticastDelegateNode *node = delegateNodes[currentNodeIndex];
|
||||
currentNodeIndex++;
|
||||
|
||||
id nodeDelegate = node.delegate; // snapshot atomic property
|
||||
#if __has_feature(objc_arc_weak) && !TARGET_OS_IPHONE
|
||||
if (nodeDelegate == [NSNull null])
|
||||
nodeDelegate = node.unsafeDelegate;
|
||||
#endif
|
||||
|
||||
if ([nodeDelegate respondsToSelector:aSelector])
|
||||
{
|
||||
if (delPtr) *delPtr = nodeDelegate;
|
||||
if (dqPtr) *dqPtr = node.delegateQueue;
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
/*
|
||||
File: RFImageToDataTransformer.h
|
||||
Abstract: A value transformer, which transforms a UIImage or NSImage object into an NSData object.
|
||||
|
||||
Based on Apple's UIImageToDataTransformer
|
||||
|
||||
Copyright (C) 2010 Apple Inc. All Rights Reserved.
|
||||
Copyright (C) 2011 RF.com All Rights Reserved.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface RFImageToDataTransformer : NSValueTransformer {
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
/*
|
||||
File: RFImageToDataTransformer.m
|
||||
Abstract: A value transformer, which transforms a UIImage or NSImage object into an NSData object.
|
||||
|
||||
Based on Apple's UIImageToDataTransformer
|
||||
|
||||
Copyright (C) 2010 Apple Inc. All Rights Reserved.
|
||||
Copyright (C) 2011 RF.com All Rights Reserved.
|
||||
*/
|
||||
|
||||
#import "RFImageToDataTransformer.h"
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#import <UIKit/UIKit.h>
|
||||
#else
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@implementation RFImageToDataTransformer
|
||||
|
||||
+ (BOOL)allowsReverseTransformation {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (Class)transformedValueClass {
|
||||
return [NSData class];
|
||||
}
|
||||
|
||||
- (id)transformedValue:(id)value {
|
||||
#if TARGET_OS_IPHONE
|
||||
return UIImagePNGRepresentation(value);
|
||||
#else
|
||||
return [(NSImage *)value TIFFRepresentation];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (id)reverseTransformedValue:(id)value {
|
||||
#if TARGET_OS_IPHONE
|
||||
return [[UIImage alloc] initWithData:value];
|
||||
#else
|
||||
return [[NSImage alloc] initWithData:value];
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol XMPPTrackingInfo;
|
||||
|
||||
@class XMPPElement;
|
||||
|
||||
@class XMPPStream;
|
||||
|
||||
extern const NSTimeInterval XMPPIDTrackerTimeoutNone;
|
||||
|
||||
/**
|
||||
* A common operation in XMPP is to send some kind of request with a unique id,
|
||||
* and wait for the response to come back.
|
||||
* The most common example is sending an IQ of type='get' with a unique id, and then awaiting the response.
|
||||
*
|
||||
* In order to properly handle the response, the id must be stored.
|
||||
* If there are multiple queries going out and/or different kinds of queries,
|
||||
* then information about the appropriate handling of the response must also be stored.
|
||||
* This may be accomplished by storing the appropriate selector, or perhaps a block handler.
|
||||
* Additionally one may need to setup timeouts and handle those properly as well.
|
||||
*
|
||||
* This class provides the scaffolding to simplify the tasks associated with this common operation.
|
||||
* Essentially, it provides the following:
|
||||
* - a dictionary where the unique id is the key, and the needed tracking info is the object
|
||||
* - an optional timer to fire upon a timeout
|
||||
*
|
||||
* The class is designed to be flexible.
|
||||
* You can provide a target/selector or a block handler to be invoked.
|
||||
* Additionally, you can use the basic tracking info, or you can extend it to suit your needs.
|
||||
*
|
||||
* It is best illustrated with a few examples.
|
||||
*
|
||||
* ---- EXAMPLE 1 - SIMPLE TRACKING WITH TARGET / SELECTOR ----
|
||||
*
|
||||
* XMPPIQ *iq = ...
|
||||
* [iqTracker addID:[iq elementID] target:self selector:@selector(processBookQuery:withInfo:) timeout:15.0];
|
||||
*
|
||||
* - (void)processBookQueury:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo)info {
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* - (BOOL)xmppStream:(XMPPStream *)stream didReceiveIQ:(XMPPIQ *)iq
|
||||
* {
|
||||
* NSString *type = [iq type];
|
||||
*
|
||||
* if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"])
|
||||
* {
|
||||
* return [iqTracker invokeForID:[iq elementID] withObject:iq];
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ---- EXAMPLE 2 - SIMPLE TRACKING WITH BLOCK HANDLER ----
|
||||
*
|
||||
* XMPPIQ *iq = ...
|
||||
*
|
||||
* void (^blockHandler)(XMPPIQ *, id <XMPPTrackingInfo>) = ^(XMPPIQ *iq, id <XMPPTrackingInfo> info) {
|
||||
* ...
|
||||
* };
|
||||
* [iqTracker addID:[iq elementID] block:blockHandler timeout:15.0];
|
||||
*
|
||||
* // Same xmppStream:didReceiveIQ: as example 1
|
||||
*
|
||||
* ---- EXAMPLE 3 - ADVANCED TRACKING ----
|
||||
*
|
||||
* @interface PingTrackingInfo : XMPPBasicTrackingInfo
|
||||
* ...
|
||||
* @end
|
||||
*
|
||||
* XMPPIQ *ping = ...
|
||||
* PingTrackingInfo *pingInfo = ...
|
||||
*
|
||||
* [iqTracker addID:[ping elementID] trackingInfo:pingInfo];
|
||||
*
|
||||
* - (void)handlePong:(XMPPIQ *)iq withInfo:(PingTrackingInfo *)info {
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* // Same xmppStream:didReceiveIQ: as example 1
|
||||
*
|
||||
*
|
||||
* ---- Validating Responses ----
|
||||
*
|
||||
* XMPPIDTracker can also be used to validate that the response was from the expected jid.
|
||||
* To do this you need to initalize XMPPIDTracker with the stream where the request/response is going to be tracked.
|
||||
*
|
||||
* xmppIDTracker = [[XMPPIDTracker alloc] initWithStream:stream dispatchQueue:queue];
|
||||
*
|
||||
* You also need to supply the element (not just the ID) to the add an invoke methods.
|
||||
*
|
||||
* ---- EXAMPLE 1 - SIMPLE TRACKING WITH TARGET / SELECTOR AND VALIDATION ----
|
||||
*
|
||||
* XMPPIQ *iq = ...
|
||||
* [iqTracker addElement:iq target:self selector:@selector(processBookQuery:withInfo:) timeout:15.0];
|
||||
*
|
||||
* - (void)processBookQueury:(XMPPIQ *)iq withInfo:(id <XMPPTrackingInfo)info {
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* - (BOOL)xmppStream:(XMPPStream *)stream didReceiveIQ:(XMPPIQ *)iq
|
||||
* {
|
||||
* NSString *type = [iq type];
|
||||
*
|
||||
* if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"])
|
||||
* {
|
||||
* return [iqTracker invokeForElement:iq withObject:iq];
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* This class is NOT thread-safe.
|
||||
* It is designed to be used within a thread-safe context (e.g. within a single dispatch_queue).
|
||||
**/
|
||||
@interface XMPPIDTracker : NSObject
|
||||
{
|
||||
XMPPStream *xmppStream;
|
||||
dispatch_queue_t queue;
|
||||
|
||||
NSMutableDictionary *dict;
|
||||
}
|
||||
|
||||
- (id)initWithDispatchQueue:(dispatch_queue_t)queue;
|
||||
|
||||
- (id)initWithStream:(XMPPStream *)stream dispatchQueue:(dispatch_queue_t)queue;
|
||||
|
||||
- (void)addID:(NSString *)elementID target:(id)target selector:(SEL)selector timeout:(NSTimeInterval)timeout;
|
||||
|
||||
- (void)addElement:(XMPPElement *)element target:(id)target selector:(SEL)selector timeout:(NSTimeInterval)timeout;
|
||||
|
||||
- (void)addID:(NSString *)elementID
|
||||
block:(void (^)(id obj, id <XMPPTrackingInfo> info))block
|
||||
timeout:(NSTimeInterval)timeout;
|
||||
|
||||
- (void)addElement:(XMPPElement *)element
|
||||
block:(void (^)(id obj, id <XMPPTrackingInfo> info))block
|
||||
timeout:(NSTimeInterval)timeout;
|
||||
|
||||
- (void)addID:(NSString *)elementID trackingInfo:(id <XMPPTrackingInfo>)trackingInfo;
|
||||
|
||||
- (void)addElement:(XMPPElement *)element trackingInfo:(id <XMPPTrackingInfo>)trackingInfo;
|
||||
|
||||
- (BOOL)invokeForID:(NSString *)elementID withObject:(id)obj;
|
||||
|
||||
- (BOOL)invokeForElement:(XMPPElement *)element withObject:(id)obj;
|
||||
|
||||
- (NSUInteger)numberOfIDs;
|
||||
|
||||
- (void)removeID:(NSString *)elementID;
|
||||
- (void)removeAllIDs;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPTrackingInfo <NSObject>
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval timeout;
|
||||
|
||||
@property (nonatomic, readwrite, copy) NSString *elementID;
|
||||
|
||||
@property (nonatomic, readwrite, copy) XMPPElement *element;
|
||||
|
||||
- (void)createTimerWithDispatchQueue:(dispatch_queue_t)queue;
|
||||
- (void)cancelTimer;
|
||||
|
||||
- (void)invokeWithObject:(id)obj;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface XMPPBasicTrackingInfo : NSObject <XMPPTrackingInfo>
|
||||
{
|
||||
id target;
|
||||
SEL selector;
|
||||
|
||||
void (^block)(id obj, id <XMPPTrackingInfo> info);
|
||||
|
||||
NSTimeInterval timeout;
|
||||
|
||||
NSString *elementID;
|
||||
XMPPElement *element;
|
||||
dispatch_source_t timer;
|
||||
}
|
||||
|
||||
- (id)initWithTarget:(id)target selector:(SEL)selector timeout:(NSTimeInterval)timeout;
|
||||
- (id)initWithBlock:(void (^)(id obj, id <XMPPTrackingInfo> info))block timeout:(NSTimeInterval)timeout;
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval timeout;
|
||||
|
||||
@property (nonatomic, readwrite, copy) NSString *elementID;
|
||||
|
||||
@property (nonatomic, readwrite, copy) XMPPElement *element;
|
||||
|
||||
- (void)createTimerWithDispatchQueue:(dispatch_queue_t)queue;
|
||||
- (void)cancelTimer;
|
||||
|
||||
- (void)invokeWithObject:(id)obj;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,347 @@
|
||||
#import "XMPPIDTracker.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_VERBOSE; // | XMPP_LOG_FLAG_TRACE;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
#define AssertProperQueue() NSAssert(dispatch_get_specific(queueTag), @"Invoked on incorrect queue")
|
||||
|
||||
const NSTimeInterval XMPPIDTrackerTimeoutNone = -1;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface XMPPIDTracker ()
|
||||
{
|
||||
void *queueTag;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation XMPPIDTracker
|
||||
|
||||
- (id)init
|
||||
{
|
||||
// You must use initWithDispatchQueue or initWithStream:dispatchQueue:
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)initWithDispatchQueue:(dispatch_queue_t)aQueue
|
||||
{
|
||||
return [self initWithStream:nil dispatchQueue:aQueue];
|
||||
}
|
||||
|
||||
- (id)initWithStream:(XMPPStream *)stream dispatchQueue:(dispatch_queue_t)aQueue
|
||||
{
|
||||
NSParameterAssert(aQueue != NULL);
|
||||
|
||||
if ((self = [super init]))
|
||||
{
|
||||
xmppStream = stream;
|
||||
|
||||
queue = aQueue;
|
||||
|
||||
queueTag = &queueTag;
|
||||
dispatch_queue_set_specific(queue, queueTag, queueTag, NULL);
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_retain(queue);
|
||||
#endif
|
||||
|
||||
dict = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// We don't call [self removeAllIDs] because dealloc might not be invoked on queue
|
||||
|
||||
for (id <XMPPTrackingInfo> info in [dict objectEnumerator])
|
||||
{
|
||||
[info cancelTimer];
|
||||
}
|
||||
[dict removeAllObjects];
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(queue);
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)addID:(NSString *)elementID target:(id)target selector:(SEL)selector timeout:(NSTimeInterval)timeout
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
XMPPBasicTrackingInfo *trackingInfo;
|
||||
trackingInfo = [[XMPPBasicTrackingInfo alloc] initWithTarget:target selector:selector timeout:timeout];
|
||||
|
||||
[self addID:elementID trackingInfo:trackingInfo];
|
||||
}
|
||||
|
||||
- (void)addElement:(XMPPElement *)element target:(id)target selector:(SEL)selector timeout:(NSTimeInterval)timeout
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
XMPPBasicTrackingInfo *trackingInfo;
|
||||
trackingInfo = [[XMPPBasicTrackingInfo alloc] initWithTarget:target selector:selector timeout:timeout];
|
||||
|
||||
[self addElement:element trackingInfo:trackingInfo];
|
||||
}
|
||||
|
||||
- (void)addID:(NSString *)elementID
|
||||
block:(void (^)(id obj, id <XMPPTrackingInfo> info))block
|
||||
timeout:(NSTimeInterval)timeout
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
XMPPBasicTrackingInfo *trackingInfo;
|
||||
trackingInfo = [[XMPPBasicTrackingInfo alloc] initWithBlock:block timeout:timeout];
|
||||
|
||||
[self addID:elementID trackingInfo:trackingInfo];
|
||||
}
|
||||
|
||||
|
||||
- (void)addElement:(XMPPElement *)element
|
||||
block:(void (^)(id obj, id <XMPPTrackingInfo> info))block
|
||||
timeout:(NSTimeInterval)timeout
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
XMPPBasicTrackingInfo *trackingInfo;
|
||||
trackingInfo = [[XMPPBasicTrackingInfo alloc] initWithBlock:block timeout:timeout];
|
||||
|
||||
[self addElement:element trackingInfo:trackingInfo];
|
||||
}
|
||||
|
||||
- (void)addID:(NSString *)elementID trackingInfo:(id <XMPPTrackingInfo>)trackingInfo
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
dict[elementID] = trackingInfo;
|
||||
|
||||
[trackingInfo setElementID:elementID];
|
||||
[trackingInfo createTimerWithDispatchQueue:queue];
|
||||
}
|
||||
|
||||
- (void)addElement:(XMPPElement *)element trackingInfo:(id <XMPPTrackingInfo>)trackingInfo
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
if([[element elementID] length] == 0) return;
|
||||
|
||||
dict[[element elementID]] = trackingInfo;
|
||||
|
||||
[trackingInfo setElementID:[element elementID]];
|
||||
[trackingInfo setElement:element];
|
||||
[trackingInfo createTimerWithDispatchQueue:queue];
|
||||
}
|
||||
|
||||
- (BOOL)invokeForID:(NSString *)elementID withObject:(id)obj
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
if([elementID length] == 0) return NO;
|
||||
|
||||
id <XMPPTrackingInfo> info = dict[elementID];
|
||||
|
||||
if (info)
|
||||
{
|
||||
[info invokeWithObject:obj];
|
||||
[info cancelTimer];
|
||||
[dict removeObjectForKey:elementID];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)invokeForElement:(XMPPElement *)element withObject:(id)obj
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
NSString *elementID = [element elementID];
|
||||
|
||||
if ([elementID length] == 0) return NO;
|
||||
|
||||
id <XMPPTrackingInfo> info = dict[elementID];
|
||||
if(info)
|
||||
{
|
||||
BOOL valid = YES;
|
||||
|
||||
if(xmppStream && [element isKindOfClass:[XMPPIQ class]] && [[info element] isKindOfClass:[XMPPIQ class]])
|
||||
{
|
||||
XMPPIQ *iq = (XMPPIQ *)element;
|
||||
|
||||
if([iq isResultIQ] || [iq isErrorIQ])
|
||||
{
|
||||
valid = [xmppStream isValidResponseElement:iq forRequestElement:[info element]];
|
||||
}
|
||||
}
|
||||
|
||||
if(!valid)
|
||||
{
|
||||
XMPPLogError(@"%s: Element with ID %@ cannot be validated.", __FILE__ , [element elementID]);
|
||||
}
|
||||
|
||||
if (valid)
|
||||
{
|
||||
[info invokeWithObject:obj];
|
||||
[info cancelTimer];
|
||||
[dict removeObjectForKey:[element elementID]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSUInteger)numberOfIDs
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
return [[dict allKeys] count];
|
||||
}
|
||||
|
||||
- (void)removeID:(NSString *)elementID
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
id <XMPPTrackingInfo> info = dict[elementID];
|
||||
if (info)
|
||||
{
|
||||
[info cancelTimer];
|
||||
[dict removeObjectForKey:elementID];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeAllIDs
|
||||
{
|
||||
AssertProperQueue();
|
||||
|
||||
for (id <XMPPTrackingInfo> info in [dict objectEnumerator])
|
||||
{
|
||||
[info cancelTimer];
|
||||
}
|
||||
[dict removeAllObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPBasicTrackingInfo
|
||||
|
||||
@synthesize timeout;
|
||||
@synthesize elementID;
|
||||
@synthesize element;
|
||||
|
||||
- (id)init
|
||||
{
|
||||
// Use initWithTarget:selector:timeout: or initWithBlock:timeout:
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (id)initWithTarget:(id)aTarget selector:(SEL)aSelector timeout:(NSTimeInterval)aTimeout
|
||||
{
|
||||
if(target || selector)
|
||||
{
|
||||
NSParameterAssert(aTarget);
|
||||
NSParameterAssert(aSelector);
|
||||
}
|
||||
|
||||
if ((self = [super init]))
|
||||
{
|
||||
target = aTarget;
|
||||
selector = aSelector;
|
||||
timeout = aTimeout;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithBlock:(void (^)(id obj, id <XMPPTrackingInfo> info))aBlock timeout:(NSTimeInterval)aTimeout
|
||||
{
|
||||
NSParameterAssert(aBlock);
|
||||
|
||||
if ((self = [super init]))
|
||||
{
|
||||
block = [aBlock copy];
|
||||
timeout = aTimeout;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self cancelTimer];
|
||||
|
||||
target = nil;
|
||||
selector = NULL;
|
||||
}
|
||||
|
||||
- (void)createTimerWithDispatchQueue:(dispatch_queue_t)queue
|
||||
{
|
||||
NSAssert(queue != NULL, @"Method invoked with NULL queue");
|
||||
NSAssert(timer == NULL, @"Method invoked multiple times");
|
||||
|
||||
if (timeout > 0.0)
|
||||
{
|
||||
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
||||
|
||||
dispatch_source_set_event_handler(timer, ^{ @autoreleasepool {
|
||||
|
||||
[self invokeWithObject:nil];
|
||||
[self cancelTimer];
|
||||
|
||||
}});
|
||||
|
||||
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
|
||||
|
||||
dispatch_source_set_timer(timer, tt, DISPATCH_TIME_FOREVER, 0);
|
||||
dispatch_resume(timer);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelTimer
|
||||
{
|
||||
if (timer)
|
||||
{
|
||||
dispatch_source_cancel(timer);
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(timer);
|
||||
#endif
|
||||
timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)invokeWithObject:(id)obj
|
||||
{
|
||||
if (block)
|
||||
{
|
||||
block(obj, self);
|
||||
}
|
||||
else if(target && selector)
|
||||
{
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
[target performSelector:selector withObject:obj withObject:self];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// XMPPSRVResolver.h
|
||||
//
|
||||
// Originally created by Eric Chamberlain on 6/15/10.
|
||||
// Based on SRVResolver by Apple, Inc.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <dns_sd.h>
|
||||
|
||||
extern NSString *const XMPPSRVResolverErrorDomain;
|
||||
|
||||
|
||||
@interface XMPPSRVResolver : NSObject
|
||||
{
|
||||
__unsafe_unretained id delegate;
|
||||
dispatch_queue_t delegateQueue;
|
||||
|
||||
dispatch_queue_t resolverQueue;
|
||||
void *resolverQueueTag;
|
||||
|
||||
__strong NSString *srvName;
|
||||
NSTimeInterval timeout;
|
||||
|
||||
BOOL resolveInProgress;
|
||||
|
||||
NSMutableArray *results;
|
||||
DNSServiceRef sdRef;
|
||||
|
||||
int sdFd;
|
||||
dispatch_source_t sdReadSource;
|
||||
dispatch_source_t timeoutTimer;
|
||||
}
|
||||
|
||||
/**
|
||||
* The delegate & delegateQueue are mandatory.
|
||||
* The resolverQueue is optional. If NULL, it will automatically create it's own internal queue.
|
||||
**/
|
||||
- (id)initWithdDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq resolverQueue:(dispatch_queue_t)rq;
|
||||
|
||||
@property (strong, readonly) NSString *srvName;
|
||||
@property (readonly) NSTimeInterval timeout;
|
||||
|
||||
- (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout;
|
||||
- (void)stop;
|
||||
|
||||
+ (NSString *)srvNameFromXMPPDomain:(NSString *)xmppDomain;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol XMPPSRVResolverDelegate
|
||||
|
||||
- (void)xmppSRVResolver:(XMPPSRVResolver *)sender didResolveRecords:(NSArray *)records;
|
||||
- (void)xmppSRVResolver:(XMPPSRVResolver *)sender didNotResolveDueToError:(NSError *)error;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface XMPPSRVRecord : NSObject
|
||||
{
|
||||
UInt16 priority;
|
||||
UInt16 weight;
|
||||
UInt16 port;
|
||||
NSString *target;
|
||||
|
||||
NSUInteger sum;
|
||||
NSUInteger srvResultsIndex;
|
||||
}
|
||||
|
||||
+ (XMPPSRVRecord *)recordWithPriority:(UInt16)priority weight:(UInt16)weight port:(UInt16)port target:(NSString *)target;
|
||||
|
||||
- (id)initWithPriority:(UInt16)priority weight:(UInt16)weight port:(UInt16)port target:(NSString *)target;
|
||||
|
||||
@property (nonatomic, readonly) UInt16 priority;
|
||||
@property (nonatomic, readonly) UInt16 weight;
|
||||
@property (nonatomic, readonly) UInt16 port;
|
||||
@property (nonatomic, readonly) NSString *target;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,687 @@
|
||||
//
|
||||
// XMPPSRVResolver.m
|
||||
//
|
||||
// Originally created by Eric Chamberlain on 6/15/10.
|
||||
// Based on SRVResolver by Apple, Inc.
|
||||
//
|
||||
|
||||
#import "XMPPSRVResolver.h"
|
||||
#import "XMPPLogging.h"
|
||||
|
||||
//#warning Fix "dns.h" issue without resorting to this ugly hack.
|
||||
// This is a hack to prevent OnionKit's clobbering of the actual system's <dns.h>
|
||||
//#include "/usr/include/dns.h"
|
||||
|
||||
#include <dns_util.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
NSString *const XMPPSRVResolverErrorDomain = @"XMPPSRVResolverErrorDomain";
|
||||
|
||||
// Log levels: off, error, warn, info, verbose
|
||||
#if DEBUG
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface XMPPSRVRecord ()
|
||||
|
||||
@property(nonatomic, assign) NSUInteger srvResultsIndex;
|
||||
@property(nonatomic, assign) NSUInteger sum;
|
||||
|
||||
- (NSComparisonResult)compareByPriority:(XMPPSRVRecord *)aRecord;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPSRVResolver
|
||||
|
||||
- (id)initWithdDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq resolverQueue:(dispatch_queue_t)rq
|
||||
{
|
||||
NSParameterAssert(aDelegate != nil);
|
||||
NSParameterAssert(dq != NULL);
|
||||
|
||||
if ((self = [super init]))
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
delegate = aDelegate;
|
||||
delegateQueue = dq;
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_retain(delegateQueue);
|
||||
#endif
|
||||
|
||||
if (rq)
|
||||
{
|
||||
resolverQueue = rq;
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_retain(resolverQueue);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
resolverQueue = dispatch_queue_create("XMPPSRVResolver", NULL);
|
||||
}
|
||||
|
||||
resolverQueueTag = &resolverQueueTag;
|
||||
dispatch_queue_set_specific(resolverQueue, resolverQueueTag, resolverQueueTag, NULL);
|
||||
|
||||
results = [[NSMutableArray alloc] initWithCapacity:2];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
[self stop];
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (resolverQueue)
|
||||
dispatch_release(resolverQueue);
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Properties
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@dynamic srvName;
|
||||
@dynamic timeout;
|
||||
|
||||
- (NSString *)srvName
|
||||
{
|
||||
__block NSString *result = nil;
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
result = [srvName copy];
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(resolverQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(resolverQueue, block);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSTimeInterval)timeout
|
||||
{
|
||||
__block NSTimeInterval result = 0.0;
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
result = timeout;
|
||||
};
|
||||
|
||||
if (dispatch_get_specific(resolverQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(resolverQueue, block);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Private Methods
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)sortResults
|
||||
{
|
||||
NSAssert(dispatch_get_specific(resolverQueueTag), @"Invoked on incorrect queue");
|
||||
|
||||
XMPPLogTrace();
|
||||
|
||||
// Sort results
|
||||
NSMutableArray *sortedResults = [NSMutableArray arrayWithCapacity:[results count]];
|
||||
|
||||
// Sort the list by priority (lowest number first)
|
||||
[results sortUsingSelector:@selector(compareByPriority:)];
|
||||
|
||||
/* From RFC 2782
|
||||
*
|
||||
* For each distinct priority level
|
||||
* While there are still elements left at this priority level
|
||||
*
|
||||
* Select an element as specified above, in the
|
||||
* description of Weight in "The format of the SRV
|
||||
* RR" Section, and move it to the tail of the new
|
||||
* list.
|
||||
*
|
||||
* The following algorithm SHOULD be used to order
|
||||
* the SRV RRs of the same priority:
|
||||
*/
|
||||
|
||||
NSUInteger srvResultsCount;
|
||||
|
||||
while ([results count] > 0)
|
||||
{
|
||||
srvResultsCount = [results count];
|
||||
|
||||
if (srvResultsCount == 1)
|
||||
{
|
||||
XMPPSRVRecord *srvRecord = results[0];
|
||||
|
||||
[sortedResults addObject:srvRecord];
|
||||
[results removeObjectAtIndex:0];
|
||||
}
|
||||
else // (srvResultsCount > 1)
|
||||
{
|
||||
// more than two records so we need to sort
|
||||
|
||||
/* To select a target to be contacted next, arrange all SRV RRs
|
||||
* (that have not been ordered yet) in any order, except that all
|
||||
* those with weight 0 are placed at the beginning of the list.
|
||||
*
|
||||
* Compute the sum of the weights of those RRs, and with each RR
|
||||
* associate the running sum in the selected order.
|
||||
*/
|
||||
|
||||
NSUInteger runningSum = 0;
|
||||
NSMutableArray *samePriorityRecords = [NSMutableArray arrayWithCapacity:srvResultsCount];
|
||||
|
||||
XMPPSRVRecord *srvRecord = results[0];
|
||||
|
||||
NSUInteger initialPriority = srvRecord.priority;
|
||||
NSUInteger index = 0;
|
||||
|
||||
do
|
||||
{
|
||||
if (srvRecord.weight == 0)
|
||||
{
|
||||
// add to front of array
|
||||
[samePriorityRecords insertObject:srvRecord atIndex:0];
|
||||
|
||||
srvRecord.srvResultsIndex = index;
|
||||
srvRecord.sum = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add to end of array and update the running sum
|
||||
[samePriorityRecords addObject:srvRecord];
|
||||
|
||||
runningSum += srvRecord.weight;
|
||||
|
||||
srvRecord.srvResultsIndex = index;
|
||||
srvRecord.sum = runningSum;
|
||||
}
|
||||
|
||||
if (++index < srvResultsCount)
|
||||
{
|
||||
srvRecord = results[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
srvRecord = nil;
|
||||
}
|
||||
|
||||
} while(srvRecord && (srvRecord.priority == initialPriority));
|
||||
|
||||
/* Then choose a uniform random number between 0 and the sum computed
|
||||
* (inclusive), and select the RR whose running sum value is the
|
||||
* first in the selected order which is greater than or equal to
|
||||
* the random number selected.
|
||||
*/
|
||||
|
||||
NSUInteger randomIndex = arc4random() % (runningSum + 1);
|
||||
|
||||
for (srvRecord in samePriorityRecords)
|
||||
{
|
||||
if (srvRecord.sum >= randomIndex)
|
||||
{
|
||||
/* The target host specified in the
|
||||
* selected SRV RR is the next one to be contacted by the client.
|
||||
* Remove this SRV RR from the set of the unordered SRV RRs and
|
||||
* apply the described algorithm to the unordered SRV RRs to select
|
||||
* the next target host. Continue the ordering process until there
|
||||
* are no unordered SRV RRs. This process is repeated for each
|
||||
* Priority.
|
||||
*/
|
||||
|
||||
[sortedResults addObject:srvRecord];
|
||||
[results removeObjectAtIndex:srvRecord.srvResultsIndex];
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = sortedResults;
|
||||
|
||||
XMPPLogVerbose(@"%@: Sorted results:\n%@", THIS_FILE, results);
|
||||
}
|
||||
|
||||
- (void)succeed
|
||||
{
|
||||
NSAssert(dispatch_get_specific(resolverQueueTag), @"Invoked on incorrect queue");
|
||||
|
||||
XMPPLogTrace();
|
||||
|
||||
[self sortResults];
|
||||
|
||||
id theDelegate = delegate;
|
||||
NSArray *records = [results copy];
|
||||
|
||||
dispatch_async(delegateQueue, ^{ @autoreleasepool {
|
||||
|
||||
SEL selector = @selector(xmppSRVResolver:didResolveRecords:);
|
||||
|
||||
if ([theDelegate respondsToSelector:selector])
|
||||
{
|
||||
[theDelegate xmppSRVResolver:self didResolveRecords:records];
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPLogWarn(@"%@: delegate doesn't implement %@", THIS_FILE, NSStringFromSelector(selector));
|
||||
}
|
||||
|
||||
}});
|
||||
|
||||
[self stop];
|
||||
}
|
||||
|
||||
- (void)failWithError:(NSError *)error
|
||||
{
|
||||
NSAssert(dispatch_get_specific(resolverQueueTag), @"Invoked on incorrect queue");
|
||||
|
||||
XMPPLogTrace2(@"%@: %@ %@", THIS_FILE, THIS_METHOD, error);
|
||||
|
||||
id theDelegate = delegate;
|
||||
|
||||
if (delegateQueue != NULL)
|
||||
{
|
||||
dispatch_async(delegateQueue, ^{ @autoreleasepool {
|
||||
|
||||
SEL selector = @selector(xmppSRVResolver:didNotResolveDueToError:);
|
||||
|
||||
if ([theDelegate respondsToSelector:selector])
|
||||
{
|
||||
[theDelegate xmppSRVResolver:self didNotResolveDueToError:error];
|
||||
}
|
||||
else
|
||||
{
|
||||
XMPPLogWarn(@"%@: delegate doesn't implement %@", THIS_FILE, NSStringFromSelector(selector));
|
||||
}
|
||||
|
||||
}});
|
||||
}
|
||||
|
||||
[self stop];
|
||||
}
|
||||
|
||||
- (void)failWithDNSError:(DNSServiceErrorType)sdErr
|
||||
{
|
||||
XMPPLogTrace2(@"%@: %@ %i", THIS_FILE, THIS_METHOD, (int)sdErr);
|
||||
|
||||
[self failWithError:[NSError errorWithDomain:XMPPSRVResolverErrorDomain code:sdErr userInfo:nil]];
|
||||
}
|
||||
|
||||
- (XMPPSRVRecord *)processRecord:(const void *)rdata length:(uint16_t)rdlen
|
||||
{
|
||||
XMPPLogTrace();
|
||||
|
||||
// Note: This method is almost entirely from Apple's sample code.
|
||||
//
|
||||
// Otherwise there would be a lot more comments and explanation...
|
||||
|
||||
if (rdata == NULL)
|
||||
{
|
||||
XMPPLogWarn(@"%@: %@ - rdata == NULL", THIS_FILE, THIS_METHOD);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Rather than write a whole bunch of icky parsing code, I just synthesise
|
||||
// a resource record and use <dns_util.h>.
|
||||
|
||||
XMPPSRVRecord *result = nil;
|
||||
|
||||
NSMutableData * rrData;
|
||||
dns_resource_record_t * rr;
|
||||
uint8_t u8; // 1 byte
|
||||
uint16_t u16; // 2 bytes
|
||||
uint32_t u32; // 4 bytes
|
||||
|
||||
rrData = [NSMutableData dataWithCapacity:(1 + 2 + 2 + 4 + 2 + rdlen)];
|
||||
|
||||
u8 = 0;
|
||||
[rrData appendBytes:&u8 length:sizeof(u8)];
|
||||
u16 = htons(kDNSServiceType_SRV);
|
||||
[rrData appendBytes:&u16 length:sizeof(u16)];
|
||||
u16 = htons(kDNSServiceClass_IN);
|
||||
[rrData appendBytes:&u16 length:sizeof(u16)];
|
||||
u32 = htonl(666);
|
||||
[rrData appendBytes:&u32 length:sizeof(u32)];
|
||||
u16 = htons(rdlen);
|
||||
[rrData appendBytes:&u16 length:sizeof(u16)];
|
||||
[rrData appendBytes:rdata length:rdlen];
|
||||
|
||||
// Parse the record.
|
||||
|
||||
rr = dns_parse_resource_record([rrData bytes], (uint32_t) [rrData length]);
|
||||
if (rr != NULL)
|
||||
{
|
||||
NSString *target;
|
||||
|
||||
target = [NSString stringWithCString:rr->data.SRV->target encoding:NSASCIIStringEncoding];
|
||||
if (target != nil)
|
||||
{
|
||||
UInt16 priority = rr->data.SRV->priority;
|
||||
UInt16 weight = rr->data.SRV->weight;
|
||||
UInt16 port = rr->data.SRV->port;
|
||||
|
||||
result = [XMPPSRVRecord recordWithPriority:priority weight:weight port:port target:target];
|
||||
}
|
||||
|
||||
dns_free_resource_record(rr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void QueryRecordCallback(DNSServiceRef sdRef,
|
||||
DNSServiceFlags flags,
|
||||
uint32_t interfaceIndex,
|
||||
DNSServiceErrorType errorCode,
|
||||
const char * fullname,
|
||||
uint16_t rrtype,
|
||||
uint16_t rrclass,
|
||||
uint16_t rdlen,
|
||||
const void * rdata,
|
||||
uint32_t ttl,
|
||||
void * context)
|
||||
{
|
||||
// Called when we get a response to our query.
|
||||
// It does some preliminary work, but the bulk of the interesting stuff
|
||||
// is done in the processRecord:length: method.
|
||||
|
||||
XMPPSRVResolver *resolver = (__bridge XMPPSRVResolver *)context;
|
||||
|
||||
NSCAssert(dispatch_get_specific(resolver->resolverQueueTag), @"Invoked on incorrect queue");
|
||||
|
||||
XMPPLogCTrace();
|
||||
|
||||
if (!(flags & kDNSServiceFlagsAdd))
|
||||
{
|
||||
// If the kDNSServiceFlagsAdd flag is not set, the domain information is not valid.
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode == kDNSServiceErr_NoError &&
|
||||
rrtype == kDNSServiceType_SRV)
|
||||
{
|
||||
XMPPSRVRecord *record = [resolver processRecord:rdata length:rdlen];
|
||||
if (record)
|
||||
{
|
||||
[resolver->results addObject:record];
|
||||
}
|
||||
|
||||
if ( ! (flags & kDNSServiceFlagsMoreComing) )
|
||||
{
|
||||
[resolver succeed];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[resolver failWithDNSError:errorCode];
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Public Methods
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)startWithSRVName:(NSString *)aSRVName timeout:(NSTimeInterval)aTimeout
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
if (resolveInProgress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
XMPPLogTrace2(@"%@: startWithSRVName:%@ timeout:%f", THIS_FILE, aSRVName, aTimeout);
|
||||
|
||||
// Save parameters
|
||||
|
||||
srvName = [aSRVName copy];
|
||||
|
||||
timeout = aTimeout;
|
||||
|
||||
// Check parameters
|
||||
|
||||
const char *srvNameCStr = [srvName cStringUsingEncoding:NSASCIIStringEncoding];
|
||||
if (srvNameCStr == NULL)
|
||||
{
|
||||
[self failWithDNSError:kDNSServiceErr_BadParam];
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// Create DNS Service
|
||||
|
||||
DNSServiceErrorType sdErr;
|
||||
sdErr = DNSServiceQueryRecord(&sdRef, // Pointer to unitialized DNSServiceRef
|
||||
kDNSServiceFlagsReturnIntermediates, // Flags
|
||||
kDNSServiceInterfaceIndexAny, // Interface index
|
||||
srvNameCStr, // Full domain name
|
||||
kDNSServiceType_SRV, // rrtype
|
||||
kDNSServiceClass_IN, // rrclass
|
||||
QueryRecordCallback, // Callback method
|
||||
(__bridge void *)self); // Context pointer
|
||||
|
||||
if (sdErr != kDNSServiceErr_NoError)
|
||||
{
|
||||
[self failWithDNSError:sdErr];
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract unix socket (so we can poll for events)
|
||||
|
||||
sdFd = DNSServiceRefSockFD(sdRef);
|
||||
if (sdFd < 0)
|
||||
{
|
||||
// Todo...
|
||||
}
|
||||
|
||||
// Create GCD read source for sd file descriptor
|
||||
|
||||
sdReadSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, sdFd, 0, resolverQueue);
|
||||
|
||||
dispatch_source_set_event_handler(sdReadSource, ^{ @autoreleasepool {
|
||||
|
||||
XMPPLogVerbose(@"%@: sdReadSource_eventHandler", THIS_FILE);
|
||||
|
||||
// There is data to be read on the socket (or an error occurred).
|
||||
//
|
||||
// Invoking DNSServiceProcessResult will invoke our QueryRecordCallback,
|
||||
// the callback we set when we created the sdRef.
|
||||
|
||||
DNSServiceErrorType dnsErr = DNSServiceProcessResult(sdRef);
|
||||
if (dnsErr != kDNSServiceErr_NoError)
|
||||
{
|
||||
[self failWithDNSError:dnsErr];
|
||||
}
|
||||
|
||||
}});
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_source_t theSdReadSource = sdReadSource;
|
||||
#endif
|
||||
DNSServiceRef theSdRef = sdRef;
|
||||
|
||||
dispatch_source_set_cancel_handler(sdReadSource, ^{ @autoreleasepool {
|
||||
|
||||
XMPPLogVerbose(@"%@: sdReadSource_cancelHandler", THIS_FILE);
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(theSdReadSource);
|
||||
#endif
|
||||
DNSServiceRefDeallocate(theSdRef);
|
||||
|
||||
}});
|
||||
|
||||
dispatch_resume(sdReadSource);
|
||||
|
||||
// Create timer (if requested timeout > 0)
|
||||
|
||||
if (timeout > 0.0)
|
||||
{
|
||||
timeoutTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, resolverQueue);
|
||||
|
||||
dispatch_source_set_event_handler(timeoutTimer, ^{ @autoreleasepool {
|
||||
|
||||
NSString *errMsg = @"Operation timed out";
|
||||
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
|
||||
|
||||
NSError *err = [NSError errorWithDomain:XMPPSRVResolverErrorDomain code:0 userInfo:userInfo];
|
||||
|
||||
[self failWithError:err];
|
||||
|
||||
}});
|
||||
|
||||
dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC));
|
||||
|
||||
dispatch_source_set_timer(timeoutTimer, tt, DISPATCH_TIME_FOREVER, 0);
|
||||
dispatch_resume(timeoutTimer);
|
||||
}
|
||||
|
||||
resolveInProgress = YES;
|
||||
}};
|
||||
|
||||
if (dispatch_get_specific(resolverQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_async(resolverQueue, block);
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
XMPPLogTrace();
|
||||
|
||||
delegate = nil;
|
||||
if (delegateQueue)
|
||||
{
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(delegateQueue);
|
||||
#endif
|
||||
delegateQueue = NULL;
|
||||
}
|
||||
|
||||
[results removeAllObjects];
|
||||
|
||||
if (sdReadSource)
|
||||
{
|
||||
// Cancel the readSource.
|
||||
// It will be released from within the cancel handler.
|
||||
dispatch_source_cancel(sdReadSource);
|
||||
sdReadSource = NULL;
|
||||
sdFd = -1;
|
||||
|
||||
// The sdRef will be deallocated from within the cancel handler too.
|
||||
sdRef = NULL;
|
||||
}
|
||||
|
||||
if (timeoutTimer)
|
||||
{
|
||||
dispatch_source_cancel(timeoutTimer);
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(timeoutTimer);
|
||||
#endif
|
||||
timeoutTimer = NULL;
|
||||
}
|
||||
|
||||
resolveInProgress = NO;
|
||||
}};
|
||||
|
||||
if (dispatch_get_specific(resolverQueueTag))
|
||||
block();
|
||||
else
|
||||
dispatch_sync(resolverQueue, block);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Utility Methods
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+ (NSString *)srvNameFromXMPPDomain:(NSString *)xmppDomain
|
||||
{
|
||||
if (xmppDomain == nil)
|
||||
return nil;
|
||||
else
|
||||
return [NSString stringWithFormat:@"_xmpp-client._tcp.%@", xmppDomain];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation XMPPSRVRecord
|
||||
|
||||
@synthesize priority;
|
||||
@synthesize weight;
|
||||
@synthesize port;
|
||||
@synthesize target;
|
||||
|
||||
@synthesize sum;
|
||||
@synthesize srvResultsIndex;
|
||||
|
||||
|
||||
+ (XMPPSRVRecord *)recordWithPriority:(UInt16)p1 weight:(UInt16)w port:(UInt16)p2 target:(NSString *)t
|
||||
{
|
||||
return [[XMPPSRVRecord alloc] initWithPriority:p1 weight:w port:p2 target:t];
|
||||
}
|
||||
|
||||
- (id)initWithPriority:(UInt16)p1 weight:(UInt16)w port:(UInt16)p2 target:(NSString *)t
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
priority = p1;
|
||||
weight = w;
|
||||
port = p2;
|
||||
target = [t copy];
|
||||
|
||||
sum = 0;
|
||||
srvResultsIndex = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return [NSString stringWithFormat:@"<%@:%p target(%@) port(%hu) priority(%hu) weight(%hu)>",
|
||||
NSStringFromClass([self class]), self, target, port, priority, weight];
|
||||
}
|
||||
|
||||
- (NSComparisonResult)compareByPriority:(XMPPSRVRecord *)aRecord
|
||||
{
|
||||
UInt16 mPriority = self.priority;
|
||||
UInt16 aPriority = aRecord.priority;
|
||||
|
||||
if (mPriority < aPriority)
|
||||
return NSOrderedAscending;
|
||||
|
||||
if (mPriority > aPriority)
|
||||
return NSOrderedDescending;
|
||||
|
||||
return NSOrderedSame;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface XMPPStringPrep : NSObject
|
||||
|
||||
/**
|
||||
* Preps a node identifier for use in a JID.
|
||||
* If the given node is invalid, this method returns nil.
|
||||
*
|
||||
* See the XMPP RFC (6120) for details.
|
||||
*
|
||||
* Note: The prep properly converts the string to lowercase, as per the RFC.
|
||||
**/
|
||||
+ (NSString *)prepNode:(NSString *)node;
|
||||
|
||||
/**
|
||||
* Preps a domain name for use in a JID.
|
||||
* If the given domain is invalid, this method returns nil.
|
||||
*
|
||||
* See the XMPP RFC (6120) for details.
|
||||
**/
|
||||
+ (NSString *)prepDomain:(NSString *)domain;
|
||||
|
||||
/**
|
||||
* Preps a resource identifier for use in a JID.
|
||||
* If the given node is invalid, this method returns nil.
|
||||
*
|
||||
* See the XMPP RFC (6120) for details.
|
||||
**/
|
||||
+ (NSString *)prepResource:(NSString *)resource;
|
||||
|
||||
/**
|
||||
* Preps a password with SASLprep profile.
|
||||
* If the given string is invalid, this method returns nil.
|
||||
*
|
||||
* See the SCRAM RFC (5802) for details.
|
||||
**/
|
||||
|
||||
+ (NSString *) prepPassword:(NSString *)password;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,67 @@
|
||||
#import "XMPPStringPrep.h"
|
||||
#import "stringprep.h"
|
||||
|
||||
|
||||
@implementation XMPPStringPrep
|
||||
|
||||
+ (NSString *)prepNode:(NSString *)node
|
||||
{
|
||||
if(node == nil) return nil;
|
||||
|
||||
// Each allowable portion of a JID MUST NOT be more than 1023 bytes in length.
|
||||
// We make the buffer just big enough to hold a null-terminated string of this length.
|
||||
char buf[1024];
|
||||
|
||||
strncpy(buf, [node UTF8String], sizeof(buf));
|
||||
|
||||
if(stringprep_xmpp_nodeprep(buf, sizeof(buf)) != 0) return nil;
|
||||
|
||||
return [NSString stringWithUTF8String:buf];
|
||||
}
|
||||
|
||||
+ (NSString *)prepDomain:(NSString *)domain
|
||||
{
|
||||
if(domain == nil) return nil;
|
||||
|
||||
// Each allowable portion of a JID MUST NOT be more than 1023 bytes in length.
|
||||
// We make the buffer just big enough to hold a null-terminated string of this length.
|
||||
char buf[1024];
|
||||
|
||||
strncpy(buf, [domain UTF8String], sizeof(buf));
|
||||
|
||||
if(stringprep_nameprep(buf, sizeof(buf)) != 0) return nil;
|
||||
|
||||
return [NSString stringWithUTF8String:buf];
|
||||
}
|
||||
|
||||
+ (NSString *)prepResource:(NSString *)resource
|
||||
{
|
||||
if(resource == nil) return nil;
|
||||
|
||||
// Each allowable portion of a JID MUST NOT be more than 1023 bytes in length.
|
||||
// We make the buffer just big enough to hold a null-terminated string of this length.
|
||||
char buf[1024];
|
||||
|
||||
strncpy(buf, [resource UTF8String], sizeof(buf));
|
||||
|
||||
if(stringprep_xmpp_resourceprep(buf, sizeof(buf)) != 0) return nil;
|
||||
|
||||
return [NSString stringWithUTF8String:buf];
|
||||
}
|
||||
|
||||
+ (NSString *)prepPassword:(NSString *)password
|
||||
{
|
||||
if(password == nil) return nil;
|
||||
|
||||
// Each allowable portion of a JID MUST NOT be more than 1023 bytes in length.
|
||||
// We make the buffer just big enough to hold a null-terminated string of this length.
|
||||
char buf[1024];
|
||||
|
||||
strncpy(buf, [password UTF8String], sizeof(buf));
|
||||
|
||||
if(stringprep(buf, sizeof(buf), 0, stringprep_saslprep) != 0) return nil;
|
||||
|
||||
return [NSString stringWithUTF8String:buf];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* This class is a simple wrapper around dispatch_source_t timers.
|
||||
*
|
||||
* The primary motivation for this is to allow timers to be stored in collections.
|
||||
* But the class also makes it easier to code timers, as it simplifies the API.
|
||||
**/
|
||||
@interface XMPPTimer : NSObject
|
||||
|
||||
/**
|
||||
* Creates an instance of a timer that will fire on the given queue.
|
||||
* It will invoke the given event handler block when it fires.
|
||||
**/
|
||||
- (instancetype)initWithQueue:(dispatch_queue_t)queue eventHandler:(dispatch_block_t)block;
|
||||
|
||||
/**
|
||||
* Starts the timer.
|
||||
* It will first fire after the timeout.
|
||||
* After that, it will continue to fire every interval.
|
||||
*
|
||||
* The interval is optional.
|
||||
* If interval is zero (or negative), it will not use an interval (will only fire once after the timeout).
|
||||
*
|
||||
* This method can only be called once.
|
||||
**/
|
||||
- (void)startWithTimeout:(NSTimeInterval)timeout interval:(NSTimeInterval)interval;
|
||||
|
||||
/**
|
||||
* Allows you to update an already started timer.
|
||||
*
|
||||
* The new timeout that you pass can be applied to 'now' or to the original start time of the timer.
|
||||
**/
|
||||
- (void)updateTimeout:(NSTimeInterval)timeout fromOriginalStartTime:(BOOL)useOriginalStartTime;
|
||||
|
||||
/**
|
||||
* Cancels the timer so that it won't fire.
|
||||
**/
|
||||
- (void)cancel;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,86 @@
|
||||
#import "XMPPTimer.h"
|
||||
#import "XMPPLogging.h"
|
||||
|
||||
// Log levels: off, error, warn, info, verbose
|
||||
// Log flags: trace
|
||||
#if DEBUG
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#else
|
||||
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
@implementation XMPPTimer
|
||||
{
|
||||
BOOL isStarted;
|
||||
|
||||
dispatch_time_t start;
|
||||
uint64_t timeout;
|
||||
uint64_t interval;
|
||||
|
||||
dispatch_source_t timer;
|
||||
}
|
||||
|
||||
- (instancetype)initWithQueue:(dispatch_queue_t)queue eventHandler:(dispatch_block_t)block
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
||||
dispatch_source_set_event_handler(timer, block);
|
||||
|
||||
isStarted = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self cancel];
|
||||
}
|
||||
|
||||
- (void)startWithTimeout:(NSTimeInterval)inTimeout interval:(NSTimeInterval)inInterval
|
||||
{
|
||||
if (isStarted)
|
||||
{
|
||||
XMPPLogWarn(@"Unable to start timer - already started");
|
||||
return;
|
||||
}
|
||||
|
||||
start = dispatch_time(DISPATCH_TIME_NOW, 0);
|
||||
timeout = (inTimeout * NSEC_PER_SEC);
|
||||
interval = (inInterval > 0.0) ? (inInterval * NSEC_PER_SEC) : DISPATCH_TIME_FOREVER;
|
||||
|
||||
dispatch_source_set_timer(timer, dispatch_time(start, timeout), interval, 0);
|
||||
dispatch_resume(timer);
|
||||
|
||||
isStarted = YES;
|
||||
}
|
||||
|
||||
- (void)updateTimeout:(NSTimeInterval)inTimeout fromOriginalStartTime:(BOOL)useOriginalStartTime
|
||||
{
|
||||
if (!isStarted)
|
||||
{
|
||||
XMPPLogWarn(@"Unable to update timer - not yet started");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!useOriginalStartTime) {
|
||||
start = dispatch_time(DISPATCH_TIME_NOW, 0);
|
||||
}
|
||||
timeout = (inTimeout * NSEC_PER_SEC);
|
||||
|
||||
dispatch_source_set_timer(timer, dispatch_time(start, timeout), interval, 0);
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
if (timer)
|
||||
{
|
||||
dispatch_source_cancel(timer);
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(timer);
|
||||
#endif
|
||||
timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user