Как разрешить конфликт имен символов в интерфейсе Cocoa touch

Я разработал концептуальную структуру Cocoa и у меня возникли проблемы с сторонними статическими кластерными классами, встроенными внутри нее.

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

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

Любая помощь или направление будут приветствоваться!

Ответы

Ответ 1

CocoaPods может помочь вам решить проблему с дублирующимися символами.
Ниже я подробно объяснил, как это сделать:

Определение
Позвольте сделать некоторые определения для более простых объяснений:
MyFramework - рамки, которые вы разрабатываете.
MyApplication - приложение, использующее MyFramework.
OtherFramework - сторонняя структура, которая используется в MyFramework и MyApplication.

Проблема
Насколько я понимаю, проблема в том, что Xcode не удается построить с ошибкой "дублированных символов" в OtherFramework.

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

1) MyFramework должен ссылаться на OtherFramework на CocoaPods:

// MyFramework Podfile
use_frameworks!
pod "OtherFramework"

2) MyApplication должен ссылаться на OtherFramework на CocoaPods:

// MyApplication Podfile
use_frameworks!
pod "OtherFramework"

3) MyApplication может использовать любой механизм для ссылки на MyFramework (с помощью CocoaPods или Drag & Drop framework для проекта).

4) OtherFramework должен быть построен с помощью CocoaPods.
Если он еще не построен с CocoaPods, вы можете сделать это сами.
Для этой цели вам необходимо создать OtherFramework.podspec и, возможно, отправить его в частный репозиторий CocoaPods. Неважно, если у вас есть исходные файлы или просто OtherFramework.framework bundle. Подробнее о создании CocoaPod здесь.

Ответ 2

TL; DR

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

В общем, относительно статической привязки

Это еще одна версия классической проблемы "ада". Теоретически существует два решения при связывании объектных файлов статически:

  • Укажите свою зависимость и позвольте пользователю вашей инфраструктуры решить эту проблему в своей системе сборки. Некоторые идеи:

    • Внешние системы, такие как CocoaPods и Carthage, помогут вам избежать ограничений форсирования вашей системы сборки пользователей (т.е. пользователь может не использовать CocoaPods).

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

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

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

    • Включите зависимость в качестве исходных файлов в вашем проекте. Используйте макросы #define для переименования символов (или используя только символы static).
    • Создание зависимости от источника, переименование символов на этапе предварительной сборки кода (или один).
    • Построение всех, как вы, а затем переименование "внутренних" символов в построенной библиотеке. Это может привести к ошибкам, тем более что swift/obj-c имеют динамические аспекты. См. этот вопрос, например.

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

При использовании динамических фреймворков

Если вы строите динамическую структуру, наилучшей практикой является "2.". выше. В частности, динамическое связывание препятствует дублированию символьной проблемы, так как ваша библиотека может ссылаться на свою версию сторонней библиотеки независимо от библиотеки, которую использует любой клиент.

Вот простой пример (с использованием C для простоты, но должен применяться к Swift, ObjC, С++ или чему-либо связанному с помощью ld):

Отметим также, что я предполагаю, что ваша сторонняя библиотека написана на C/objC/С++, так как классы Swift могут (AFAIK) не жить в статических библиотеках.

myapp.c

#include <stdio.h>

void main() {
  printf("Hello from app\n");

  third_party_func();

  my_dylib_func();
}

mylib.c

#include <stdio.h>

void my_dylib_func() {
  printf("Now in dylib\n");
  third_party_func();
  printf("Now exiting dylib\n");
}

thirdparty_v1.c

#include <stdio.h>

void third_party_func() {
  printf("Third party func, v1\n");
}

thirdparty_v2.c

#include <stdio.h>

void third_party_func() {
  printf("Third party func, v2\n");
}

Теперь давайте сначала скомпилируем файлы:

$ clang -c *.c
$ ls *.o
myapp.o         mylib.o         thirdparty_v1.o thirdparty_v2.o

Затем создайте статические и динамические библиотеки

