4

In a subclasses interface, I declare a method, implemented by the superclass, as returning a different object type, since the subclass is specialized. However, there is no need for me to actually implement the method in the subclass - the superclass's implementation works fine. Unfortunately, this pattern now seems to give me the Method definition for … not found error on recent versions of Xcode.

I am aware that I can turn off all such warnings by disabling -Wincomplete-implementation with a diagnostic push and pop around the @implementation, but I'd rather not take such an extreme approach, as the warning is useful in other contexts. (If this can mark individual methods to be silenced, I'm all ears)

Here is one of several example implementations that exhibit this problem:

Test.h

@interface GeneralItem : NSObject

@end

@interface GeneralGroup : NSObject

- (GeneralItem *)item;

@end


@interface ItemA : GeneralItem

@end

@interface GroupA : GeneralGroup

- (ItemA *)item;

@end

Test.m

#import "Test.h"

@implementation GeneralGroup

- (GeneralItem *)item
{
    return nil;
}

@end

@implementation GroupA
// Warning: Method definition for 'item' not found

@end

To be clear, everything compiles and works fine, and to my understanding, the compiler should know that the superclass implements this method, not to mention Objective-C is not too picky about type-overloading in method definitions.

I'd like to avoid implementing the method and simply calling super, as I see it as completely redundant code, but if someone can confirm to me that the compiler will optimize away a direct call to super so the runtime can do its usual thing without needing to make a pitstop to the subclass, and that's the only way to silence this warning, then so be it.


Edit 1

Here are a few other scenarios where this comes up.

The first is a scenario where a common base class (or abstract superclass) implements a shared implementation of something with, say, blocks, but we only want some subclasses to selectively declare their support for it, with types properly annotated:

BlockTest.h

@interface GeneralItem : NSObject

@end

@interface GeneralGroup : NSObject

@end


@interface ItemA : GeneralItem

@end

@interface GroupA : GeneralGroup

- (void)loadItemWithCompletionHandler:(void (^)(ItemA *item))completionHandler;

@end


@interface ItemB : GeneralItem

@end

@interface GroupB : GeneralGroup

- (void)loadItemWithCompletionHandler:(void (^)(ItemB *item))completionHandler;

@end

BlockTest.m

#import "BlockTest.h"

@interface GeneralGroup ()

- (id)item;
- (void)loadItemWithCompletionHandler:(void (^)(Item *item))completionHandler;

@end

@implementation GeneralGroup

- (void)loadItemWithCompletionHandler:(void (^)(Item *item))completionHandler;
{
    if (completionHandler) completionHandler(self.item);
}

@end

@implementation GroupA
// Warning: Method definition for 'loadItemWithCompletionHandler:' not found
// A custom implementation of -item can be here, for instance

@end

I should note, you get no warning if you declare the method as - (void)loadItemWithCompletionHandler:(void (^)(Item *item))completionHandler; in the public header in GroupA, but you do get the "method definition not found" warning when you change the type to be a subclass of the originally-specified type.


The second is where you simply want to give more context to a superclass-provided method (using NSObject's copy here, but this applies to any method typed as id that we would like to re-declare with more accurate types, so things like property-chaining work a bit less painlessly):

Subclass.h

@interface Subclass : NSObject

- (instancetype)copy;

@end

Subclass.m

#import "Subclass.h"

@implementation Subclass
// Warning: Method definition for 'copy' not found

@end
arnt
  • 8,949
  • 5
  • 24
  • 32
Dimitri Bouniol
  • 735
  • 11
  • 15

2 Answers2

2

This seems like an opportunity to somehow indicate that -[GeneralGroup item] may return an instance of something that is a subclass of GeneralItem.

The quickest way to do this is to use the __kindof annotation on -[GeneralGroup item], indicating that the return value is a GeneralItem or any subclass:

@interface GeneralGroup : NSObject
- (__kindof GeneralItem *)item;
@end

@interface GroupA : GeneralGroup
@end

Since you no longer redeclare -item on GroupA, you don't get a warning in its implementation — but you're still free to override -item if you want, returning an instance of ItemA.

If you do want to get a little more specific, you can consider using lightweight generics:

@interface GeneralGroup<__covariant ItemType: GeneralItem*> : NSObject
- (ItemType)item;
@end

This is tantamount to the same thing to the compiler, but expresses a little more information to other developers reading the header: they know that -item returns a GeneralItem or a subclass, and — because of the presence of the __covariant attribute — that subclasses of GeneralGroup may require a more specific associated item type than GeneralItem.

Callers can then freely type the returned value of -[GroupA item] as ItemA:

GroupA *ga = [[GroupA alloc] init];
ItemA *ia = ga.item; // no warning

With further refactoring, you may find that you don't need specific group types for each item; instead, often a general grouping implementation can suffice for multiple item types, with a specific item type declared on local instances of the group. (This is how collections like NSArray work: they declare a generic contained object type, and specific array instances are annotated as containing a particular object at the point they're created or passed around.)

Tim
  • 59,527
  • 19
  • 156
  • 165
  • This was unfortunately the easiest-to-simplify example that shows this problem in detail — the goal is to provide my subclasses with correctly typed headers, so users of the API know exactly what to expect. This unfortunately does not help much in the case where the superclass is out of my control and implements a method using `id` (to which I want to be more specific), or declares the method in a private header that the implementation has access to, but the public header does not. – Dimitri Bouniol May 15 '18 at 17:35
0

You can silence warnings temporarily like so:

Before the line that causes the warning:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wthe-error-to-ignore"

And after the line:

// this resets clang's diagnostic system to the state it was in before we called "push"
#pragma clang diagnostic pop
Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
  • As I stated, I'm aware that I can disable warnings around the implementation, but that silences **all** "method not found" warnings for the class, not just the ones for the methods I'd like it to ignore. I've clarified my question to make this more clear. – Dimitri Bouniol May 15 '18 at 17:11
  • @DimitriBouniol Do you have access to the superclass's code? You could use Objective-C's Lightweight Generics feature to declare an item type for the superclass, and have the method return that instead of `GeneralItem` explicitly. Then, you could declare your subclass with its item as a parameterized type. – Charles Srstka May 15 '18 at 17:24
  • Not always, unfortunately. Using generics for something like this also requires the API user to always type their variables with the correct generics, or it requires my API to always return the right generic override for a given subclass (which I'm not quite sure how to do correctly). – Dimitri Bouniol May 15 '18 at 17:50