Каков наилучший способ решения конфликта имен Objective-C?

Objective-C не имеет пространств имен; это очень похоже на C, все в одном глобальном пространстве имен. Обычной практикой является префикс классов с инициалами, например. если вы работаете в IBM, вы можете префикс их "IBM"; если вы работаете в Microsoft, вы можете использовать "MS"; и так далее. Иногда инициалы ссылаются на проект, например. Adium префикс классов с "AI" (поскольку за ним нет компании, чтобы вы могли взять инициалы). Apple префикс классов с NS и говорит, что этот префикс зарезервирован только для Apple.

До сих пор так хорошо. Но добавление от 2 до 4 букв к имени класса впереди - очень ограниченное пространство имен. Например. MS или AI могут иметь совершенно разные значения (например, AI может быть искусственным интеллектом), а другой разработчик может решить использовать их и создать одинаково названный класс. Bang, конфликт пространства имен.

Хорошо, если это столкновение между одним из ваших собственных классов и одной из внешних фреймворков, которые вы используете, вы можете легко изменить название своего класса, неважно. Но что, если вы используете две внешние среды, обе структуры, к которым у вас нет источника, и которые вы не можете изменить? Ваше приложение связывается с обоими из них, и вы получаете конфликты имен. Как бы вы решили их решить? Каков наилучший способ обойти их таким образом, что вы все равно можете использовать оба класса?

В C вы можете обойти их, не связывая напрямую с библиотекой, вместо этого вы загружаете библиотеку во время выполнения, используя dlopen(), затем найдите символ, который вы ищете, используя dlsym() и назначьте его глобальному символу (который вы можете назвать любым способом), а затем получить доступ к нему через этот глобальный символ. Например. если у вас есть конфликт, потому что у некоторой библиотеки C есть функция с именем open(), вы можете определить переменную с именем myOpen и указать ее на функцию open() библиотеки, поэтому, когда вы хотите использовать систему open(), вы просто используете open(), а когда хотите использовать другой, вы получаете доступ к нему через идентификатор myOpen.

Возможно ли что-то подобное в Objective-C, а если нет, есть ли другое умное, сложное решение, которое вы можете использовать конфликты пространства имен решения? Любые идеи?


Обновление:

Просто для того, чтобы прояснить это: ответы, которые предлагают, как избежать конфликтов пространства имен заранее или как создать лучшее пространство имен, безусловно, приветствуются; однако я не буду принимать их как ответ, так как они не решают мою проблему. У меня две библиотеки, и имена их классов сталкиваются. Я не могу их изменить; У меня нет источника ни одного. Столкновение уже существует, и подсказки о том, как его можно было избежать заранее, больше не помогут. Я могу направить их разработчикам этих фреймворков и надеюсь, что они в будущем будут выбирать лучшее пространство имен, но пока я ищу решение для работы с фреймворками прямо сейчас в рамках одного приложения. Любые решения, чтобы сделать это возможным?

Ответ 1

Если вам не нужно одновременно использовать классы из обеих фреймворков, и вы нацеливаете платформы, поддерживающие разгрузку NSBundle (OS X 10.4 или новее, поддержка GNUStep), а производительность действительно не является проблемой для вас, Я считаю, что каждый раз, когда вам нужно использовать класс, вы можете загружать одну фреймворк, а затем выгружать ее и загружать другую, когда вам нужно использовать другую инфраструктуру.

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

Вам не нужно было бы копировать или переименовывать класс, если бы был способ скопировать данные, на которые указывает IMP. Вы можете создать новый класс, а затем скопировать поверх ivars, методов, свойств и категорий. Гораздо больше работы, но это возможно. Однако у вас все еще будет проблема с другими классами в структуре, ссылающимися на неправильный класс.

EDIT: фундаментальное различие между временем выполнения C и Objective-C, как я понимаю, когда загружаются библиотеки, функции в этих библиотеках содержат указатели на любые символы, которые они ссылаются, тогда как в Objective-C они содержат строку представления имен символов. Таким образом, в вашем примере вы можете использовать dlsym для получения символьного адреса в памяти и прикрепить его к другому символу. Другой код в библиотеке по-прежнему работает, потому что вы не меняете адрес исходного символа. Objective-C использует таблицу поиска для сопоставления имен классов по адресам, и это сопоставление 1-1, поэтому вы не можете иметь два класса с тем же именем. Таким образом, чтобы загрузить оба класса, одно из них должно изменить свое имя. Однако, когда другим классам необходимо получить доступ к одному из классов с этим именем, они будут запрашивать таблицу поиска для своего адреса, и таблица поиска никогда не вернет адрес переименованного класса с учетом исходного имени класса.

Ответ 2

Префикс ваших классов с уникальным префиксом в принципе является единственным вариантом, но есть несколько способов сделать это менее обременительным и уродливым. Существует длинное обсуждение опций здесь. Моей любимой является директива компилятора @compatibility_alias Objective-C (описывается здесь). Вы можете использовать @compatibility_alias для "переименования" класса, позволяя вам назвать ваш класс с помощью FQDN или какого-то такого префикса:

@interface COM_WHATEVER_ClassName : NSObject
@end

@compatibility_alias ClassName COM_WHATEVER_ClassName
// now ClassName is an alias for COM_WHATEVER_ClassName

@implementation ClassName //OK
//blah
@end

ClassName *myClass; //OK

В рамках полной стратегии вы можете префикс всех ваших классов с помощью уникального префикса, такого как полное доменное имя, а затем создать заголовок со всем @compatibility_alias (я бы предположил, что вы можете автоматически генерировать этот заголовок).

Недостатком такого префикса является то, что вы должны ввести истинное имя класса (например, COM_WHATEVER_ClassName выше) во всем, что требует имени класса из строки, кроме компилятора. Примечательно, что @compatibility_alias - это директива компилятора, а не функция времени выполнения, поэтому NSClassFromString(ClassName) завершится с ошибкой (return nil) - вам нужно будет использовать NSClassFromString(COM_WHATERVER_ClassName). Вы можете использовать ibtool через фазу сборки, чтобы изменить имена классов в интерфейсе Builder nib/xib, чтобы вам не приходилось писать полный COM_WHATEVER _... в Interface Builder.

Заключительное предостережение: поскольку это директива компилятора (и неясная), она не может быть переносимой для компиляторов. В частности, я не знаю, работает ли он с интерфейсом Clang из проекта LLVM, хотя он должен работать с LLVM-GCC (LLVM с использованием интерфейса GCC).

Ответ 3

Несколько человек уже поделились каким-то хитрым и умным кодом, который мог бы помочь решить проблему. Некоторые из предложений могут работать, но все они менее идеальны, и некоторые из них совершенно противны для реализации. (Иногда уродливые хаки неизбежны, но я стараюсь избегать их всякий раз, когда могу). С практической точки зрения, вот мои предложения.

  • В любом случае проинформируйте разработчиков о и структуре конфликта и дайте понять, что их отказ избежать и/или с ним справиться вызывает серьезные проблемы бизнеса, которые могут потерял доход от бизнеса, если не был решен. Подчеркните, что при разрешении существующих конфликтов на уровне каждого класса это менее навязчивое решение, полностью изменяющее их префикс (или использование одного, если они не в настоящее время, и стыд за них!) - лучший способ гарантировать, что они не будут снова увидеть ту же проблему.
  • Если конфликты именования ограничены достаточно небольшим набором классов, посмотрите, можете ли вы работать только с этими классами, особенно если один из конфликтующих классов не используется вашим кодом прямо или косвенно. Если это так, посмотрите, будет ли поставщик предоставлять пользовательскую версию фреймворка, которая не включает конфликтующие классы. Если нет, будьте откровенны в том, что их негибкость снижает рентабельность инвестиций, используя их рамки. Не чувствуйте себя плохо в том, чтобы быть назойливым в разумных пределах - клиент всегда прав.; -)
  • Если одна структура более "непригодна", вы можете подумать о ее замене другой картой (или комбинацией кода), либо сторонней, либо доморощенной. (Последний является нежелательным наихудшим случаем, поскольку он, безусловно, будет нести дополнительные бизнес-затраты как для разработки, так и для обслуживания.) Если вы это сделаете, сообщите поставщику этой структуры о том, почему вы решили не использовать свою инфраструктуру.
  • Если обе структуры считаются одинаково необходимыми для вашего приложения, изучите способы отделить использование одного из них к одному или нескольким отдельным процессам, возможно, обмениваясь через DO, как предложил Луи Гербарг. В зависимости от степени общения это может быть не так плохо, как вы могли бы ожидать. Несколько программ (в том числе QuickTime, я полагаю) используют этот подход для обеспечения более детальной безопасности, предоставляемой с помощью профилей песочницы Seatbelt в Leopard, так что только конкретный подмножество вашего кода разрешено выполнять критические или чувствительные операции. Производительность будет компромиссом, но может быть вашим единственным вариантом.

Я предполагаю, что лицензионные сборы, сроки и длительность могут предотвратить мгновенное действие по любому из этих пунктов. Надеюсь, вы сможете как можно скорее разрешить конфликт. Удачи!

Ответ 4

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

Существуют и другие потенциальные решения, но многие из них зависят от конкретной ситуации. В частности, используете ли вы современные или устаревшие временные ряды, вы жирная или единая архитектура, 32 или 64 бит, какие версии ОС вы используете для таргетинга, динамически связываете ли вы статически, или у вас есть выбор, и потенциально нормально делать что-то, что может потребовать обслуживания новых обновлений программного обеспечения.

Если вы действительно в отчаянии, что вы можете сделать, это:

  • Не ссылаться непосредственно на одну из библиотек
  • Внедрить альтернативную версию подпрограмм выполнения objc, которая меняет имя во время загрузки (проверьте проект objc4, что именно вам нужно делать, зависит от ряда вопросов, которые я задал выше, но это должно быть возможно независимо от того, что ответы).
  • Используйте что-то вроде mach_override, чтобы внедрить новую реализацию.
  • Загрузите новую библиотеку с помощью обычных методов, она пройдет через исправленную процедуру компоновщика и изменит ее имя класса.

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

Ответ 5

Рассматривали ли вы использование функций времени выполнения (/usr/include/objc/runtime.h), чтобы клонировать один из конфликтующих классов в не сталкивающийся друг с другом класс, а затем загружать встречную структуру классов? (это потребует, чтобы сталкивающиеся фреймворки загружались в разное время, чтобы работать.)

Вы можете проверять классы ivars, методы (с именами и адресами реализации) и именами с исполняемой средой, а также динамически создавать свои собственные, иметь одинаковый макет ivar, имена методов/адреса реализации и только отличаться по имени ( чтобы избежать столкновения)

Ответ 6

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

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

Ответ 7

Похоже, проблема заключается в том, что вы не можете ссылаться на файлы заголовков с обеих систем в одной и той же системе перевода (исходный файл). Если вы создаете обертки objective-c вокруг библиотек (что делает их более пригодными для использования в процессе) и только # включает заголовки для каждой библиотеки в реализации классов-оболочек, что эффективно делит коллизии имен.

У меня недостаточно опыта с этим в objective-c (только на начальном этапе), но я считаю, что это то, что я сделал бы на C.

Ответ 8

@compatibility_alias сможет разрешать конфликты пространства имен, например.

@compatibility_alias NewAliasClass OriginalClass;

Однако это не будет разрешать какие-либо из коллизий переходов, typedefs или протоколов имен. Кроме того, он не очень хорошо работает с @class forward decls исходного класса. Поскольку большинство фреймворков будут поставляться с такими неклассическими элементами, как typedefs, вы, вероятно, не сможете исправить проблему с именами с помощью только совместимости_alias.

Я посмотрел на похожую проблему на вашу, но у меня был доступ к исходному коду и строился фреймворк. Лучшим решением, которое я нашел для этого, было использование @compatibility_alias условно С#defines для поддержки перечислений /typedefs/protocols/etc. Вы можете сделать это условно в блоке компиляции для заголовка, о котором идет речь, чтобы свести к минимуму риск расширения контента в другой конфликтующей структуре.

Ответ 9

Префикс файлов - это самое простое решение, о котором я знаю. Cocoadev имеет страницу пространства имен, которая является попыткой сообщества избегать конфликтов пространства имен. Не стесняйтесь добавлять свои собственные в этот список, я считаю, что это для него.

http://www.cocoadev.com/index.pl?ChooseYourOwnPrefix

Ответ 10

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

Более техническое решение, если бы я был на вашем месте, был бы моим выбором.

Ответ 11

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

cc foo.o -ldog bar.o -lcat

Если foo.o и bar.o оба ссылаются на символ rat, тогда libdog разрешит foo.o rat и libcat разрешит bar.o rat.

Ответ 12

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

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

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

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

Опять же, никак не доказано, но хотелось добавить перспективу. надеюсь, что это поможет:)

Ответ 13

Если у вас есть две структуры, имеющие одно и то же имя функции, вы можете попробовать динамически загружать фреймворки. Это будет неэлегантно, но возможно. Как это сделать с классами Objective-C, я не знаю. Я предполагаю, что класс NSBundle будет иметь методы, которые будут загружать определенный класс.