I know that I can extend a class (for example a framework class) using categories, but is it possible to have a class for which you do not control the source code implement one of your custom protocols? I not only want to have it respond to certain messages if sent to an instance, but also, ideally, want objects of that class to return true on runtime type checks when querying for the protocol.
You can define a category that conforms to the protocol, so you'd do something like:
#interface UIWebView (MyGreatExtensions) <UITableViewDelegate>
#end
#implementation UIWebView (MyGreatExtensions)
- (CGFloat)tableView: (UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath *)indexPath {
return 42.0;
}
// ...
#end
A small edge case to consider is that if someone else tries to do the same thing (e.g. a third-party framework also adds the protocol via a category) then you can't guarantee that your version will be used (and of course neither can they).
More on this approach from Mark Dalrymple.
I think this is possible. You can easily create dynamic proxies in Objective-C (this is how NSUndoManager does its magic) by overwriting NSObject's forwardInvocation: method, intercepting all non-recognized messages that an implementor of the protocol would respond to. I have never done this myself, so there might be a more elegant way to do this.
In addition, to fool the runtime checks into agreeing that your object does implement the protocol, you could override NSObject's class method conformsToProtocol: like so:
+ (BOOL)conformsToProtocol:(Protocol *)aProtocol {
if (aProtocol == #protocol(MyDynamicallyImplementedProtocol))
return YES;
return [super conformsToProtocol:aProtocol];
}
There might be more methods that you need to override, one example would be NSObject's instancesRespondToSelector: and the resolve*: methods. Examine the NSObject class reference to find out more.
Related
In a category I got from a book, there is a protocol specified. It requires one method and declares that method in the category as well.
If I implement the protocol in another object (a table view cell), my understanding is that I MUST include the required method. However, other than the title of the method being the same as the one in the category, do I inherit any of the code from the category version? That code is meaningful because only after that code completes do I want to do something.
category: .m
#protocol DownloadHelperDelegate <NSObject>
-(void)didCompleteDownloadForURL:(NSString *)url withData:(NSMutableData *)data;
#end
- (void)didCompleteDownloadForURL:(NSString *)url withData:(NSMutableData *)data{
// doThis
// doThat
// readyToDoSomethingElsewhere
}
In tableviewCell: .h
#import "category.h";
#protocol category
.m
-(void)didCompleteDownloadForURL:url withData:data;
{
// Did doThis happen?
// Did doThat happen?
// If so, I want to do Something
}
Since the category is a category on UIImageView, it only adds methods to that class.
Since your class is derived from UITableViewCell and that does not derive, directly or indirectly, from UIImageView, it does not inherit an implementation of -didCompleteDownloadForURL:withData: from the category.
I'm guessing the category's method would not be an appropriate implementation of the protocol method, anyway. It has the same signature, but I suspect it has a different purpose. In particular, I suspect the category method's purpose is, at least in part, to forward the call along to a delegate which implements the protocol. So, it wouldn't make sense for the delegate itself to inherit that implementation.
It's just a coincidence that the category method and the protocol method have the same signature. In fact, I would suggest that the category method be changed to start with a unique prefix so that there's no chance that it collides with a method on UIImageView, which might be private to Apple or added in a future version of UIKit.
I have three different classes, one of them is parsing xml from a certain website and the two other will be recieving the information from the class that is running the NSXMLParserDelegate protocol methods. My question is, how do i tell the class to run the protocol methods from another class? Or run every instance method or the whole class or something like that.
Any suggestions?
Edit: I'm going to parse xml information from a website when some certain view is active. To do this, i'm going to have a class that i'm going to send a message to, and tell it to run its methods from the xml parser protocol and send the value it recieves to the view that is present.
There are two ways of seeing it.
An object (A) having a pointer to the delegate (B) (the delegate is the object that implements the methods of a protocol) can call the methods of the protocol by just invoking them.
Form the delegate's (B) point of view, you don't call the protocol's methods, you IMPLEMENT them, and some other object (A) will call them whenever it needs to inform you of some event, or to request some information. That's what protocols are designed for.
Object (A) somewhere it declares the delegate
id <someKindOfDelegate> delegate;
and whenever it want's, it calls the protocol's methods
if (self.delegate)
[self.delegate someMethod]
(B) must declare itself as an implementor of the protocol
#interface ObjectB <someKindOfDelegate>
then (B) sets itself as the delegate of an instance of (A)
ObjectA *object = [[ObjectA alloc] init];
object.delegate = self;
and finally (B) implements the protocol's methods
- (void)someMethod {
// do something... I've been called!
}
I understand that UI controls such as UITextField notify of client interactions / events via their delegate, which is defined as a class that supports the required protocol.
I have often found myself wanting to receive notifications of UI event in more than one class, so would want to support multicasting. For example, specifying more than one delegate for a UI control. I am pretty sure that there is no iOS framework feature that supports this. I was wondering if anyone had come up with a decent solution to this problem?
There is a cocoa feature that lets you build multicast delegates with relative ease - it's the Message Forwarding system built into the framework.
Make a class that overrides forwardInvocation:, and return an instance of your object instead of a delegate. This is what is sometimes called a "Trampoline object". The logic inside your forwardInvocation: implementation can now decide which "real" objects should receive the message, and forward the invocation to one or more of them.
As an update to this accepted answer, I created my own multicasting delegate implementation here:
http://www.scottlogic.co.uk/blog/colin/2012/11/a-multicast-delegate-pattern-for-ios-controls/
You could implement your UI control in your class, and then your class will receive notification from this UI, you can send message to another class (using the same delegate technic - implement delegate property in first class, and then in second class implement delegate method from 1-st class).
For example, for UITextField method -textFieldDidEndEditing:
In first class -
1) implement protocol:
#protocol TextControllerDelegate <NSObject>
#optional // Delegate protocols
- (void)textFieldDidEndEditing:(UITextField *)textField;
#end
2) #property (nonatomic, unsafe_unretained, readwrite) id <TextControllerDelegate> delegate;
3)in method - (void)textFieldDidEndEditing:(UITextField *)textField inside the class, call [delegate textFieldDidEndEditing:textField]
In second class:
1) implement object of first class, set delegate to self (to second class).
2) implement method - (void)textFieldDidEndEditing:(UITextField *)textField
One technique to support multicasting is to give your delegating class the following methods:
#interface Delegator : NSObject
- (void)addDelegate:(id<MyProtocol>)delegate;
- (void)removeDelegate:(id<MyProtocol>)delegate;
#end
And store the reference to the delegates in an NSHashTable.
See the implementation here:
http://arielelkin.github.io/articles/objective-c-multicast-delegate/
I've created my own Delegate for a ObjC class. The class itself deals with Core Data operations. Delegate methods are used to inform other classes about changes that happened to the datastore. The class that deals with the datastore is called Datastore and it's delegate is called DatastoreDelegate. A UIViewController of mine (ContactsViewController) implements the delegate.
My DatastoreDelegate is declared as followed:
#class Datastore;
#protocol DatastoreDelegate <NSObject>;
#optional
- (void)didAddMessage:(Message *)message;
- (void)didUpdateContact:(Contact *)contact;
- (void)didAddContact:(Contact *)contact;
- (void)didUpdateContact:(Contact *)contact;
- (void)didDeleteContacts;
#end
The weird thing is, my code all worked fine with these methods except for the [didAddMessage:] method. Whenever I try to call this delegate from within the Datastore class I get an error from the ContactsViewController. The error tells me the [didAddMessage:] selector is missing in the ContactsViewController instance (Unrecognized selector sent to instance). How can the selector be missing if it's optional?
I should note that my Datastore class is a Singleton. I'm not sure if this is somehow related to this issue I'm having.
"Optional" means the caller is responsible for checking that a target responds to a given selector. E.g.:
if ([_myDelegate respondsToSelector:#selector(didAddMessage:)])
{
[_myDelegate didAddMessage:theMessage];
}
Did you implement didAddMessage: in your ContactsViewController? It's optional so you aren't forced to implement it, but if you send the didAddMessage: message to ContactsViewController but haven't actually implemented it in ContactsViewController, you'll still get a compiler warning. In other words, #optional just means you don't have to implement it, but the compiler may still give a warning if you haven't implemented it but try to use it.
What you might want to do in Datastore is this:
if ([delegate respondsToSelector:#selector(didAddMessage:)]) {
[delegate didAddMessage:theMessage];
}
rather than just:
[delegate didAddMessage:theMessage];
(You'll still get a compiler warning in the first example, but it's safe to ignore since you're checking at runtime to see if the appropriate method is implemented in the delegate.)
Can I intercept a method call in Objective-C? How?
Edit:
Mark Powell's answer gave me a partial solution, the -forwardInvocation method.
But the documentation states that -forwardInvocation is only called when an object is sent a message for which it has no corresponding method. I'd like a method to be called under all circumstances, even if the receiver does have that selector.
You do it by swizzling the method call. Assuming you want to grab all releases to NSTableView:
static IMP gOriginalRelease = nil;
static void newNSTableViewRelease(id self, SEL releaseSelector, ...) {
NSLog(#"Release called on an NSTableView");
gOriginalRelease(self, releaseSelector);
}
//Then somewhere do this:
gOriginalRelease = class_replaceMethod([NSTableView class], #selector(release), newNSTableViewRelease, "v#:");
You can get more details in the Objective C runtime documentation.
Intercepting method calls in Objective-C (asuming it is an Objective-C, not a C call) is done with a technique called method swizzling.
You can find an introduction on how to implement that here. For an example how method swizzling is implemented in a real project check out OCMock (an Isolation Framework for Objective-C).
Sending a message in Objective-C is translated into a call of the function objc_msgSend(receiver, selector, arguments) or one of its variants objc_msgSendSuper, objc_msgSend_stret, objc_msgSendSuper_stret.
If it was possible to change the implementation of these functions, we could intercept any message. Unfortunately, objc_msgSend is part of the Objective-C runtime and cannot be overridden.
By googling I found a paper on Google Books: A Reflective Architecture for Process Control Applications by Charlotte Pii Lunau. The paper introduces a hack by redirecting an object's isa class pointer to an instance of a custom MetaObject class. Messages that were intended for the modified object are thus sent to the MetaObject instance. Since the MetaObject class has no methods of its own, it can then respond to the forward invocation by forwarding the message to the modified object.
The paper does not include the interesting bits of the source code and I have no idea if such an approach would have side effects in Cocoa. But it might be interesting to try.
If you want to log message sends from your application code, the -forwardingTargetForSelector: tip is part of the solution.
Wrap your object:
#interface Interceptor : NSObject
#property (nonatomic, retain) id interceptedTarget;
#end
#implementation Interceptor
#synthesize interceptedTarget=_interceptedTarget;
- (void)dealloc {
[_interceptedTarget release];
[super dealloc];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(#"Intercepting %#", NSStringFromSelector(aSelector));
return self.interceptedTarget;
}
#end
Now do something like this:
Interceptor *i = [[[Interceptor alloc] init] autorelease];
NSFetchedResultsController *controller = [self setupFetchedResultsController];
i.interceptedTarget = controller;
controller = (NSFetchedResultsController *)i;
and you will have a log of message sends. Note, sends sent from within the intercepted object won't be intercepted, as they will be sent using the original object 'self' pointer.
If you only want to log messages called from the outside (usually called from delegates; to see which kind of messages, when, etc.), you can override respondsToSelector like this:
- (BOOL)respondsToSelector:(SEL)aSelector {
NSLog(#"respondsToSelector called for '%#'", NSStringFromSelector(aSelector));
// look up, if a method is implemented
if([[self class] instancesRespondToSelector:aSelector]) return YES;
return NO;
}
Create a subclass of NSProxy and implement -forwardInvocation: and -methodSignatureForSelector: (or -forwardingTargetForSelector:, if you're simply directing it on to a second object instead of fiddling with the method yourself).
NSProxy is a class designed for implementing -forwardInvocation: on. It has a few methods, but mostly you don't want them to be caught. For example, catching the reference counting methods would prevent the proxy from being deallocated except under garbage collection. But if there are specific methods on NSProxy that you absolutely need to forward, you can override that method specifically and call -forwardInvocation: manually. Do note that simply because a method is listed under the NSProxy documentation does not mean that NSProxy implements it, merely that it is expected that all proxied objects have it.
If this won't work for you, provide additional details about your situation.
Perhaps you want NSObject's -forwardInvocation method. This allows you to capture a message, retarget it and then resend it.
You can swizzle the method call with one of your own, which does whatever you want to do on "interception" and calls through to the original implementation. Swizzling is done with class_replaceMethod().
A method call, no. A message send, yes, but you're going to have to be a lot more descriptive if you want a good answer as to how.
To do something when a method is called, you could try an events based approach. So when the method is called, it broadcasts an event, which is picked up by any listeners. I'm not great with objective C, but I just figured out something similar using NSNotificationCenter in Cocoa.
But if by "intercept" you mean "stop", then maybe you need more logic to decide wether the method should be called at all.