Почему нереализованные необязательные методы протокола вызывают ошибки во время выполнения, когда этот метод вызывается в obj-c?

У меня есть два класса, которые могут выступать в качестве делегата третьего класса, и оба реализуют формальный протокол, сделанный полностью из необязательных методов. Один из классов реализует все, в то время как другой только реализует пару методов, о которых я забочусь. Тем не менее, во время выполнения, когда у меня есть второй класс, выступающий в качестве делегата третьего класса, а третий класс вызывает вызов одного из нереализованных необязательных методов для этого делегата, я получаю ошибку времени выполнения, в основном говоря: "Target не отвечает на это селектор сообщений". Я думал, что objective-c правильно обработал этот случай и что он просто ничего не сделает, если этот метод фактически не определен в классе. Может быть, что-то мне не хватает?

Ответ 1

Когда вы вызываете необязательный метод вашего делегата, вам нужно убедиться, что он отвечает на селектор, прежде чем вызывать его:

if ([delegate respondsToSelector:@selector(optionalMethod)])
    [delegate optionalMethod];

Ответ 2

Необязательные методы протокола просто означают, что объект, реализующий протокол, не должен реализовывать данный метод - тогда вызывающий должен обязательно проверить, реализует ли объект метод перед вызовом (в противном случае вы столкнетесь, как вы заметили). Эти категории NSObject HOM могут быть полезны:

@implementation NSObject (Extensions)

- (id)performSelectorIfResponds:(SEL)aSelector
{
    if ( [self respondsToSelector:aSelector] ) {
        return [self performSelector:aSelector];
    }
    return NULL;
}

- (id)performSelectorIfResponds:(SEL)aSelector withObject:(id)anObject
{
    if ( [self respondsToSelector:aSelector] ) {
        return [self performSelector:aSelector withObject:anObject];
    }
    return NULL;
}

@end

Тогда вы можете просто сделать:

[delegate performSelectorIfResponds:@selector(optionalMethod)];

Ответ 3

Это решение блоков работает хорошо, как только вы получаете свою голову, обернутую вокруг того, что происходит. Я добавил результат BOOL, потому что хотел иметь возможность условно запустить один из нескольких необязательных методов. Некоторые советы, если вы пытаетесь реализовать это решение:

Во-первых, если вы еще не сталкивались с расширением/Категории, вы просто добавляете это в начало своего класса, ВНЕШНЕЕ определение существующего класса. Это будет публичное или частное расширение, основанное на том, где вы его разместили.

@implementation NSObject (Extensions)
// add block-based execution of optional protocol messages
-(BOOL) performBlock:(void (^)(void))block ifRespondsTo:(SEL) aSelector
{
    if ([self respondsToSelector:aSelector]) {
        block();
        return YES;
    }
    return NO;
}
@end

Во-вторых, как вы это называете из своего кода:

BOOL b = [(NSObject*)self.delegate performBlock:^{
    // code to run if the protocol method is implemented
} 
ifRespondsTo:@selector(Param1:Param2:ParamN:)];

Заменить Param1: Param2: ParamN: имена каждого параметра для вашего метода протокола. Каждый из них должен заканчиваться двоеточием. Поэтому, если ваш метод протокола выглядит следующим образом:

-(void)dosomething:(id)blah withObj:(id)blah2 andWithObj(id)blah3;

последняя строка будет выглядеть так:

ifRespondsTo:@selector(dosomething:withObj:andWithObj:)];

Ответ 4

Блоки могут обеспечить лучшее решение. Они позволяют условно выполнять любой код, основанный на существовании реализации данного метода:

-(void) performBlock:(void (^)(void))block ifRespondsTo:(SEL) aSelector{
    if ([self respondsToSelector:aSelector]) {
        block();
    }
}

Используя это дополнение к NSObject, вы можете условно выполнить любой метод @optional, независимо от того, сколько параметров оно может иметь.

См. Как безопасно отправлять сообщения @optional протокола, которые могут быть не реализованы