Как заменить экземпляр класса в системе подклассом в Objective-C?

Я создал подкласс системного класса (UINavigationBar, если быть точным), чтобы добавить некоторые специфические функции. Я использовал это везде, как замену UINavigationBar. Однако теперь я хочу заменить некоторые UINavigationBars, используемые в системных фреймворках, моим собственным подклассом, чтобы они обеспечивали такое же поведение. В частности, я хотел бы, чтобы UINavigationBar в контроллере представления UITabBarController был экземпляром моего класса.

Я думал, что это невозможно, поэтому попытался создать категорию в UINavigationBar, которая будет распространяться по всей системе. Тем не менее, моя категория должна будет выполнить некоторую пользовательскую инициализацию и отмену (подписка и отказ от подписки на уведомление NSNotificationCenter). Если я перезапишу методы init/dealloc в своей категории, я не смогу вызывать исходные методы (реализованные UINavigationBar), что может быть очень опасным/фатальным/вероятно, не очень функциональным.

Одним из возможных решений является метод swizzling, но я не совсем уверен, как его использовать, и, по-видимому, это может быть довольно сложно.

Если кто-нибудь сможет рассказать о том, как реализовать что-то, что решит мои проблемы (или какой-нибудь собственный код, как я мог бы использовать метод swizzling), я был бы очень благодарен.


person Tom H    schedule 31.08.2011    source источник
comment
+1 за хорошо составленный вопрос.   -  person RLT    schedule 25.09.2011


Ответы (1)


Вы можете переопределить метод +allocWithZone: в классе UINavigationBar, чтобы вместо этого создать экземпляр вашего подкласса (так же, как ведет себя кластер классов), используя функцию method_setImplementation() (проверьте Справочник по среде выполнения Objective-C для получения более подробной информации о функции)

person chmeee    schedule 31.08.2011
comment
Как получить параметр метода? Я могу получить IMP с помощью IMP theImplementation = [self methodForSelector:theSelector]; И где мне вызывать method_setImplementation? Вызывается ли application:didFinishLaunching: перед загрузкой перьев? Или main() лучше? - person Tom H; 31.08.2011
comment
Упс, понятно, функция class_getInstanceMethod. Мила, спасибо за помощь! - person Tom H; 31.08.2011
comment
На самом деле, категория в классе UINavigationBar, которая переопределяет +allocWithZone: для предоставления экземпляра вашего подкласса, чище. Таким образом, он загружается перед main(), поэтому всегда используется вместо оригинала. - person chmeee; 31.08.2011
comment
Зачем мне тогда использовать method_setImplementation? - person Tom H; 31.08.2011
comment
Это было первое, что пришло мне в голову, когда я ответил. Это также полезно для динамического переопределения методов, но в этом случае, вероятно, не нужно, если только существующий +allocWithZone: не делает что-то большее, чем просто выделение экземпляра класса, в чем я сомневаюсь. - person chmeee; 31.08.2011
comment
Если только... если я создам категорию в UINavigationBar и перезапишу allocWithZone:, то не просочится ли перезаписанная реализация, поэтому, когда я попытаюсь вызвать alloc в своем подклассе, я войду в бесконечный цикл? [Или методы категорий не наследуются таким образом?] - person Tom H; 31.08.2011
comment
Если вы напишете свою категорию +allocWithZone: просто для выполнения NSAllocateObject([MyUINavigationBar class], 0, zone), то он просто создаст экземпляр вашего класса, несмотря ни на что (всегда лучше добавлять к нему префикс: if (self == [класс UINavigationBar]) / else [super allocWithZone:zone], чтобы другие подклассы не создавали экземпляры вашего подкласса по ошибке) - person chmeee; 31.08.2011
comment
Большое спасибо за вашу помощь! - person Tom H; 31.08.2011
comment
Просто для ясности: это не столько переопределение allocWithZone:, сколько его использование, что может помочь будущим спелеологам SO в их поисках, верно? - person Olie; 05.08.2014
comment
Кроме того, NSAllocateObject() недоступен в ARC. - person Olie; 05.08.2014