Внедрение, а затем использование метода

#objective-c #objective-c-runtime

#objective-c #objective-c-время выполнения

Вопрос:

Я играю с внедрением метода, но обжигаю пальцы на этом.

Есть два класса, cats и dogs. Собаки пытаются разгадать секрет кошек, используя speak метод. Собаки выясняют, что у кошек есть catSecret метод, но не могут динамически вводить ему замену. Почему?

Соответствующие методы на Dog стороне

 - ( NSString * ) speak
{
    return @"Woof";
}

- ( NSString * ) wannabeCat
{
    return @"Meow?";
}

  ( BOOL ) resolveInstanceMethod:( SEL ) aSelector
{
    NSString * s = NSStringFromSelector ( aSelector );

    if ( [s containsString:@"Secret"] )
    {
        NSLog ( @"Oops - the cats have a %@", s );
        
        IMP m = [Dog.class methodForSelector:@selector ( wannabeCat )];
        
        class_addMethod ( Dog.class, aSelector, m, "@@:" );
        
        NSLog ( @"tAdded" );
        
        return YES;
    }
    else
    {
        NSLog ( @"Skipping %@", s );
        return [super resolveInstanceMethod:aSelector];
    }
}
  

Редактировать

Ранее я пытался сделать то же самое в forwardingTargetForSelector методе, но после комментариев от Cy-4AH я переместил его в resolveInstanceMethod то место, которое кажется подходящим для этого.

Собаки надеялись, что после этого у Dog класса будет добавлен метод, но это не работает. Он по-прежнему завершается '-[Dog catSecret]: unrecognized selector sent to instance сообщением. Мой вопрос — почему это не работает?

На Cat стороне это соответствующие фрагменты.

 - ( NSString * ) catSecret
{
    return @"scratch";
}

- ( NSString * ) speak
{
        return [NSString stringWithFormat:@"Miaau (%@)", self.catSecret] 
}
  

Я надеялся динамически внедрить catSecret селектор с wannabeCat реализацией на Dog стороне.

Результат выглядит следующим образом

 2020-10-28 12:05:12.613324 0200 Swizzle[4786:119045] Oops - the cats have a catSecret
2020-10-28 12:05:12.613357 0200 Swizzle[4786:119045]    Added
2020-10-28 12:05:12.613434 0200 Swizzle[4786:119045] Skipping _forwardStackInvocation:
2020-10-28 12:05:12.613482 0200 Swizzle[4786:119045] -[Dog catSecret]: unrecognized selector sent to instance 0x1006b2730
  

Комментарии:

1. forwardingTargetForSelector для пересылки цели для селектора, это значит найти кого-то, кто может выполнить селектор. Для динамического добавления реализации для селекторов вам нужно resolveInstanceMethod:

2. Да, я надеялся внедрить метод forwardingTargetForSelector , поэтому я возвращаюсь self оттуда… но я попробую это сделать resolveInstanceMethod:

3. Попробуйте поместить свой тестовый код внутрь dyspatch_asynch , возможно, вы пытаетесь протестировать его на ранней стадии

4. Я переместил код resolveInstanceMethod: , и затем он добавляет его без проблем. Я думаю, что это способ сделать это, спасибо, но это все равно заканчивается нераспознанным исключением селектора. Я регистрирую селекторы, которые он хочет разрешить, а также получаю a _forwardStackInvocation: , с resolveInstanceMethod которым я не знаю, как обращаться. Я полагаю, я просто ищу способ динамического внедрения, а затем использования метода, и то, что я пытаюсь, не работает. Примеры, которые я видел, обычно вводят какой-то известный метод, но здесь. метод обнаруживается динамически.

Ответ №1:

Редактировать

Очищено на основе комментариев Cy-4AH.

Первым, гораздо большим шагом было перенести внедрение метода resolveInstanceMethod в, и я отредактировал вопрос, чтобы отразить это. Это также было связано с Cy-4AH, который к настоящему времени я могу почти печатать без ошибок.

Однако окончательное исправление было небольшим … то, как код находится в вопросе

 IMP m = [Dog.class methodForSelector:@selector ( wannabeCat )];
  

относится к методу класса, а не к методу экземпляра. Это должно быть изменено на

 IMP m = class_getMethodImplementation ( Dog.class, @selector ( wannabeCat ) );
  

чтобы сделать его методом экземпляра, и тогда он работает просто отлично, к радости собак…

Для справки, полный новый метод становится

   ( BOOL ) resolveInstanceMethod:( SEL ) aSelector
{
    if ( [super resolveInstanceMethod:aSelector] )
    {
        return YES;
    }
    else
    {
        NSLog ( @"Oops - the cats have a %@", NSStringFromSelector( aSelector ) );
        
        IMP m = class_getMethodImplementation ( Dog.class, @selector ( wannabeCat ) );
        
        class_addMethod ( Dog.class, aSelector, m, "@@:" );
        
        NSLog ( @"tAdded" );
        
        return YES;
    }
}
  

Комментарии:

1. Вам нужно class_getMethodImplementation(self, @selector ( wannabeCat )) вместо [[Dog alloc] init...

2. Да! Намного чище, спасибо… Я отредактировал его в ответ.