Search code examples
cocoaprotocolscategoriesnsoutlineview

How to extend a Cocoa protocol in a category to avoid "not found in protocol(s)" warning?


This seems like a simple thing, but my brain doesn't seem to be working today, and my searches haven't turned up a helpful answer.

I have lots of code that extends Cocoa classes via categories (it's open source, too). Some methods want to call the delegate; the old code used informal protocols to do this, but now when building targeting 10.6, I get the warning:

warning: '-outlineView:menuForTableColumn:byItem:' not found in protocol(s)

As an example, here's a category:

@interface NSOutlineView (DSOutlineViewCategories)

- (NSMenu *)menuForEvent:(NSEvent *)event;

@end

Which used an informal protocol to declare a delegate method:

@interface NSObject (DSTableViewDelegate)

- (NSMenu *)outlineView:(NSOutlineView *)olv menuForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;

@end

And the implementation calls that on the delegate:

@implementation NSOutlineView (DSOutlineViewCategories)

- (NSMenu *)menuForEvent:(NSEvent *)event
{
    NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
    NSInteger column = [self columnAtPoint:point];
    NSInteger row = [self rowAtPoint:point];
    id item = [self itemAtRow:row];

    if (column >= 0 && item && [[self delegate] respondsToSelector:@selector(outlineView:menuForTableColumn:byItem:)])
        return [[self delegate] outlineView:self menuForTableColumn:[[self tableColumns] objectAtIndex:column] byItem:item];
    else
        return [super menuForEvent:event];
}

@end

How can I update this code for 10.6 (and beyond), to avoid the "not found in protocol(s)" warning?


Solution

  • I think this is because the NSOutlineView delegate is now typed as id <NSOutlineViewDelegate> rather than a plain id as it was in the 10.5 SDK. The category is declared on NSObject, but the compiler doesn't see the delegate object as inheriting from NSObject, so it doesn't recognize that it would respond to the message. Before, since the delegate was a plain id, it wouldn't complain about any message sent to it, as long as it could find the declaration somewhere.

    The quick and dirty fix would be to just add a cast, making the code [(id)[self delegate] outlineView:self menuForTableColumn:[[self tableColumns] objectAtIndex:column] byItem:item];

    To be a little more formal, you could declare your own formal delegate protocol which inherits from NSOutlineViewDelegate, which would look like

    @protocol DSOutlineViewDelegate <NSOutlineViewDelegate>
    
    @optional
    - (NSMenu *)outlineView:(NSOutlineView *)olv menuForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
    
    @end
    

    Then, in the code that calls it, you would want to be calling the method on an object with type id <DSOutlineViewDelegate>. You can do this by declaring a new method that does the casting for you, like:

    - (id <DSOutlineViewDelegate>)ds_delegate
    {
        return (id <DSOutlineViewDelegate>)[self delegate];
    }
    

    Then, in the actual code, you'd call:

    [[self ds_delegate] outlineView:self menuForTableColumn:[[self tableColumns] objectAtIndex:column] byItem:item];
    

    and the compiler should be OK with that. Since the method is declared as optional in the protocol, you still want to check at runtime whether the delegate actually responds to the selector.