Intercept Objective-C Delegate Messages Within a Subclass

Catching a delegate method call in the originating subclass

This will take some time but you can achieve what you want.

//EDITED

I have added implementation of the method forwardInvocation:. This method is invoked on object when object doesn't recognize message which was sent to it.

In our case when some of the UIScrollView delegate's methods will be called and our subclass and it doesn't implement called method, forwardInvocation will be called. This method checks whether called selector is part of the UIScrollViewDelegate protocol. If yes and if 'true' delegate of our class responds to this selector we forward invocation of called selector to our custom delegate.

That being said, using this solution you don't have to implement all methods of UIScrollViewDelegate protocol in your subclass. Execution of the methods which won;t be implemented in subclass of the ScrollView will be forwarded to 'true' delegate of our subclass.

Example:

#import <objc/runtime.h>

@protocol YourSubclassDelegate<UIScrollViewDelegate>
@end

@interface YourSubclass ()
@property (nonatomic, weak) id<YourSubclassDelegate> delegate;
@end

@implementation

//override delegate setter
- (void)setDelegate:(UIScrollViewDelegate)delegate
{
self.customDelegate = delegate;
[super setDelegate:self];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//do something you want
if ([self.customDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
[self.customDelegate scrollViewDidScroll:self];
}
}

- (id)forwardingTargetForSelector:(SEL)selector
{
struct objc_method_description methodDescription = protocol_getMethodDescription(@protocol(UIScrollViewDelegate), selector, YES, YES);

if(NULL != methodDescription.name) {
if ([self.customDelegate respondsToSelector:selector]) {
return self.customDelegate;
}
}

return nil;
}

@end

Subclass as Delegate

Based upon what you are doing, I think a UICollectionView would fit perfectly with what you want to accomplish. This removes you from having to write your own code for reusing views and other things you would have to do in order to have performant code.

UICollectionView Reference

How to subclass UIScrollView and make the delegate property private

There is a problem with making MySubclass its own delegate. Presumably you don't want to run custom code for all of the UIScrollViewDelegate methods, but you have to forward the messages to the user-provided delegate whether you have your own implementation or not. So you could try to implement all of the delegate methods, with most of them just forwarding like this:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self.myOwnDelegate scrollViewDidZoom:scrollView];
}

The problem here is that sometimes new versions of iOS add new delegate methods. For example, iOS 5.0 added scrollViewWillEndDragging:withVelocity:targetContentOffset:. So your scrollview subclass won't be future-proof.

The best way to handle this is to create a separate, private object that just acts as your scrollview's delegate, and handles forwarding. This dedicated-delegate object can forward every message it receives to the user-provided delegate, because it only receives delegate messages.

Here's what you do. In your header file, you only need to declare the interface for your scrollview subclass. You don't need to expose any new methods or properties, so it just looks like this:

MyScrollView.h

@interface MyScrollView : UIScrollView
@end

All the real work is done in the .m file. First, we define the interface for the private delegate class. Its job is to call back into MyScrollView for some of the delegate methods, and to forward all messages to the user's delegate. So we only want to give it methods that are part of UIScrollViewDelegate. We don't want it to have extra methods for managing a reference to the user's delegate, so we'll just keep that reference as an instance variable:

MyScrollView.m

@interface MyScrollViewPrivateDelegate : NSObject <UIScrollViewDelegate> {
@public
id<UIScrollViewDelegate> _userDelegate;
}
@end

Next we'll implement MyScrollView. It needs to create an instance of MyScrollViewPrivateDelegate, which it needs to own. Since a UIScrollView doesn't own its delegate, we need an extra, strong reference to this object.

@implementation MyScrollView {
MyScrollViewPrivateDelegate *_myDelegate;
}

- (void)initDelegate {
_myDelegate = [[MyScrollViewPrivateDelegate alloc] init];
[_myDelegate retain]; // remove if using ARC
[super setDelegate:_myDelegate];
}

