Banana
Banana

Reputation: 4228

Check if subclass overrides a method

Is it possible to check whether a subclass implements a method that exists either in its immediate superclass or in some superclass of its superclass, etc?

E.g. I subclass UIView, to make my custom generic view for my app, and then I subclass this custom view. Now some of my subclasses override some method from UIView and some don't. I only want to call this method if it is actually overridden, I do not want the default UIView method being called.

Is there a way to check this, i.e. with a method similar to respondsToSelector:?

Edit: This question is different from the one asked in Objective-C detect if class overrides inherited method because I do not really know or do not want to care which superclass originally implements the method.

Upvotes: 7

Views: 3356

Answers (7)

manmal
manmal

Reputation: 3938

Adapted for Swift 4.0 (and added an optional baseClass BELOW which the algorithm looks for overrides):

extension NSObject {

    func checkIf(overrides selector: Selector, baseClass: AnyClass? = nil) -> Bool {
        guard var objSuperClass = self.superclass else { return false }

        var isOverridden = false

        while objSuperClass != nil {
            isOverridden = object.method(for: selector) != objSuperClass?.instanceMethod(for: selector)
            if isOverridden {
                break
            }
            objSuperClass = objSuperClass?.superclass()
            if objSuperClass == baseClass {
                break
            }
        }

        return isOverridden
    }
}

Upvotes: 0

Ian Bytchek
Ian Bytchek

Reputation: 9085

You might ask Why?! šŸ¤¦ā€ā™‚ļøā€¦ but if one night you find yourself looking how to do this in Swift 4 ā€“ this is how:

import Foundation

extension NSObject
{
    func overrides(_ selector: Selector) -> Bool {
        var currentClass: AnyClass = type(of: self)
        let method: Method? = class_getInstanceMethod(currentClass, selector)

        while let superClass: AnyClass = class_getSuperclass(currentClass) {
            // Make sure we only check against non-nil returned instance methods.
            if class_getInstanceMethod(superClass, selector).map({ $0 != method}) ?? false { return true }
            currentClass = superClass
        }

        return false
    }
}

class Foo: NSObject { @objc func mtd() {} }
class Bar: Foo {}
class Baz: Bar { override func mtd() {} }
class Qux: Bar {}
class Fen: Baz {}

Foo().overrides(#selector(Foo.mtd)) // false
Bar().overrides(#selector(Bar.mtd)) // false
Baz().overrides(#selector(Baz.mtd)) // true
Qux().overrides(#selector(Qux.mtd)) // false
Fen().overrides(#selector(Fen.mtd)) // true

Upvotes: 1

uchuugaka
uchuugaka

Reputation: 12782

Using the runtime, you want to use the function Method

* class_copyMethodList ( Class cls, unsigned int *outCount );

This will give you a list to work with.

Upvotes: 0

Ricky
Ricky

Reputation: 3171

Expanding on ZeMoon's answer, I found that there wasn't enough logic to satisfy all cases. I needed to add a check to ensure that instances of the next superclass in the loop implement the specified selector at all. Subclasses of UIView are one example.

while (objSuperClass != Nil) {
    isMethodOverridden = ([objSuperClass instancesRespondToSelector:selector]) && 
                         ([object methodForSelector:selector] !=
                          [objSuperClass instanceMethodForSelector:selector]);

    if (isMethodOverridden) {

    }
}

Upvotes: 1

ZeMoon
ZeMoon

Reputation: 20284

Based on this answer by Mert Buran.

You can create a simple method to check whether a given object overrides a given selector (method):

-(BOOL)checkIfObject:(id)object overridesSelector:(SEL)selector {

    Class objSuperClass = [object superclass];
    BOOL isMethodOverridden = NO;

    while (objSuperClass != Nil) {

        isMethodOverridden = [object methodForSelector: selector] !=
        [objSuperClass instanceMethodForSelector: selector];

        if (isMethodOverridden) {
            break;
        }

        objSuperClass = [objSuperClass superclass];
    }

    return isMethodOverridden;
}

This can be called as follows:

[self checkIfObject:someObject overridesSelector:@selector(someSelector)];

Upvotes: 10

giorashc
giorashc

Reputation: 13713

Not the optimal solution but you can keep a boolean member to your UIView subclass (let's call it UIViewSub indicating if the desired behavior is implemented by the class and check that boolean before using the method.

Your subclasses (those inheriting UIViewSub) will set/unset this member accordingly in its construction (i.e if it does override it with implementation or not)

Upvotes: 1

nburk
nburk

Reputation: 22751

Unfortunately, there is no equivalent to respondsToSelector: that will do precisely this job for you. As you will probably know, respondsToSelector: works in the way that it will return YES as long as the class itself or any of its superclasses implements this method.

But why not just put an empty implementation of the method into your custom subclass, that way you make sure that calling it doesn't have any effect and doesn't call the same method in the superclass. Did you think about this?

Update: While there is no method equivalent to respondsToSelector:, you might want to take a look at the Objective-C Runtime. It's a library that allows you to inspect characteristics of a class during runtime (a bit similar to Java reflections). Check out the reference here.

Upvotes: 2

Related Questions