Skip to content

Commit

Permalink
Merge pull request #25 from sinnerschrader-mobile/feature_delegateDis…
Browse files Browse the repository at this point in the history
…patch

Feature delegate dispatch
  • Loading branch information
nilsgrabenhorst committed Aug 31, 2015
2 parents e8f599f + 9ab9230 commit 6eba5f8
Show file tree
Hide file tree
Showing 310 changed files with 4,172 additions and 2,469 deletions.
47 changes: 47 additions & 0 deletions DelegateDispatch/S2MDelegateDispatcher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// S2MDelegateDispatcher.h
// DelegateDispatch
//
// Created by Nils Grabenhorst on 22/05/15.
// Copyright (c) 2015 SinnerSchrader-Mobile. All rights reserved.
//

#import <Foundation/Foundation.h>
@protocol S2MDelegateDispatcher
@property (nonatomic, readonly) NSUInteger delegateCount;

/**
Adds a delegate to an internal list of delegates.
@param delegate The delegate @b must conform to the protocol that was provided when initializing an instance of this RFMessageTrampoline.
*/
- (void)addDelegate:(id)delegate;


/**
Removes a delegate from the internal list of delegates.
*/
- (void)removeDelegate:(id)delegate;

- (BOOL)isRegisteredAsDelegate:(id)delegate;
@end





/**
Forwards messages to multiple delegates.
The messages must belong to a given protocol.
Works for instance methods only, not for class methods.
@note Each delegate can only be added once. Pointer equality is checked to determine whether a delegate can be added to the list. Two separate objects with the same hash and returning \c YES for \c isEqual can therefore be added to the list of delegates.
@warning The return value of a message is undefined. Only the return value of the last delegate is returned to the sender of a message. Since the order of delegates called is undefined, it is best to only specify methods that do not return anything in the given protocol.
*/
@interface S2MDelegateDispatcher : NSProxy <S2MDelegateDispatcher>
- (instancetype)initWithDelegateProtocol:(Protocol *)protocol;
@end




136 changes: 136 additions & 0 deletions DelegateDispatch/S2MDelegateDispatcher.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//
// S2MDelegateDispatcher.m
// DelegateDispatch
//
// Created by Nils Grabenhorst on 22/05/15.
// Copyright (c) 2015 SinnerSchrader-Mobile. All rights reserved.
//

#import "S2MDelegateDispatcher.h"
#import "S2MProtocolIntrospector.h"
#import <ObjC/runtime.h>

@interface S2MDelegateDispatcher ()

@property (nonatomic, strong) NSHashTable *delegates;
@property (nonatomic, strong) S2MProtocolIntrospector *protocolIntrospector;
@property (nonatomic) Protocol *delegateProtocol;

/// This queue is used to assure serial access to the delegates hashTable.
@property (nonatomic, strong) dispatch_queue_t serialQueue;
@end

@implementation S2MDelegateDispatcher

- (instancetype)initWithDelegateProtocol:(Protocol *)protocol
{
NSParameterAssert(protocol);
if (self) {
_serialQueue = dispatch_queue_create("com.sinnerschrader-mobile.delegateDispatcher.accessSerializationQueue", DISPATCH_QUEUE_SERIAL);

_delegateProtocol = protocol;
_protocolIntrospector = [[S2MProtocolIntrospector alloc] initWithProtocol:_delegateProtocol];
_delegates = [[NSHashTable alloc] initWithOptions:NSHashTableWeakMemory | NSHashTableObjectPointerPersonality
capacity:1];
}
return self;
}

#pragma mark - Public

- (void)addDelegate:(id)delegate
{
NSAssert([delegate conformsToProtocol:self.delegateProtocol], @"You can only add delegates conforming to %@.", self.delegateProtocol);
if ([delegate conformsToProtocol:self.delegateProtocol]) {
dispatch_sync(self.serialQueue, ^{
[self.delegates addObject:delegate];
});
}
}

- (void)removeDelegate:(id)delegate
{
dispatch_sync(self.serialQueue, ^{
[self.delegates removeObject:delegate];
});
}

- (BOOL)isRegisteredAsDelegate:(id)delegate
{
__block BOOL isRegistered = NO;
dispatch_sync(self.serialQueue, ^{
isRegistered = [self.delegates containsObject:delegate];
});
return isRegistered;
}

- (NSString *)description
{
NSString *__block description;
dispatch_sync(self.serialQueue, ^{
description = [NSString stringWithFormat:@"%@<%@> (current delegates: %@)", NSStringFromClass(self.class), NSStringFromProtocol(self.delegateProtocol), self.delegates];
});
return description;
}



