Является ли переопределение методов инфраструктуры Objective-C хорошей идеей? - PullRequest
4 голосов
/ 23 марта 2012

ObjC имеет очень уникальный способ переопределения методов. В частности, вы можете переопределить функции в собственной структуре OSX. Через "категории" или "Swizzling". Вы даже можете переопределить «скрытые» функции, используемые только внутри.

Может ли кто-нибудь привести мне пример, когда для этого была веская причина? Что-то, что вы бы использовали в выпущенном коммерческом программном обеспечении, а не просто взломанный инструмент для внутреннего использования?

Например, возможно, вы хотели улучшить какой-то встроенный метод, или, возможно, была ошибка в методе фреймворка, который вы хотели исправить.

Кроме того, вы можете объяснить, почему это лучше всего сделать с помощью функций в ObjC, а не в C ++ / Java и т.п. Я имею в виду, что слышал о возможности загрузки библиотеки C, но позволяет заменять определенные функции функциями с тем же именем, которые были загружены ранее. Как ObjC лучше в изменении поведения библиотеки, чем это?

Ответы [ 3 ]

2 голосов
/ 23 марта 2012

Если вы расширяете вопрос от простого изменения к реальной модификации библиотеки, то я могу вспомнить полезные примеры.

Начиная с iOS 5, NSURLConnection предоставляет sendAsynchronousRequest:queue:completionHandler:,который является блочным (/ closure) управляемым способом выполнения асинхронной загрузки из любого ресурса, идентифицируемого с помощью URL (локального или удаленного).Это очень полезный способ продолжить работу, так как он делает ваш код чище и меньше, чем классическая альтернатива делегата, и с гораздо большей вероятностью сохраняет связанные части кода рядом друг с другом.

Этот метод нене поставляется в iOS 4. Итак, что я сделал в своем проекте, так это то, что, когда приложение запускается (через подходящий + (void)load), я проверяю, определен ли метод.Если нет, я исправляю реализацию этого класса.Отныне каждая другая часть программы может быть записана в спецификации iOS 5 без выполнения какой-либо проверки версии или проверки доступности точно так же, как если бы я предназначался только для iOS 5, за исключением того, что она также будет работать на iOS 4.

В Java или C ++, я думаю, того же можно добиться, создав собственный класс для выдачи соединений URL-адресов, который выполняет проверку во время выполнения при каждом вызове.Это худшее решение, потому что от него труднее отступить.Таким образом, если однажды я решу поддерживать только iOS 5, я просто удаляю исходный файл, в котором добавлена ​​моя реализация sendAsynchronousRequest:....Больше ничего не меняется.

Что касается метода swizzling, то единственное, что я вижу в нем, это когда кто-то хочет изменить функциональность существующего класса и не имеет доступа к коду, в котором этот класс создан.Таким образом, вы обычно говорите о попытке изменить логически непрозрачный код извне, делая предположения о его реализации.Я бы не стал поддерживать это как идею на любом языке.Я полагаю, что в Objective-C его рекомендуют больше, потому что Apple более склонна к непрозрачности (см., Например, каждое приложение, которое хотело показывать настраиваемый вид камеры до iOS 3.1, каждое приложение, которое хотело выполнить пользовательскую обработку на входе камеры до этого).на iOS 4.0 и т. д.), а не потому, что это хорошая идея в Objective-C.Это не так.

РЕДАКТИРОВАТЬ: так, в дальнейшем изложении - я не могу опубликовать полный код, потому что я написал его как часть моей работы, но у меня есть класс с именем NSURLConnectionAsyncForiOS4 с реализацией sendAsynchronousRequest:queue:completionHandler:.Эта реализация на самом деле довольно тривиальна, просто отправляя операцию в назначенную очередь, которая выполняет синхронную загрузку через старый интерфейс sendSynchronousRequest:..., а затем отправляет результаты этого в обработчик.

Этот класс имеет + (void)load, который является методом класса, который вы добавляете в класс, который будет выпущен сразу после загрузки этого класса в память, фактически как глобальный конструктор для метакласса и со всеми обычными предостережениями.

В моем+load Я использую среду выполнения Objective C напрямую через интерфейс C, чтобы проверить, определено ли sendAsynchronousRequest:... в NSURLConnection.Если это не так, я добавляю свою реализацию к NSURLConnection, поэтому отныне она определена.Это явно не так уж и быстро - я не корректирую существующую реализацию чего-либо, я просто добавляю пользовательскую реализацию чего-либо, если Apple не доступна.Соответствующие вызовы времени выполнения: objc_getClass, class_getClassMethod и class_addMethod.