- (id)initWithFrame:(CGRect)frame {
if (!(self = [super initWithFrame:frame]))
return nil;
[self initDelegate];
return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
if (!(self = [super initWithCoder:aDecoder]))
return nil;
[self initDelegate];
return self;
}

- (void)dealloc {
// Omit this if using ARC
[_myDelegate release];
[super dealloc];
}

We need to override setDelegate: and delegate: to store and return a reference to the user's delegate:

- (void)setDelegate:(id<UIScrollViewDelegate>)delegate {
_myDelegate->_userDelegate = delegate;
// Scroll view delegate caches whether the delegate responds to some of the delegate
// methods, so we need to force it to re-evaluate if the delegate responds to them
super.delegate = nil;
super.delegate = (id)_myDelegate;
}

- (id<UIScrollViewDelegate>)delegate {
return _myDelegate->_userDelegate;
}

We also need to define any extra methods that our private delegate might need to use:

- (void)myScrollViewDidEndDecelerating {
// do whatever you want here
}

@end

Now we can finally define the implementation of MyScrollViewPrivateDelegate. We need to explicitly define each method that should contain our private custom code. The method needs to execute our custom code, and forward the message to the user's delegate, if the user's delegate responds to the message:

@implementation MyScrollViewPrivateDelegate

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[(MyScrollView *)scrollView myScrollViewDidEndDecelerating];
if ([_userDelegate respondsToSelector:_cmd]) {
[_userDelegate scrollViewDidEndDecelerating:scrollView];
}
}

And we need to handle all of the other UIScrollViewDelegate methods that we don't have custom code for, and all of those messages that will be added in future versions of iOS. We have to implement two methods to make that happen:

- (BOOL)respondsToSelector:(SEL)selector {
return [_userDelegate respondsToSelector:selector] || [super respondsToSelector:selector];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
// This should only ever be called from `UIScrollView`, after it has verified
// that `_userDelegate` responds to the selector by sending me
// `respondsToSelector:`. So I don't need to check again here.
[invocation invokeWithTarget:_userDelegate];
}

@end

Dynamically implementing a delegate during runtime

What you want is possible, but it's not method swizzling, since you don't want to switch to methods but add a new one. It can be done, thanks to Objective-C's dynamic nature, but it's still a dirty hack so also file a feature request with the library vendor.

