Skip to content

Instantly share code, notes, and snippets.

@CodaFi
Last active July 25, 2017 05:25
Show Gist options
  • Save CodaFi/def4b66dcbb57624a8c8 to your computer and use it in GitHub Desktop.
Save CodaFi/def4b66dcbb57624a8c8 to your computer and use it in GitHub Desktop.
A quick reimplementation and port of NSProtocolChecker.
//
// 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);
//
// 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