#pragma mark - Accessors

- (NSUInteger)delegateCount
{
NSArray *__block allDelegates;
dispatch_sync(self.serialQueue, ^{
allDelegates = [self.delegates allObjects];
});
// the count of an NSHashTable does not update if a collection member has been deallocated and therefore zeroed out. Hence we need to count allObjects to get the correct number.
return [allDelegates count];
}



#pragma mark - HOM

- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return protocol_isEqual(aProtocol, self.delegateProtocol);
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
if ([self.protocolIntrospector protocolDeclaresMethodForSelector:aSelector]) {
return YES;
}
return [super respondsToSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
NSArray *__block allDelegates;
dispatch_sync(self.serialQueue, ^{
allDelegates = [self.delegates allObjects];
});

for (id delegate in allDelegates) {
if ([delegate respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:delegate];
}
}
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSAssert(self.protocolIntrospector, @"There is no protocolIntrospector!");
NSMethodSignature *signature = [self.protocolIntrospector protocolMethodSignatureForSelector:aSelector];

if (!signature) {
signature = [super methodSignatureForSelector:aSelector];
}
NSAssert(signature, @"Could not get a method description for the selector '%@' from protocol '%@'.", NSStringFromSelector(aSelector), NSStringFromProtocol(self.delegateProtocol));

return signature;
}




@end
18 changes: 18 additions & 0 deletions DelegateDispatch/S2MProtocolIntrospector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// S2MProtocolIntrospector.h
// DelegateDispatch
//
// Created by Nils Grabenhorst on 22/05/15.
// Copyright (c) 2015 SinnerSchrader-Mobile. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface S2MProtocolIntrospector : NSObject

- (BOOL)protocolDeclaresMethodForSelector:(SEL)aSelector;
- (NSMethodSignature *)protocolMethodSignatureForSelector:(SEL)aSelector;

- (instancetype)initWithProtocol:(Protocol *)protocol NS_DESIGNATED_INITIALIZER;

@end
98 changes: 98 additions & 0 deletions DelegateDispatch/S2MProtocolIntrospector.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// S2MProtocolIntrospector.m
// DelegateDispatch
//
// Created by Nils Grabenhorst on 22/05/15.
// Copyright (c) 2015 SinnerSchrader-Mobile. All rights reserved.
//

#import "S2MProtocolIntrospector.h"
#import <ObjC/runtime.h>
#import <libkern/OSAtomic.h>

@interface S2MProtocolIntrospector () {
CFDictionaryRef _signatures;
}
@property (nonatomic) Protocol *protocol;
@property (nonatomic) CFDictionaryRef signatures;
@end

@implementation S2MProtocolIntrospector

#pragma mark - Lifecycle

- (instancetype)initWithProtocol:(Protocol *)protocol {
self = [super init];
if (self) {
_protocol = protocol;
}
return self;
}

#pragma mark - Public

- (BOOL)protocolDeclaresMethodForSelector:(SEL)aSelector {
return [self protocolMethodSignatureForSelector:aSelector] != nil;
}

- (NSMethodSignature *)protocolMethodSignatureForSelector:(SEL)aSelector {
return CFDictionaryGetValue(self.signatures, aSelector);
}


#pragma mark - Accessors

- (CFDictionaryRef)signatures {
if (!_signatures) {
_signatures = [self methodSignaturesForProtocol:self.protocol];
}
return _signatures;
}

#pragma mark - Private

// See https://github.com/steipete/PSTDelegateProxy
static CFMutableDictionaryRef _protocolCache = nil;
static OSSpinLock _lock = OS_SPINLOCK_INIT;