What you want is class_addMethod() and a C function with the actual implementation for that. One more thing, Objective-C methods are C methods, but with two implicit parameters, self and _cmd, which have to keep in mind (both when creating your C method and when telling class_addMethod your methods signature. And here is an SSCE of how to pull something like that off:

#import <Foundation/Foundation.h>
#import <objc/runtime.h> // Required for class_addMethod()

@interface MyClass : NSObject
@end

@implementation MyClass
@end

@protocol MyProtocol <NSObject>
- (void)printString:(NSString *)string;
@end

// Note the method signature containing the
// two implicit parameters self and _cmd!
void MyClassPrinStringIMP(id self, SEL _cmd, NSString *string)
{
NSLog(@"Hi I'm %@:%s and this is the string: %@", self, sel_getName(_cmd), string);
}

void PimpMyClass()
{
// The last argument is the signature. First character is the return type, in our case void
// Then comes self and _cmd, followed by the NSString. You can use @encode() to find out how your
// type is encoded. Best is to build this string at runtime, since the encoding can change with architectures
class_addMethod([MyClass class], @selector(printString:), (IMP)MyClassPrinStringIMP, "v@:@");
}

int main(int argc, const char * argv[])
{
@autoreleasepool
{
PimpMyClass();

id foo = [[MyClass alloc] init]; // id, to silence the compiler!
[foo printString:@"Hello World"];
}

return 0;
}

Example output:

Hi I'm <MyClass: 0x100101810>:printString: and this is the string: Hello World

Edit: Something that you may find is that the passed object is checked at runtime wether it conforms to a protocol or not using conformsToProtocol:. Since this code just adds the method implementation, it would still fail, but you can tell the runtime that you totally do implement that protocol with this one function call:

class_addProtocol([MyClass class], @protocol(MyProtocol));

Alternative: proxies

Objective-Cs dynamism and message forwarding is already praised by @JasperBlues, however, there is one particular class in Objective-C that is designed to do just that: NSProxy. It is designed to intercept sent messages and dispatching them dynamically to the relevant target, and does use the high-level NSInvocation approach. If you can pass a proxied object in some way as the delegate (depending on what your code allows for and what not), creating a NSProxy subclass might be the cleanest way to go.

However, note though that you then end up with a shim object that wraps over your other object, which comes with its own bag of pain and will break when you try to directly access variables via -> syntax. It's not a perfectly invisible proxy, but good enough for most cases.

Subclassed UIScrollView

Your subclass has a weak property - oldDelegate - and when the delegate is set (you detect this in a subclassed setDelegate method), you actually set oldDelegate instead, then make yourself the delegate.

Implement all the delegate methods and send each to "oldDelegate".

Override property with different type

The way it works for Objective C is because of the dynamic nature of the language. You can redeclare the type of super class and instead of synthesizing the property you would make it a dynamic type, which would let you redeclare it.

@protocol MyTableViewDelegate<UITableViewDelegate>

- (void)demoDelegateMethod;

@end

@interface WrapperTableView: UITableView

@property (nonatomic, weak, nullable) id <MyTableViewDelegate> delegate;

@end

@implementation WrapperTableView

@dynamic delegate;

@end

But, I doubt this would be possible with Swift, since, you are changing the type completely. It is because of Swift being strong static language.

Answer Edited

I got your approach. I write above approach in Objective-C and then inherit this class in Swift.
So if I have to override SuperClass property with different type I need to create a wrapper class in Objective-C inherit from desired SuperClass and then finally instead of inheriting directly from desired super class I should inherit from the newly created WrapperClass which is written in Objective-C

class MyTableView: WrapperTableView {
//Now Here the delegate object is of type MyTableViewDelegate
}

This approach is far better then

class MyTableView: UITableView {
private var myDelegate: MyTableViewDelegate?
override var delegate: UITableViewDelegate {
set{
myDelegate = newValue
}
get{
return myDelegate
}

}
}

Is there a less repetitive way to forward action messages to another object?

You can do what Gabriele suggested and it is certainly an example of how dynamic ObjC can be, but you are likely better off avoiding it. As Gabriele said, you'd better know exactly what you are doing and definitely not to overuse such feature. And that often indicates that such feature is likely more trouble than it is worth.

The reality is that your calculator application is a quite contrived for the purposes of driving home the separation inherent to the Model-View-Controller pattern. It is a learning app, as you state.

In reality, no application is ever that simple. You will rarely, if ever, have a field of buttons where that the control layer blindly forwards said functionality on to the model.

Instead, there will be all manners of business logic in that control layer that will may do everything from automating various actions to validation (potentially by querying the model) to updating UI state in response to various actions.

Likely this code will be present from very early in the project, thus that generic forwarding mechanism will quickly become completely unused.

As well, such forwarding mechanisms become funnels full of pain when it comes to debugging. you no longer have a concrete spot to drop a breakpoint, but now have to add conditions. Nor do you have an easy means of finding all the places that might invoke or implement a particular method. As well, it makes following the control flow more difficult.

If you do find yourself with lots of repetitive boiler-plate code, it is more of a sign that your architecture is likely flawed than a sign that you need to inject a spiffy dynamic mechanism to reduce the repetitiveness.

As well, if you were to continue to flesh out your calculator app, how much of your coding time would have been spent doing those repetitive methods vs. all other features in your app? Likely, very very little and, because of their simplicity and convenience to debugging, it is unlikely that said repetitive methods are ever going to incur any significant maintenance cost whereas a spiffy-dynamic bit of trickery (which is very cool and I encourage you to explore that in other contexts) is pretty much guaranteed to require a "Huh. What was I thinking here?!" moment later on.



Related Topics



Leave a reply



Submit