views:

385

answers:

3

I have a view controller class that has to implement several protocols. Too keep things neat I have a habit of putting each protocol's methods in a category on the view controller class.

This time I am getting warnings from the linker that the class does not implement one of the protocols. The methods do work at runtime, the linker just can't seem to recognize the implementation in the category.

I simplified the class in a different project and I get the same error in the same place.

The class header:

#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
#import <AddressBookUI/AddressBookUI.h>

@interface TopVC : UIViewController 
<
    UINavigationControllerDelegate,
    ABPeoplePickerNavigationControllerDelegate  
>
{}
@end

The TopVC.m (not shown) is the automatically generated one with no changes. The UINavigationControllerDelegate protocol methods are implemented in this category:

#import <Foundation/Foundation.h>
#import "TopVC.h"

@interface TopVC (UINavigationControllerDelegate)
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;    
@end

#import "TopVC+UINavigationControllerDelegate.h"

@implementation TopVC (UINavigationControllerDelegate)
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
    NSLog(@"navigationController:willShowViewController:animated:");
}

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
    NSLog(@"navigationController:didShowViewController:animated:");
}
@end

The linker does not complain about this methods in this category. However, if I try a category to implement the ABPeoplePickerNavigationControllerDelegate protocol in an identical fashion it complains:

#import "TopVC.h"

@interface TopVC (ABPeoplePickerNavigationControllerDelegate)

- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker;

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person;

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier;

@end

#import "TopVC+ABPeoplePickerNavigationControllerDelegate.h"


@implementation TopVC (ABPeoplePickerNavigationControllerDelegate)

- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker{

}

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person{
    return YES;
}

- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier{
    return YES;
}
@end

The linker complains:

warning: incomplete implementation of class 'TopVC'
warning: method definition for '-peoplePickerNavigationController:shouldContinueAfterSelectingPerson:property:identifier:' not found
warning: method definition for '-peoplePickerNavigationController:shouldContinueAfterSelectingPerson:' not found
warning: method definition for '-peoplePickerNavigationControllerDidCancel:' not found
warning: class 'TopVC' does not fully implement the 'ABPeoplePickerNavigationControllerDelegate' protocol

The only difference I can see is that the UINavigationControllerDelegate protocol methods are all optional whereas the ABPeoplePickerNavigationControllerDelegate are all required.

Yet, even though the linker complains, the methods are still called at runtime. I just refuse to have a build with warnings in it. I've apparently missed something or made a trivial error somewhere but I can't spot it.

+2  A: 

By declaring the protocols, you intend to implement, on the primary interface, the compiler expects to see the methods in the primary implementation (about which you state "... [it] is the automatically generated one with no changes"). The compiler doesn't know, that you are going to implement the methods in a category. All it knows is: you "promised" they would be there, but you failed to provide them where it did expect them.

Dirk
A: 

When the compiler is compiling TopVC.m, it has no way to know that some other file is going to provide the rest of the required protocol methods. In gcc, every .m file is compiled independently. Your desire to split up this stuff into separate categories is fine, but all of them need to be implemented in TopVC.m.

Your re-definition of the protocol methods in TopVC+ABPeoplePickerNavigationControllerDelegate.h has no effect on anything. There is no relationship between a particular category tag in an @interface and one in an @implementation. A category definition in an @interface just says "here are some other methods that are legal to pass to this object without generating a compiler warning." It's not even an explicit promise to create such methods. That was already handled when you indicated that you conform to the protocol (which is a promise to implement the methods).

A category definition in an @implementation just says "here are some other methods for this class." The category tag itself is nothing more than a comment. It is not correlated in any way.

You need to move the protocol implementations into TopVC.m, and there is no reason to have a TopVC+<protocol>.h file. If you like to break up your .m into separate @implementation blocks, that's fine, and I know folks who do it that way. Personally, I just use #pragma mark to break up the file.

Rob Napier
+1  A: 

Hah! I was having a brain blank.

I forgot to move the protocol implementation declaration to the category's interface like so:

#import "TopVC.h"

@interface TopVC (ABPeoplePickerNavigationControllerDelegateMethods) <ABPeoplePickerNavigationControllerDelegate>
...
@end

This compiles without warning and works as expected.

I just got lazy when using so many optional protocols which the linker ignores if it can't find the method implementations.

TechZen