Skip to content

Instantly share code, notes, and snippets.

@joshavant
Last active December 15, 2015 19:59
Show Gist options
  • Save joshavant/5314853 to your computer and use it in GitHub Desktop.
Save joshavant/5314853 to your computer and use it in GitHub Desktop.
.delegate MitM Proxying

.delegate MitM Proxying

Overview

This is a quick and easy way to insert yourself as a .delegate man-in-the-middle for protocol methods. External to your class, the .delegate property can be used as normal, and all method calls will filter through, as if nothing has changed.

A benefit of this method over swizzling is that it will keep proxying behavior contained to the subclass that implements this, and not all instances of the parent class.

License

This is licensed under a MIT/Beerware License:

Copyright (c) 2013 Josh Avant

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

If we meet some day, and you think this stuff is worth it, you can buy me a
beer in return.
#import "JAScrollView.h"
#import <objc/runtime.h>
@interface JAScrollView () <UIScrollViewDelegate>
{
__weak id<UIScrollViewDelegate> _externalDelegate;
}
@property(nonatomic) NSSet *delegateSelectorStrings;
- (NSSet *)setOfSelectorStringsImplementedByProtocol:(Protocol *)protocol;
@end
// This class proxies UIScrollViewDelegate.
//
// * If this class implements a method in that protocol, the implementation here will be called.
// * If this class doesn't implement a method in that protocol, the message will be forwarded to
// _externalDelegate, automatically.
//
// !: For any UIScrollViewDelegate methods implemented here, it is imperative that, at some point in the
// implementation, _externalDelegate is checked to respond to that selector and, if so, the method is
// manually called on _externalDelegate. This ensures that all protocol methods will eventually pass through
// to the externally set delegate.
@implementation JAScrollView
- (id)initWithFrame:(CGRect)frame
{
if(self = [super initWithFrame:frame])
{
// set .delegateSelectorStrings, before assigning .delegate
self.delegateSelectorStrings = [self setOfSelectorStringsImplementedByProtocol:@protocol(UIScrollViewDelegate)];
self.delegate = self;
}
return self;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
// do mitm stuff here
// check if _externalDelegate responds and, if so, manually forward the message
if([_externalDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)])
{
[_externalDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
}
}
#pragma mark - UIScrollViewDelegate Proxying
- (void)setDelegate:(id<UIScrollViewDelegate>)delegate
{
[super setDelegate:self]; // Cocoa caches delegate capabilities so this triggers recaching
if(delegate != self) _externalDelegate = delegate;
}
- (NSSet *)setOfSelectorStringsImplementedByProtocol:(Protocol *)protocol
{
NSMutableSet *result = [NSMutableSet set];
unsigned int requiredInstanceCount = 0;
unsigned int requiredClassCount = 0;
unsigned int optionalInstanceCount = 0;
unsigned int optionalClassCount = 0;
struct objc_method_description *requiredInstance = protocol_copyMethodDescriptionList(protocol, YES, YES, &requiredInstanceCount);
struct objc_method_description *requiredClass = protocol_copyMethodDescriptionList(protocol, YES, NO, &requiredClassCount);
struct objc_method_description *optionalInstance = protocol_copyMethodDescriptionList(protocol, NO, YES, &optionalInstanceCount);
struct objc_method_description *optionalClass = protocol_copyMethodDescriptionList(protocol, NO, NO, &optionalClassCount);
for(int i = 0; i < requiredInstanceCount; i++)
{
[result addObject:NSStringFromSelector(requiredInstance[i].name)];
}
for(int i = 0; i < requiredClassCount; i++)
{
[result addObject:NSStringFromSelector(requiredClass[i].name)];
}
for(int i = 0; i < optionalInstanceCount; i++)
{
[result addObject:NSStringFromSelector(optionalInstance[i].name)];
}
for(int i = 0; i < optionalClassCount; i++)
{
[result addObject:NSStringFromSelector(optionalClass[i].name)];
}
free(requiredInstance);
free(requiredClass);
free(optionalInstance);
free(optionalClass);
return result;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
if([self.delegateSelectorStrings containsObject:NSStringFromSelector(aSelector)])
{
if([_externalDelegate conformsToProtocol:@protocol(UIScrollViewDelegate)] &&
[_externalDelegate respondsToSelector:aSelector])
{
return YES;
}
}
return [super respondsToSelector:aSelector];
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if([self.delegateSelectorStrings containsObject:NSStringFromSelector(aSelector)])
{
return _externalDelegate;
}
return nil; // we shouldn't get down here
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment