how to block a superclass method to be called to a subclass - objective-c

I'm extending the functionality of a class with a subclass, and I'm doing some dirty stuff that make superclass methods dangerous (app will hang in a loop) in the context of the subclass. I know it's not a genius idea, but I'm going for the low-hanging fruit, right now it's gonna save me some time. Oh it's a dirty job, but someone's gotta do it.
Bottom line, I need to either block that method from outside, or throw an exception when it's called directly to the superclass. (But I still use it from the subclass, except with care).
What would be the best way to do this?
UPDATE ---
So this is what I went for. I'm not self-answering, as Boaz' answer mentions multiple valid ways to do this, this is just the way that suited me. In the subclass, I overrode the method like this:
- (int)dangerousMethod
{
[NSException raise:#"Danger!" format:#"Do not call send this method directly to this subclass"];
return nil;
}
I'm marking this as answered, but evidently that doesn't mean it's closed, further suggestions are welcome.

Just re-implement the unsafe method in your subclass and have it do nothing or throw an exception or re-implement it as safe, just as long as the new implementation doesn't call the unsafe superclass method.
For the C++ crew in here: Objective C doesn't let you mark methods as private. You can use its category system to split up the interface into separate files (thus hiding 'private' ones), but all methods on a class are public.

You can override whichever methods you want to block in your subclass's .h file. You can make dangerousMethod unavailable by placing the following in your .h file.
- (int)dangerousMethod __attribute__((unavailable("message")));
This will make the dangerousMethod method unavailable to anyone using your subclass.
To keep other code from using the superclass's version this method, you can further restrict by putting this in your .m file.
- (int)dangerousMethod
{
return nil;
}

note: I'm ObjC/Cocoa newcomer:
#implementation MyClass
-(void)myMethod:(NSString *)txt{
if([self class] != [MyClass class]) return;
NSLog(#"Hello");
}
#end
Peter

This article explains how to create private variables in Objective C. They aren't truly private, but from what I read, the compiler will throw a warning if you try to call them from the subclass.

If you create the methods in your superclass as "private" then the subclass has no possible way of calling them. I'm not familiar with Objective C, but every other object oriented language I've seen has the "private" qualifier.

Related

Removing an action from a subclass

My NSDocument subclass implements selectAll:. Only problem is, I'm using NSTableView, and it also implements selectAll:. However, the selectAll: action in NSTableView doesn't do what I want, and it does prevent the selectAll: method in my Document class from ever being reached in the responder chain.
I already have a subclass of NSTableView, and after poking around a bit I got things working the way I want by adding a respondsToSelector: method to my NSTableView subclass which lies to the runtime by telling it there is no selectAll: action:
-(BOOL)respondsToSelector:(SEL)targetSelector
{
if (targetSelector == #selector(selectAll:)) {
return FALSE; // we don't want tableView's implementation of selectAll
}
return [super respondsToSelector:targetSelector];
}
This seems to work fine, allowing the selectAll: method in my document subclass to do its thing. But this solution leaves me a bit uneasy. What about other action methods I have implemented in this subclass? Do I need to manually check and return true for each of them? I do have two actions defined in this subclass, moveLeft: and moveRight:, and they seem to work, even though I am not handling them in respondsToSelector:. So my question is, am I doing this correctly, or is there something I am missing? Or perhaps there is some entirely different way to do this properly?
By the way, I got the idea of overriding respondsToSelector from this post on the OmniGroup forum:
http://mac-os-x.10953.n7.nabble.com/Removing-an-action-from-a-subclass-td27045.html
Sending a message to super affects which implementation of that method we use. It doesn't change who self is.
So let's try to imagine how respondsToSelector: works. Given a selector mySelector, it probably introspects every class up the superclass chain, starting with [self class], to see whether it actually implements mySelector.
Now then, let's say your subclass is called MyTableView. When MyTableView says
[super respondsToSelector:targetSelector]
what happens? The runtime will look up the superclass chain for another implementation of respondsToSelector:, and eventually will find NSObject's original implementation. What does that implementation do? Well, we just answered that: it starts the search for an implementation of targetSelector in [self class]. That's still the MyTableView class! So if you have defined moveLeft: in MyTableView, respondsToSelector: will find it and will return YES for moveLeft:, exactly as you hope and expect.
Thus, to generalize, the only selector for which this search has been perverted is the search for selectAll: - exactly as you hope and expect. So I think you can relax and believe that what you're doing is not only acceptable and workable but the normal solution to the problem you originally posed.
You might also like to look at the Message Forwarding chapter of Apple's Objective-C Runtime Programming Guide.

overriding undeclared methods in subclass

I have a class with some methods neither declared in .h file nor in any category, I want to override these methods in the subclass to change the behavior slightly. I am thinking of simply redefining those methods in subclass. Is it a good idea? would it even work?
Ideally the subclass needs to know what the methods are if it wants to override the method and still call super.
One way of doing this is by having a separate header file which both the super class and subclass implementations both import.
// MyClass_protected.h
- (void)someMethodThatYouWantSubclassesToBeAbleToOverride;
Then
// MyClass.m
#import "MyClass_protected.h"
// MySubClass.m
#import "MyClass_protected.h"
It'll 'work' in that the compiler allows it. The class that defines these methods probably assumes they do particular things when called, which your implementation needs to respect when overriding them to avoid introducing bugs in the use of the class's interface.
You can override private methods in a base class but the problem is that you can't call [super someMethod]. If you wish to completely replace the original method then this isn't an issue.
Otherwise you need to let the derived class know about the methods in the parent class.

In ObjC, how do I hide implementation a superclass's methods in a subclass?

In ObjC, how do I hide implementation a superclass's methods in a subclass?
I'm not sure if #private would do the trick, as it appears to only apply to ivars.
You're right, the #private directive is for instance variables, not methods. To hide a method's implementation, simply omit its declaration from the header file. To suppress warnings, you can use use a category or class extension to declare the method in the .m file.
There's no built-in language feature to prevent a subclass from seeing the method, though. Why would you want to do that?
There are no actually "private" methods in Obj-C; since any message can be sent to any object at runtime, there's no way to prevent someone from sending the message you care about.
That said, you can intercept that message in the subclass and not handle it. The simplest way to make a superclass's method inaccessible is to override it in the subclass and do nothing:
- (void)someMethodIDontWantToSupport
{
}
MARK as in subclass interface file.
- (void)someMethodIDontWantToSupport __unavailable;

Objective-C Removing Inherited Methods from a class

For a subclass, is there way I can remove/stop methods from super class implementation?
(ie. nsstring has a method length. i want to stop "mystring" class from attaining the length method).
length is a primitive method in the NSString class cluster; pretty much every other method in NSString is implemented using calls to length.
Instead of going down this path of madness, make your class a subclass of NSObject. Without a length method, it isn't really a valid NSString subclass anyway.
You can override the method to call doesNotRecognizeSelector::
- (NSUInteger)length
{
[self doesNotRecognizeSelector:_cmd];
}
But if you have to do this, you may want to rethink whether you really want to subclass in the first place. It may be better to use composition instead of inheritance if you only want to "inherit" specific behaviors/methods.
You override respondsToSelector: to return NO for the appropriate selectors and override the method to call doesNotRecognizeSelector:.
The better answer is "don't do that". Don't try removing methods, instead put an NSAssert(NO, #"don't call me") in your class's implementation, and never call it yourself.
If you must make a "thing that is like X but doesn't do Y", make a new class, and delegate behavior to an instance of the real X.
Can't you simply override the method ?
You can use doesNotRecognizeSelector:. From the documentation:
- (id)copy
{
[self doesNotRecognizeSelector:_cmd];
}
However, there's a good chance that this is the Wrong Thing To Do. The interface for a class is a contract; it states that an instance that is a kind of that class (which includes subclass instances) will accept that message. When your MyString is passed as an NSString and it doesn't accept the length message, it's violating the contract and will likely cause the program to crash, since no one would think to handle an NSInvalidArgumentException from invoking length on an NSString.

Intercept method call in Objective-C

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.