$ ar -rcs libmystatic.a mylib.o thirdparty_v1.o
$ ld -dylib mylib.o thirdparty_v1.o -lc -o libmydynamic.dylib
$ ls libmy* | xargs file
libmydynamic.dylib: Mach-O 64-bit dynamically linked shared library x86_64
libmystatic.a:      current ar archive random library

Теперь, если мы скомпилируем статически с помощью (неявно) предоставленной реализации:

clang -omyapp myapp.o -L. -lmystatic thirdparty_v2.o && ./myapp
Hello from app
Third party func, v1
Now in dylib
Third party func, v1
Now exiting dylib

Теперь это было для меня совершенно неожиданным, так как я ожидал ошибку "дублированного символа". Оказывается, ld на OSX молча заменяет символы, заставляя пользовательское приложение заменять символы в моей библиотеке. Причина задокументирована в man-странице ld:

ld будет вытаскивать файлы .o из статической библиотеки, если это необходимо, чтобы разрешить некоторую ссылку на символ

Это соответствовало бы пункту 1 выше. (С другой стороны, выполнение приведенного выше примера в Linux обязательно дает ошибку "дубликат символа" ).

Теперь дайте ссылку динамически, как в вашем примере:

clang -omyapp myapp.o -L. -lmydynamic thirdparty_v2.o && ./myapp
Hello from app
Third party func, v2
Now in dylib
Third party func, v1
Now exiting dylib

Как вы можете видеть, ваша динамическая библиотека теперь ссылается на свою версию (v1) статической библиотеки, в то время как само приложение будет использовать другую версию (v2). Это, вероятно, то, что вы хотите, и по умолчанию это. Разумеется, причина в том, что теперь есть два бинарных файла, каждый со своим набором символов. Если мы проверим .dylib, мы увидим, что он по-прежнему экспортирует стороннюю библиотеку:

$ nm libmydynamic.dylib 
0000000000000ec0 T _my_dylib_func
                 U _printf
0000000000000f00 T _third_party_func
                 U dyld_stub_binder

И конечно, если мы не ссылаемся на статическую библиотеку в нашем приложении, компоновщик найдет символ в .dylib:

$ clang -omyapp myapp.o -L. -lmydynamic  && ./myapp
Hello from app
Third party func, v1
Now in dylib
Third party func, v1
Now exiting dylib

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

$ ld -dylib mylib.o -unexported_symbol '_third_party_*' thirdparty_v1.o -lc -o libmydynamic_no3p.dylib
$ nm -A libmydyn*
...
libmydynamic.dylib: 0000000000000f00 T _third_party_func
libmydynamic_no3p.dylib: 0000000000000f00 t _third_party_func 

(A capital T означает, что символ является общедоступным, строчный T означает, что символ является закрытым).

Повторите попытку в следующем примере:

$ clang -omyapp myapp.o -L. -lmydynamic_no3p  && ./myapp
Undefined symbols for architecture x86_64:
  "_third_party_func", referenced from:
      _main in myapp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

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

Как насчет того, действительно ли вы хотите 1. в динамических рамках

Например, вашей библиотеке может понадобиться точная версия сторонней библиотеки, которую предоставляет ваш клиент.

Для этого также имеется флаг компоновщика: -undefined dynamic_lookup.

$ ld -dylib mylib.o -undefined dynamic -lc -o libmydynamic_undef.dylib
$ clang -omyapp myapp.o -L. -lmydynamic_undef thirdparty_v2.o && ./myapp
Hello from app
Third party func, v2
Now in dylib
Third party func, v2
Now exiting dylib

Недостатком является, конечно, то, что он потерпит неудачу во время выполнения, если ваш клиент не включит статическую библиотеку.

Ответ 3

Вы всегда можете использовать классы из фреймворка:

import Alamofire

let request = Alamofire.Request(...)

И если у вас есть класс Request в вашей собственной структуре, вы можете использовать его таким же образом:

import YourFramework

let request = YourFramework.Request(...)

Там не было бы конфликтов.

Ответ 4

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