В остальной части кода, когда я хочу выполнить асинхронное соединение URL, я просто пишу, например,

[NSURLConnection sendAsynchronousRequest:request
    queue:[self anyBackgroundOperationQueue]
    completionHandler:
    ^(NSURLResponse *response, NSData *data, NSError *blockError)
    {
        if(blockError)
        {
            // oh dear; was it fatal?
        }

        if(data)
        {
            // hooray! You know, unless this was an HTTP request, in
            // which case I should check the response code, etc.
        }

        /* etc */
    }

Таким образом, остальная часть моего кода просто написана для API iOS 5 и не знает и не заботится о том, что у меня есть прокладка где-то еще, чтобы обеспечить изменение одной микроскопической части iOS 5 на iOS 4. И, как я уже сказал, когда япрекрати поддерживать iOS 4, я просто удалю прокладку из проекта, а весь остальной мой код по-прежнему не будет знать или беспокоиться.

У меня был похожий код для предоставления альтернативной частичной реализации NSJSONSerialization (которая динамически создавала новый класс во время выполнения и копировала в него методы);единственное изменение, которое вам нужно сделать, это то, что ссылки на NSJSONSerialization в другом месте будут разрешаться компоновщиком один раз во время загрузки, что вам на самом деле не нужно.Поэтому я добавил быстрый #define из NSJSONSerialization к NSClassFromString(@"NSJSONSerialization") в моем предварительно скомпилированном заголовке.Что является менее функционально аккуратным, но схожим направлением действия с точки зрения поиска способа сохранить поддержку iOS 4 в то время, пока просто записываю остальную часть проекта в стандарты iOS 5.

1 голос
/ 23 марта 2012

Если вы говорите о категориях , вы не переопределяете методы с ними (потому что нет способа вызвать оригинальный метод, такой как вызов super при создании подклассов), но только полностью заменить наваши собственные, что делает всю идею в основном бессмысленной.Категории полезны только для безопасного расширения функциональности, и это единственное использование, которое я когда-либо видел (и это очень хорошо, отличная идея), хотя на самом деле они могут использоваться для опасных вещей.

Если вы имеете в виду переопределение с помощью подкласса , это не уникально.Но в Obj-C вы можете переопределить все, даже частные недокументированные методы, а не только то, что было объявлено перезаписываемым, как в других языках.Лично я думаю, что это хорошо, как я помню, в Delphi и C ++ я использовал «взлом» доступ к закрытым и защищенным членам, чтобы обойти внутреннюю ошибку в фреймворке.Это не очень хорошая идея, но в некоторые моменты это может быть спасением жизни.

Существует также метод swizzling , но это не стандартная языковая функция, это хак.Взламывать недокументированные внутренние компоненты редко бывает хорошей идеей.

А что касается « как , можете ли вы объяснить, почему это лучше всего сделать с помощью функций в ObjC», ответ прост - Obj-C динамическийи эта свобода присуща почти всем динамическим языкам (Javascript, Python, Ruby, Io и многое другое).Если это не искусственно отключено, оно есть у каждого динамического языка.

Обратитесь к странице Википедии о динамических языках для более подробного объяснения и большего количества примеров.Например, еще более удивительные вещи, которые возможны в Obj-C и других динамических языках, - это то, что объект может изменить свой тип (класс) на месте без воссоздания.

1 голос
/ 23 марта 2012

Есть как хорошие, так и плохие случаи.Так как вы ничего не упомянули, в частности, эти примеры будут повсеместными.

Совершенно нормально ( хорошая идея ) переопределять методы фреймворка при создании подклассов:

  • При создании подкласса NSView (из AppKit.framework) ожидается, что вы переопределите drawRect:(NSRect).Это механизм, используемый для рисования видов.
  • При создании пользовательского NSMenu вы можете переопределить insertItemWithTitle:action:keyEquivalent:atIndex: и любые другие методы ...

Главное при создании подклассов является ли илине ваше поведение завершает, переопределяет старое поведение ... или расширяет его (в этом случае ваше переопределение в конечном итоге вызывает [super ...];)


Тем не менее, вы должны всегда избегать использования (и переопределение) любых частных методов API (обычно они имеют префикс подчеркивания в своем имени).Это плохая идея .

Вы также не должны переопределять существующие методы с помощью категорий.Это также плохо .У него неопределенное поведение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...