- (CFDictionaryRef)methodSignaturesForProtocol:(Protocol *)protocol {
OSSpinLockLock(&_lock);
// Cache lookup
if (!_protocolCache) _protocolCache = CFDictionaryCreateMutable(NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFDictionaryRef signatureCache = CFDictionaryGetValue(_protocolCache, (__bridge const void *)(protocol));

if (!signatureCache) {
// Add protocol methods + derived protocol method definitions into protocolCache.
signatureCache = CFDictionaryCreateMutable(NULL, 0, NULL, &kCFTypeDictionaryValueCallBacks);
[self methodSignaturesForProtocol:protocol inDictionary:(CFMutableDictionaryRef)signatureCache];
CFDictionarySetValue(_protocolCache, (__bridge const void *)(protocol), signatureCache);
CFRelease(signatureCache);
}
OSSpinLockUnlock(&_lock);
return signatureCache;
}

- (void)methodSignaturesForProtocol:(Protocol *)protocol inDictionary:(CFMutableDictionaryRef)cache {
void (^enumerateRequiredMethods)(BOOL) = ^(BOOL isRequired) {
unsigned int methodCount;
struct objc_method_description *descr = protocol_copyMethodDescriptionList(protocol, isRequired, YES, &methodCount);
for (NSUInteger idx = 0; idx < methodCount; idx++) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:descr[idx].types];
CFDictionarySetValue(cache, descr[idx].name, (__bridge const void *)(signature));
}
free(descr);
};
// We need to enumerate both required and optional protocol methods.
enumerateRequiredMethods(NO); enumerateRequiredMethods(YES);

// There might be sub-protocols we need to catch as well.
unsigned int inheritedProtocolCount;
Protocol *__unsafe_unretained* inheritedProtocols = protocol_copyProtocolList(protocol, &inheritedProtocolCount);
for (NSUInteger idx = 0; idx < inheritedProtocolCount; idx++) {
[self methodSignaturesForProtocol:inheritedProtocols[idx] inDictionary:cache];
}
free(inheritedProtocols);
}


@end
1 change: 1 addition & 0 deletions Example/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ target "S2MToolbox" do
pod 'S2MToolbox/ShopFinder', :path => "../"
pod 'S2MToolbox/HockeyApp', :path => "../"
pod 'S2MToolbox/LocalNotificationHelper', :path => "../"
pod 'S2MToolbox/DelegateDispatch', :path => "../"
end

target "S2MToolboxTests", :exclusive => true do
Expand Down
32 changes: 17 additions & 15 deletions Example/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
PODS:
- HockeySDK (3.6.2)
- HockeySDK (3.6.4)
- Kiwi (2.3.1)
- S2MToolbox (0.2.0):
- S2MToolbox/Foundation (= 0.2.0)
- S2MToolbox/UIKit (= 0.2.0)
- S2MToolbox/Foundation (0.2.0)
- S2MToolbox/HockeyApp (0.2.0):
- S2MToolbox (0.2.2):
- S2MToolbox/Foundation (= 0.2.2)
- S2MToolbox/UIKit (= 0.2.2)
- S2MToolbox/DelegateDispatch (0.2.2)
- S2MToolbox/Foundation (0.2.2)
- S2MToolbox/HockeyApp (0.2.2):
- HockeySDK
- S2MToolbox/Kiwi (0.2.0):
- S2MToolbox/Kiwi (0.2.2):
- Kiwi (~> 2.3.0)
- S2MToolbox/LocalNotificationHelper (0.2.0)
- S2MToolbox/QRCode (0.2.0)
- S2MToolbox/ShopFinder (0.2.0)
- S2MToolbox/UIKit (0.2.0)
- S2MToolbox/LocalNotificationHelper (0.2.2)
- S2MToolbox/QRCode (0.2.2)
- S2MToolbox/ShopFinder (0.2.2)
- S2MToolbox/UIKit (0.2.2)

DEPENDENCIES:
- S2MToolbox (from `../`)
- S2MToolbox/DelegateDispatch (from `../`)
- S2MToolbox/HockeyApp (from `../`)
- S2MToolbox/Kiwi (from `../`)
- S2MToolbox/LocalNotificationHelper (from `../`)
Expand All @@ -27,8 +29,8 @@ EXTERNAL SOURCES:
:path: ../

SPEC CHECKSUMS:
HockeySDK: bfd1f5ac75938b07499c4ac12932244b72a2e70b
Kiwi: 73e1400209055ee9c8ba78c6012b6b642d0fb9f7
S2MToolbox: d8460f4f9a8fa633283106f34b670bf19cfa834d
HockeySDK: c07cdd580296737edcd0963e292c19885a53f563
Kiwi: f038a6c61f7a9e4d7766bff5717aa3b3fdb75f55
S2MToolbox: 162cfcf46316ba952776fe8d16ff034e6e0bae51

COCOAPODS: 0.35.0
COCOAPODS: 0.37.1

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Example/Pods/Headers/Private/HockeySDK/BITAppStoreHeader.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Example/Pods/Headers/Private/HockeySDK/BITAuthenticator.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Example/Pods/Headers/Private/HockeySDK/BITCrashDetails.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Example/Pods/Headers/Private/HockeySDK/BITCrashManager.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6eba5f8

Please sign in to comment.