This commit is contained in:
giuseppenuc committed 2016-02-24 16:56:39 +01:00
1 parent d281d765ea
commit 5b074a5176
1261 files changed
+199158 -7303

No files matched your search

+61
View File
@@ -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
+297
View File
@@ -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
+56
View File
@@ -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
+654
View File
@@ -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
+19
View File
@@ -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
+48
View File
@@ -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
+210
View File
@@ -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
+347
View File
@@ -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
+86
View File
@@ -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
+687
View File
@@ -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
+41
View File
@@ -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
+67
View File
@@ -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
+41
View File
@@ -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
+86
View File
@@ -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