Last active
July 25, 2017 05:25
-
-
Save CodaFi/def4b66dcbb57624a8c8 to your computer and use it in GitHub Desktop.
A quick reimplementation and port of NSProtocolChecker.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NUIProtocolChecker.h | |
// NUIKit | |
// | |
// Created by Robert Widmann on 1/14/15. | |
// Copyright (c) 2015 CodaFi. All rights reserved. | |
// Released under the MIT license. | |
// | |
#import <Foundation/Foundation.h> | |
#import <NUIKit/NUIKitDefines.h> | |
/// The NUIProtocolChecker class defines an object that restricts the messages that can be sent to | |
/// another object (referred to as the checker’s delegate). This fact can be particularly useful | |
/// when an object with many methods, only a few of which ought to be remotely accessible, is made | |
/// available using the distributed objects system. | |
/// | |
/// A protocol checker acts as a kind of proxy; when it receives a message that is in its designated | |
/// protocol, it forwards the message to its target and consequently appears to be the target object | |
/// itself. However, when it receives a message not in its protocol, it raises an | |
/// NSInvalidArgumentException to indicate that the message isn’t allowed, whether or not the target | |
/// object implements the method. | |
/// | |
/// The object should be careful about vending references to self—the protocol checker will convert | |
/// a return value of self to indicate the checker rather than the object for any messages forwarded | |
/// by the checker, but direct references to the object (bypassing the checker) could be passed | |
/// around by other objects. | |
@interface NUIProtocolChecker : NSProxy | |
/// Allocates and initializes an NUIProtocolChecker instance that will forward any messages in | |
/// aProtocol to anObject, the protocol checker’s target. | |
+ (instancetype)protocolCheckerWithTarget:(NSObject *)anObject protocol:(Protocol *)aProtocol; | |
/// Initializes a newly allocated NUIProtocolChecker instance that will forward any messages in | |
/// aProtocol to anObject, the protocol checker’s target. | |
- (instancetype)initWithTarget:(NSObject *)anObject protocol:(Protocol *)aProtocol; | |
/// Returns the protocol object the receiver uses. | |
@property (readonly) Protocol *protocol; | |
/// Returns the target of the receiver. | |
@property (readonly, strong) NSObject *target; | |
@end | |
/// Returns a method description structure for a specified method among the protocols a given class | |
/// conforms to. | |
/// | |
/// @param cls A class to search. | |
/// @param aSel A selector. | |
/// | |
NUIKIT_EXTERN struct objc_method_description nuiobjc_methodDescriptionForSelectorAmongProtocols(Class cls, SEL aSel); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// NUIProtocolChecker.m | |
// NUIKit | |
// | |
// Created by Robert Widmann on 1/14/15. | |
// Copyright (c) 2015 CodaFi. All rights reserved. | |
// | |
#import "NUIProtocolChecker.h" | |
#include <objc/runtime.h> | |
struct objc_method_description nuiobjc_methodDescriptionForSelectorAmongProtocols(Class cls, SEL sel) { | |
NSCParameterAssert(cls); | |
struct objc_method_description desc; | |
// Try the class hierarchy. | |
Class class = cls; | |
do { | |
unsigned int count = 0; | |
Protocol * __unsafe_unretained * list = class_copyProtocolList(class, &count); | |
for (NSUInteger i = 0; i < count; i++) { | |
// Matches among required methods. | |
desc = protocol_getMethodDescription(list[i], sel, YES, !class_isMetaClass(class)); | |
if (desc.name != NULL) { | |
return desc; | |
} | |
// Matches among optional methods. | |
desc = protocol_getMethodDescription(list[i], sel, NO, !class_isMetaClass(class)); | |
if (desc.name != NULL) { | |
return desc; | |
} | |
} | |
free(list); | |
} while ((class = [class superclass])); | |
// Fallback: The instance method may still exist in the class even if the object doesn't declare | |
// conformance or the runtime doesn't believe us. | |
Method meth = class_getInstanceMethod(cls, sel); | |
if (meth != NULL) { | |
return *method_getDescription(meth); | |
} | |
// Otherwise bad things. | |
desc.name = NULL; | |
desc.types = (char *)NULL; | |
return desc; | |
} | |
@implementation NUIProtocolChecker | |
+ (instancetype)protocolCheckerWithTarget:(NSObject *)anObject protocol:(Protocol *)aProtocol { | |
return [[self alloc] initWithTarget:anObject protocol:aProtocol]; | |
} | |
- (instancetype)initWithTarget:(NSObject *)anObject protocol:(Protocol *)aProtocol { | |
NSCParameterAssert(anObject); | |
NSCParameterAssert(aProtocol); | |
_target = NUI_RETAIN_MRCONLY(anObject); | |
_protocol = aProtocol; | |
return self; | |
} | |
- (BOOL)respondsToSelector:(SEL)aSelector { | |
if (protocol_getMethodDescription(self.protocol, aSelector, YES, YES).name != NULL) { | |
return YES; | |
} else if (protocol_getMethodDescription(self.protocol, aSelector, NO, YES).name != NULL) { | |
return [self.target respondsToSelector:aSelector]; | |
} | |
return NO; | |
} | |
- (BOOL)conformsToProtocol:(Protocol *)aProtocol { | |
return protocol_conformsToProtocol(self.protocol, aProtocol); | |
} | |
- (id)forwardingTargetForSelector:(SEL)aSelector { | |
if (protocol_getMethodDescription(self.protocol, aSelector, YES, YES).name != NULL) { | |
return self.target; | |
} else if (protocol_getMethodDescription(self.protocol, aSelector, NO, YES).name != NULL) { | |
return [self.target respondsToSelector:aSelector] ? self.target : nil; | |
} | |
return nil; | |
} | |
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { | |
if (aSelector == @selector(respondsToSelector:) || aSelector == @selector(conformsToProtocol:)) { | |
return [super methodSignatureForSelector:aSelector]; | |
} | |
return [self.target methodSignatureForSelector:aSelector]; | |
} | |
- (void)forwardInvocation:(NSInvocation *)invocation { | |
if (invocation.selector == @selector(respondsToSelector:) || invocation.selector == @selector(conformsToProtocol:)) { | |
return [invocation invokeWithTarget:self]; | |
} | |
if (nuiobjc_methodDescriptionForSelectorAmongProtocols(self.target.class, invocation.selector).name == NULL) { | |
NSCAssert(NO, @"NUIProtocolChecker target does not recognize selector %s", sel_getName(invocation.selector)); | |
} | |
[invocation invokeWithTarget:self.target]; | |
} | |
- (void)dealloc { | |
NUI_RELEASE_MRCONLY(_target); | |
_target = nil; | |
_protocol = nil; | |
NUI_DEALLOC_MRCONLY; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment