Який найкращий спосіб вирішити зіткнення в просторі імен Objective-C?


174

Objective-C не має просторів імен; це майже як C, все знаходиться в одному глобальному просторі імен. Поширена практика полягає в префіксації класів з ініціалами, наприклад, якщо ви працюєте в IBM, ви можете префіксувати їх за допомогою "IBM"; якщо ви працюєте в Microsoft, ви можете використовувати "MS"; і так далі. Іноді ініціали посилаються на проект, наприклад, клас префіксів Adium з "AI" (оскільки за ним не стоїть компанія, яку ви могли взяти ініціали). Префікси Apple класів із NS та кажуть, що цей префікс зарезервований лише для Apple.

Поки що добре. Але додавання 2 - 4 букв до імені класу спереду - це дуже, дуже обмежений простір імен. Наприклад, MS або AI можуть мати зовсім інші значення (AI може бути штучним інтелектом, наприклад), а деякі інші розробники можуть вирішити використовувати їх і створити клас з однаковою назвою. Вибух , зіткнення простору імен.

Гаразд, якщо це зіткнення одного з ваших власних класів та одного із зовнішніх рамок, якими ви користуєтесь, ви можете легко змінити іменування свого класу, без особливих зусиль. Але що робити, якщо ви використовуєте дві зовнішні рамки, обидві рамки, для яких у вас немає джерела, і які ви не можете змінити? Ваша програма зв’язується з обома, і ви отримуєте конфлікти імен. Як би ви вирішили це вирішити? Який найкращий спосіб обійти їх таким чином, що ви все ще можете використовувати обидва класи?

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

Чи можливо щось подібне в Objective-C, а якщо ні, чи є якесь інше розумне, хитре рішення, яке ви можете використовувати для вирішення конфліктів у просторі імен? Будь-які ідеї?


Оновлення:

Просто для уточнення цього: відповіді, які підказують, як заздалегідь уникнути зіткнень у просторі імен або як створити кращу область імен, безумовно, вітаються; однак я не прийму їх як відповідь, оскільки вони не вирішують моєї проблеми. У мене дві бібліотеки і назви їхніх класів стикаються. Я не можу їх змінити; У мене немає джерела жодного. Зіткнення вже існує, і поради про те, як його можна було заздалегідь уникнути, вже не допоможуть. Я можу переслати їх розробникам цих фреймворків і сподіваюся, що вони виберуть кращу область імен у майбутньому, але поки я шукаю рішення для роботи з фреймворками прямо зараз в рамках однієї програми. Будь-які рішення, щоб зробити це можливим?


7
У вас є гарне запитання (що робити, якщо вам потрібні дві рамки зі зіткненням імені), але він закопаний у тексті. Перегляньте, щоб зробити це зрозумілішим, і ви уникнете спрощених відповідей, як зараз у вас.
benzado

4
Це мій найбільший захват від сучасного дизайну мови Objective-C. Подивіться на відповіді нижче; ті, хто насправді вирішує питання (розвантаження NSBundle, використання DO тощо), - це огидні хаки, які просто не повинні бути необхідними для чогось такого тривіального, як уникнення конфлікту в просторі імен.
erikprice

@erikprice: Амінь. Я вивчаю obj-c і торкаюся саме цього питання. Сюди прийшли шукати просте рішення .... кульгавий.
Дейв Матір

1
Для запису технічно і C, і Objective-C забезпечують підтримку декількох просторів імен - не все, що шукає ОП. Дивіться objectivistc.tumblr.com/post/3340816080/…

Гм, я цього не знав. Виду жахливого дизайнерського рішення немає?
Ніко

Відповіді:


47

Якщо вам не потрібно одночасно використовувати класи з обох фреймів, і ви орієнтуєтесь на платформи, які підтримують розвантаження NSBundle (ОС X 10.4 або пізнішої версії, немає підтримки GNUStep), і продуктивність насправді не є проблемою для вас, я вважаю що ви можете завантажувати один фреймворк щоразу, коли вам потрібно використовувати клас із нього, а потім розвантажувати його та завантажувати інший, коли потрібно використовувати інший фреймворк.

Моя початкова ідея полягала в тому, щоб використовувати NSBundle для завантаження однієї з фреймворків, потім скопіювати або перейменувати класи всередині цього фрейму, а потім завантажити інший фреймворк. З цим є дві проблеми. По-перше, я не зміг знайти функцію копіювання даних, вказаних на перейменування або копіювання класу, а будь-які інші класи в тій першій рамці, які посилаються на перейменований клас, тепер посилаються б на клас з іншого фрейму.

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

EDIT: Принципова відмінність між умовами виконання C і Objective-C полягає в тому, що я розумію, що коли бібліотеки завантажуються, функції цих бібліотек містять вказівники на будь-які символи, на які вони посилаються, тоді як у Objective-C вони містять рядкові представлення назви символів thsoe. Таким чином, у вашому прикладі ви можете використовувати dlsym, щоб отримати адресу символу в пам'яті та приєднати його до іншого символу. Інший код у бібліотеці все ще працює, оскільки ви не змінюєте адресу оригінального символу. Objective-C використовує таблицю пошуку для відображення назв класів за адресами, і це відображення 1-1, тому у вас не може бути двох класів з тим самим іменем. Таким чином, для завантаження обох класів в одному з них повинно бути змінено їх ім'я. Однак, коли іншим класам потрібно отримати доступ до одного з класів з таким ім'ям,


5
Я вважаю, що розвантаження пакетів не підтримується до 10.5 або пізніше.
Квінн Тейлор

93

Префіксація ваших класів унікальним префіксом є принципово єдиним варіантом, але є кілька способів зробити це менш обтяжливим і некрасивим. Існує довге обговорення варіантів тут . Моя улюблена - @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

Як частина повної стратегії, ви можете встановити всі свої класи унікальним префіксом, таким як FQDN, а потім створити заголовок із усіма @compatibility_alias (я думаю, ви могли б автоматично генерувати зазначений заголовок).

Недоліком префікса, як це, є те, що вам потрібно ввести справжнє ім’я класу (наприклад, COM_WHATEVER_ClassNameвище) у будь-що, що потребує назви класу з рядка, крім компілятора. Зокрема, @compatibility_aliasце директива компілятора, а не функція виконання, тому NSClassFromString(ClassName)не вдасться (повернутись nil) - вам доведеться використовувати NSClassFromString(COM_WHATERVER_ClassName). Можна використовуватиibtool через фазу збірки для зміни назв класів в nib / xib Interface Builder, щоб не потрібно було писати повний COM_WHATEVER _... в Interface Builder.

Остаточне застереження: оскільки це директива щодо компілятора (і мала за нею незрозуміла), вона може не переноситися через компілятори. Зокрема, я не знаю, чи працює він із фронтендом Clang в рамках проекту LLVM, хоча він повинен працювати з LLVM-GCC (LLVM за допомогою фронталу GCC).


4
Оновлення сумісності_alias, я про це не знав! Дякую. Але це не вирішує мою проблему. Я не маю можливості змінювати будь-які префікси будь-якої основи, яку я використовую, я маю їх лише у двійковій формі, і вони стикаються. Що мені робити з цього приводу?
Mecki

У якому файлі (.h або .m) повинен переходити рядок @compatibility_alias?
Алекс Бассон

1
@Alex_Basson @compatibility_alias, ймовірно, повинен зайти в заголовок, щоб він був видно скрізь, де відображається декларація @interface.
Баррі Ворк

Цікаво, чи можете ви використовувати @compatibility_alias з іменами протоколів, визначеннями typedef, або що-небудь інше для цього?
Алі

Хороших думок тут. Чи може бути використаний метод swizzling, щоб запобігти поверненню нуля для зведених класів NSClassFromString (ClassName)? Напевно, було б важко знайти всі методи, які взяли назву класу.
Дікі Сінгх

12

Кілька людей вже поділилися деяким хитрим та розумним кодом, який може допомогти вирішити проблему. Деякі з пропозицій можуть спрацювати, але всі вони є менш ідеальними, а деякі з них прямо противно реалізувати. (Іноді некрасиві хаки неминучі, але я намагаюся уникати їх, коли можу.) З практичної точки зору, ось мої пропозиції.

  1. У будь-якому випадку повідомте розробникам обох каркасів про конфлікт і дайте зрозуміти, що їх неспроможність уникнути та / або боротися з ним спричиняє вам справжні бізнес-проблеми, які можуть вирішити втрату ділових доходів, якщо їх вирішити. Підкресліть, що розв’язання існуючих конфліктів на основі класу є менш нав'язливим виправленням, цілком змінивши їх префікс (або використовуючи його, якщо їх зараз немає, і соромтеся за них!) - найкращий спосіб переконатися, що вони не будуть знову побачити ту саму проблему.
  2. Якщо конфлікти імен обмежуються досить невеликим набором класів, подивіться, чи можна обійти лише ці класи, особливо якщо один із конфліктуючих класів не використовується прямо чи опосередковано вашим кодом. Якщо так, подивіться, чи надасть постачальник користувальницьку версію фреймворку, що не включає конфліктуючі класи. Якщо ні, будьте відвертими щодо того, що їхня негнучкість зменшує рентабельність інвестицій від використання їхніх фреймворків. Не відчувайте себе погано з розуму в межах причини - замовник завжди правий. ;-)
  3. Якщо один фреймворк більше "необхідний", ви можете замінити його на інший фреймворк (або комбінацію коду), стороннім або домашнім. (Останнє є небажаним гіршим випадком, оскільки це, безумовно, спричинить додаткові витрати бізнесу, як на розробку, так і на технічне обслуговування.) Якщо ви це зробите, повідомте продавця цієї рамки саме тому, що ви вирішили не використовувати їх рамки.
  4. Якщо обидві рамки вважаються однаково необхідними для вашої програми, вивчіть способи розподілити використання одного з них на один або кілька окремих процесів, можливо, спілкування через DO, як запропонував Луї Гербарг. Залежно від ступеня спілкування це може бути не так погано, як ви могли очікувати. Кілька програм (включаючи QuickTime, я вважаю) використовують цей підхід, щоб забезпечити більш детальну безпеку, що забезпечується використанням профілів пісочниці Seatbelt в Leopard , таким чином, що для виконання критичних або чутливих операцій дозволено лише певний підмножина вашого коду. Продуктивність буде корисною, але це може бути ваш єдиний варіант

Я здогадуюсь, що плата за ліцензування, терміни та тривалість ліцензування можуть перешкоджати негайним діям на будь-який із цих пунктів. Сподіваємось, ви зможете вирішити конфлікт якомога швидше. Удачі!


8

Це суворо, але ви можете використовувати розподілені об'єкти для того, щоб один з класів утримував лише підпорядковану адресу програми та RPC до нього. Це стане безладним, якщо ви передаватимете туди речі назад і назад (а це може бути неможливо, якщо обидва класу безпосередньо маніпулюють думками тощо).

Є й інші потенційні рішення, але дуже багато їх залежить від точної ситуації. Зокрема, чи використовуєте ви сучасні або застарілі версії часу, ви жирна чи одиночна архітектура, 32 або 64 біт, на які випуски ОС націлені, чи динамічно зв’язуєтесь, стаціонарно зв’язуєтесь чи маєте вибір, і чи потенційно це? добре зробити щось, що може потребувати обслуговування для нових оновлень програмного забезпечення.

Якщо ви справді відчайдушні, то можете зробити:

  1. Не посилайтеся безпосередньо на одну з бібліотек
  2. Реалізуйте альтернативну версію підпрограми виконання objc, яка змінює ім'я під час завантаження ( перевірте проект objc4 , що саме вам потрібно зробити, залежить від ряду питань, які я задав вище, але це має бути можливим незалежно від відповідей. ).
  3. Використовуйте щось на зразок mach_override, щоб ввести свою нову реалізацію
  4. Завантажте нову бібліотеку, використовуючи звичайні методи, вона пройде процедуру виправленого зв’язку та змінить її className

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


4

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

Ви можете перевіряти класи ivars, методи (з іменами та адресами реалізації) та імена під час виконання, а також створювати свої власні, а також динамічно мати однаковий макет ivar, назви методів / адреси реалізації та відрізнятися лише за назвою (щоб уникнути зіткнення)


3

Відчайдушні ситуації вимагають відчайдушних заходів. Чи розглядали ви злом коду об’єкта (або бібліотечного файлу) однієї з бібліотек, змінивши символ зіткнення на альтернативне ім’я - тієї ж довжини, але різного написання (але, рекомендація, однакової довжини імені)? По суті неприємний.

Незрозуміло, чи ваш код безпосередньо викликає дві функції з тим самим іменем, але різні реалізації, чи конфлікт непрямий (також не ясно, чи має це значення). Однак є, принаймні, зовнішній шанс, що перейменування спрацює. Можливо, також є ідея мінімізувати різницю в написанні, щоб, якщо символи були впорядкованому порядку в таблиці, перейменування не виводило речі з ладу. Такі речі, як двійковий пошук, засмучуються, якщо масив, який вони шукають, не є впорядкованому порядку, як очікувалося.


Зміна бібліотеки на диску не викликає сумнівів, оскільки ліцензія цього не дозволяє. Зміна символів у пам'яті було б можливим, але я не бачу способу, як це можна було зробити (завантаження lib у пам'ять, модифікація та передання її в динамічний лінкер ... для цього не бачу жодного методу).
Mecki

3
Гаразд - час вирішити, яка з двох бібліотек менш важлива, і знайти заміну. І поясніть тому, за що ви платите, але що ви відмовляєтесь, що причина, яку ви змінюєте, - через це зіткнення, і вони можуть зберегти ваш бізнес, вирішивши вашу проблему.
Джонатан Леффлер

2

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

@compatibility_alias NewAliasClass OriginalClass;

Однак це не вирішить жодних зіткнень з переліком, typedefs або протоколом простору імен . Крім того, це не добре грає@class перехідними відмінками оригінального класу. Оскільки більшість фреймворків поставляться з такими некласовими речами, як typedefs, ви, ймовірно, не зможете виправити проблему простору імен за допомогою просто сумісності_alias.

Я розглядав подібну проблему до вашої , але я мав доступ до джерела і будував рамки. Найкращим рішенням, яке я знайшов для цього, було використання @compatibility_aliasумовно з #defines для підтримки enums / typedefs / protocols / тощо. Це можна зробити умовно на блоці компіляції для відповідного заголовка, щоб мінімізувати ризик розширення матеріалу в іншій системі зіткнення.


1

Схоже, проблема полягає в тому, що ви не можете посилатися на заголовки файлів обох систем в одному блоці перекладу (вихідний файл). Якщо навколо бібліотек ви створите обгортки об'єктивів-c (зробивши їх більш корисними в процесі), і лише # включіть заголовки кожної бібліотеки у реалізацію класів обгортки, це фактично розділить зіткнення імен.

Я не маю достатнього досвіду з цим у предметі-c (тільки розпочинаю), але я вважаю, що саме це я зробив би в C.


1
Але хіба ви все ще не зіткнетесь, якби ви спробували включити обидва заголовки обгортки в один і той же файл (оскільки кожен з них повинен буде сам включати заголовки відповідної рамки)?
Вілько

0

Префіксація файлів - це найпростіше рішення, про яке я знаю. У Cocoadev є сторінка простору імен, яка допомагає спільноті уникати зіткнень простору імен. Не соромтеся додати своїх до цього списку, я вважаю, що це саме так.

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


Як допомагає префіксація файлів, якщо об’єкти, визначені у файлах, викликають проблему? Я, безумовно, можу маніпулювати h-файлами, але тоді лінкер вже не знайде об’єктів, коли
сподобається

Я вважаю, що це, скоріше, найкраще - ніж рішення вихідної проблеми.
Алі

Це зовсім не стосується питання
Madbreaks

0

Якщо у вас зіткнення, я б запропонував вам подумати над тим, як ви могли б відновити один із рамок своєї програми. Зіткнення говорить про те, що обидва роблять подібні речі, як це є, і ви, швидше за все, могли б обійтись, використовуючи додатковий фреймворк, просто переробляючи програму. Це не тільки вирішить вашу проблему з простором імен, але зробить ваш код більш надійним, простішим в обслуговуванні та більш ефективним.

Щодо більш технічного рішення, якби я був на вашому становищі, це був би мій вибір.


0

Якщо зіткнення відбувається лише на рівні статичного зв'язку, ви можете вибрати, яку бібліотеку використовувати для розв’язання символів:

cc foo.o -ldog bar.o -lcat

Якщо foo.oі bar.oобидва посилаються на символ, ratтоді libdogвін вирішить foo.o's ratі libcatвирішить bar.o' s rat.


0

Просто думка .. не перевірена і не перевірена, і це могло б стати позначкою, але ви думали про те, щоб написати адаптер для класу, який ви використовуєте, з більш простих фреймворків ... або принаймні їх інтерфейсів?

Якби ви писали обгортку навколо найпростішого з фреймворків (або того, хто має найменший доступ до інтерфейсів), неможливо було б скласти цю обгортку в бібліотеку. Зважаючи на те, що бібліотека є попередньою компіляцією і потрібно розповсюджувати лише її заголовки, ви ефективно ховатимете базовий фреймворк і зможете поєднати його з другим фреймворком із зіткненням.

Звичайно, я вдячний, що, ймовірно, будуть випадки, коли вам потрібно одночасно використовувати клас з обох рамок, проте ви можете надати фабрикам для подальших адаптерів класу цього фреймворку. На звороті цієї точки, я думаю, вам знадобиться трохи рефакторингу, щоб витягти інтерфейси, які ви використовуєте, з обох фреймворків, які повинні забезпечити хороший вихідний пункт для створення вашої обгортки.

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

Знову ж таки, жодним чином не доведено, але відчувалося, як додавати перспективу. сподіваюся, що це допоможе :)


-1

Якщо у вас є дві рамки з однаковою назвою функції, ви можете спробувати динамічно завантажити рамки. Це буде неелегантно, але можливо. Як це зробити з класами Objective-C, я не знаю. Я здогадуюсь, що у NSBundleкласі будуть методи, які завантажуватимуть певний клас.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.