Objective-C forwardInvocation:

Я часто делаю что-то вроде:

CoolViewController *coolViewController = [[CoolViewController alloc] init];
[self.navigationController pushViewController:coolViewController animated:YES];
[coolViewController release];

Как бы я, в категории UINavigationControllerпереопределить forwardInvocation: так что я мог бы вместо этого сделать:

[self.navigationController pushCoolViewControllerAnimated:YES];
  1. Пожалуйста, включите соответствующий код в ваш ответ, а не просто объяснение. Спасибо!

  2. Не стесняйтесь комментировать, является ли это хорошей практикой. Я также спрашиваю об этом в образовательных целях, но мне кажется, что в этом случае упрощение кода может перевесить незаметную (правильную?) Стоимость времени обработки и использования памяти. Кроме того, я родом из Ruby и люблю использовать динамическое программирование для упрощения вещей, например, динамические искатели (например, find_by_name) в Rails.

  3. Бонусные баллы, если вы могли бы реализовать pushCoolViewControllerAnimated:withBlock и вызвать блок после инициализации контроллера представления, что позволяет мне устанавливать определенные переменные экземпляра на созданном контроллере представления.

ОБНОВЛЕНИЕ: Я только что вспомнил, что ARC скоро придет. Таким образом, этот конкретный пример может быть не очень полезным, но все же это отличное упражнение / пример, который можно использовать в других случаях, например, для динамических искателей для базовых данных и передачи блока для настройки NSFetchRequest,

1 ответ

Решение

Используйте механизм разрешения динамического метода, описанный в Руководстве по программированию Objective-C, в частности, +[NSObject resolveInstanceMethod:]:

@implementation UINavigationController (FWD)
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *name = NSStringFromSelector(sel);
    NSString *prefix = @"push";
    NSString *suffix = @"Animated:";
    if ([name hasPrefix:prefix] && [name hasSuffix:suffix]) {
        NSRange classNameRange = {[prefix length],
            [name length] - [prefix length] - [suffix length]}
        NSString *className = [name substringWithRange:classNameRange];
        Class cls = NSClassFromString(className);
        if (cls) {
            IMP imp = imp_implementationWithBlock(
            ^(id me, BOOL animated) {
                id vc = [[cls alloc] init];
                [me pushViewController:vc animated:animated];
                [vc release];
            });
            class_addMethod(cls, sel, imp, "v@:c");
            return YES;
        }
    }
    return [super resolveInstanceMethod:sel];
}
@end

Конечно, если UINavigationController уже использует +resolveInstanceMethod:, ты сейчас сломал это. Делать это в подклассе UINavigationControllerили использование метода swizzling для вызова исходной реализации решит эту проблему.

Версия, принимающая блок после создания, является прямым расширением (измените параметры блока, измените кодировку типа, измените шаблон имени селектора и способ извлечения предполагаемого имени класса).

Другие вопросы по тегам