Objective-C категории в статической библиотеке

Можете ли вы посоветовать мне, как правильно связать статическую библиотеку с проектом iPhone. Я использую проект статической библиотеки, добавленный в проект приложения, как прямую зависимость (target → general → direct dependencies), и все работает нормально, но категории. Категория, определенная в статической библиотеке, не работает в приложении.

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

И вообще, что лучше всего использовать в коде проекта приложения из других проектов?

Ответ 1

Решение: Как и в Xcode 4.2, вам нужно только перейти к приложению, которое связано с библиотекой (а не самой библиотекой) и щелкнуть проект в Навигаторе проектов, нажмите на целевую страницу приложения, затем выполните настройки, затем найдите "Другие флаги компоновщика", нажмите кнопку + и добавьте "-ObjC". '-all_load' и '-force_load' больше не нужны.

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

Проблема была вызвана (цитата из Apple Technical Q & A QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html):

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

И их решение:

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

а также есть рекомендации по iPhone Development FAQ:

Как связать все Objective-Cклассы в статической библиотеке? Установить Другие флаги компоновщика устанавливают для -ObjC.

и описания флагов:

- all_load Загружает все элементы статических архивных библиотек.

- ObjC Загружает все элементы статических архивных библиотек, которые реализуют Objective-C класс или категория.

- force_load (path_to_archive) Загружает все члены указанного статического архивная библиотека. Примечание: -all_load заставляет всех членов всех архивов загружаться. Этот параметр позволяет вам укажите конкретный архив.

* мы можем использовать force_load для уменьшения двоичного размера приложения и избежать конфликтов, которые могут возникнуть при использовании all_load в некоторых случаях.

Да, он работает с *.a файлами, добавленными в проект. Тем не менее у меня были проблемы с проектом lib, добавленным как прямая зависимость. Но позже я обнаружил, что это была моя ошибка - прямой проект зависимости, возможно, не был добавлен должным образом. Когда я удалю его и снова добавлю шаги:

  • Файл проекта Drag & drop lib в проекте приложения (или добавьте его в Project- > Добавить в проект...).
  • Нажмите на стрелку в значке проекта проекта - отобразите имя файла mylib.a, перетащите этот файл mylib.a и поместите его в Target → Link Binary With Library group.
  • Откройте целевую информацию в кулачной странице (Общие) и добавьте мой список lib в список зависимостей

после этого все работает нормально. Флаг "-ObjC" был достаточно в моем случае.

Мне также была интересна идея из http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html блога. Автор говорит, что он может использовать категорию из lib без установки -all_load или -ObjC-флага. Он просто добавляет в категорию h/m файлы пустой интерфейс/интерфейс класса dummy, чтобы заставить компоновщик использовать этот файл. И да, этот трюк выполняет эту работу.

Но автор также сказал, что он даже не создал экземпляр фиктивного объекта. Mm... Как я нашел, мы должны явно называть некоторый "реальный" код из файла категории. Поэтому должна быть вызвана функция класса. И нам даже не нужен фиктивный класс. Одинарная функция c делает то же самое.

Итак, если мы пишем файлы lib как:

// mylib.h
void useMyLib();

@interface NSObject (Logger)
-(void)logSelf;
@end


// mylib.m
void useMyLib(){
    NSLog(@"do nothing, just for make mylib linked");
}


@implementation NSObject (Logger)
-(void)logSelf{
    NSLog(@"self is:%@", [self description]);
}
@end

и если мы будем называть useMyLib(); в любом месте приложения App то в любом классе мы можем использовать метод категории logSelf;

[self logSelf];

И еще блоги по теме:

http://t-machine.org/index.php/2009/10/13/how-to-make-an-iphone-static-library-part-1/

http://blog.costan.us/2009/12/fat-iphone-static-libraries-device-and.html

Ответ 2

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

Компилятор преобразует исходные файлы (.c,.cc,.cpp,.m) в файлы объектов (.o). В исходном файле есть один объектный файл. Файлы объектов содержат символы, код и данные. Объектные файлы не могут использоваться непосредственно операционной системой.

Теперь, когда вы создаете динамическую библиотеку (.dylib), фреймворк, загружаемый пакет (.bundle) или исполняемый двоичный файл, эти объектные файлы связаны между собой компоновщиком для создания чего-то, что операционная система считает "пригодной для использования", например. что-то, что он может напрямую загрузить на определенный адрес памяти.

Однако при создании статической библиотеки все эти объектные файлы просто добавляются в большой файл архива, следовательно, расширяются статические библиотеки (.a для архива). Таким образом, файл .a - это не что иное, как архив файлов объектов (.o). Подумайте о архиве TAR или архиве ZIP без сжатия. Просто проще скопировать один файл .a, а не целую кучу файлов .o(похож на Java, где вы упаковываете файлы .class в архив .jar для легкого распространения).

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

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

Если компоновщик загружает объектный файл, содержащий код Obj-C, все его части Obj-C всегда являются частью этапа компоновки. Таким образом, если объектный файл, содержащий категории, загружается, потому что любой символ из него считается "в использовании" (будь то класс, будь то функция, будь то глобальная переменная), категории также загружаются и будут доступны во время выполнения, Однако, если сам объект объекта не загружен, категории в нем будут недоступны во время выполнения. Объектный файл, содержащий категории только, загружается никогда, потому что он содержит без символов, который компоновщик когда-либо учитывал "при использовании". И здесь вся проблема.

Было предложено несколько решений, и теперь, когда вы знаете, как все это играет вместе, давайте еще раз взглянем на предлагаемое решение:

  • Одним из решений является добавление -all_load к вызову компоновщика. Что на самом деле сделает этот флаг компоновщика? Фактически он сообщает компоновщику следующее "Загружать все объектные файлы всех архивов, независимо от того, видите ли вы какой-либо используемый символ или нет". Конечно, это сработает, но оно также может создавать довольно большие двоичные файлы.

  • Другим решением является добавление -force_load к вызову компоновщика, включая путь к архиву. Этот флаг работает точно так же, как -all_load, но только для указанного архива. Конечно, это тоже будет работать.

  • Самое популярное решение - добавить -ObjC в вызов компоновщика. Что на самом деле сделает этот флаг компоновщика? Этот флаг сообщает компоновщику "Загружать все объектные файлы из всех архивов, если вы видите, что они содержат любой код Obj-C". И "любой код Obj-C" включает категории. Это также будет работать и не будет принудительно загружать объектные файлы, не содержащие кода Obj-C (они по-прежнему загружаются только по запросу).

  • Другим решением является довольно новая настройка сборки Xcode Perform Single-Object Prelink. Что будет делать эта настройка? Если включено, все объектные файлы (помните, что есть один в исходном файле) объединяются в один объектный файл (это не реальная привязка, а следовательно, имя PreLink) и этот единственный объектный файл (иногда также называемый "главным объектом" файл ") затем добавляется в архив. Если теперь используется какой-либо символ файла основного объекта, весь основной файл объекта рассматривается в использовании, и поэтому все его части Objective-C всегда загружаются. И поскольку классы являются нормальными символами, достаточно использовать один класс из такой статической библиотеки, чтобы также получить все категории.

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

Что все люди.

О, подождите, еще одна вещь:
У компоновщика есть опция с именем -dead_strip. Что делает этот вариант? Если компоновщик решил загрузить объектный файл, все символы объектного файла становятся частью связанного двоичного файла, независимо от того, используются они или нет. Например. объектный файл содержит 100 функций, но только один из них используется двоичным кодом, все 100 функций все еще добавляются в двоичный файл, потому что объектные файлы либо добавляются в целом, либо вообще не добавляются. Обычно добавление объектного файла не поддерживается линкерами.

Однако, если вы сообщите компоновщику "мертвую полосу", компоновщик сначала добавит все файлы объектов в двоичный файл, разрешит все ссылки и, наконец, сканирует двоичный код на символы, которые не используются (или используются только другими символы не используются). Все символы, которые не используются, затем удаляются как часть этапа оптимизации. В приведенном выше примере 99 неиспользуемых функций снова удаляются. Это очень полезно, если вы используете такие опции, как -load_all, -force_load или Perform Single-Object Prelink, потому что в некоторых случаях эти параметры могут легко взорвать размеры двоичных файлов, а мертвое удаление снова удалит неиспользуемый код и данные.

Мертвая зачистка очень хорошо работает для кода C (например, неиспользуемые функции, переменные и константы удаляются, как ожидалось), а также хорошо подходит для С++ (например, неиспользуемые классы удаляются). Это не идеально, в некоторых случаях некоторые символы не удаляются, хотя было бы неплохо их удалить, но в большинстве случаев это хорошо работает для этих языков.

Как насчет Obj-C? Забудь об этом! Для Obj-C нет мертвой зачистки. Поскольку Obj-C является языком исполнения во время выполнения, компилятор не может сказать во время компиляции, действительно ли символ используется или нет. Например. класс Obj-C не используется, если нет кода, непосредственно ссылающегося на него, правильно? Неправильно! Вы можете динамически строить строку, содержащую имя класса, запрашивать указатель класса для этого имени и динамически распределять класс. Например. вместо

MyCoolClass * mcc = [[MyCoolClass alloc] init];

Я бы также написал

NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];

В обоих случаях mmc является ссылкой на объект класса "MyCoolClass", но во втором примере кода нет прямой ссылки <класs > (даже не имя класса как статическая строка). Все происходит только во время выполнения. И что даже если классы являются реальными символами. Это еще хуже для категорий, поскольку они не являются даже настоящими символами.

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

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

// NSData+Compress.h
@interface NSData (Compression)
    - (NSData *)compressedData;
    - (NSData *)decompressedData;
@end

void import_NSData_Compression ( );

и файл реализации

// NSData+Compress
@implementation NSData (Compression)
    - (NSData *)compressedData 
    {
        // ... magic ...
    }

    - (NSData *)decompressedData
    {
        // ... magic ...
    }
@end

void import_NSData_Compression ( ) { }

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

__attribute__((used)) static void importCategories ()
{
    import_NSData_Compression();
    // add more import calls here
}

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

И последний совет:
Если вы добавите -whyload в окончательный вызов ссылки, компоновщик будет печатать в журнале сборки, какой файл объекта, из которого он загружался, из-за того, какой символ используется. Он будет печатать только первый символ, рассмотренный при использовании, но это не обязательно единственный символ, используемый этим объектным файлом.

Ответ 3

Эта проблема была исправлена ​​в LLVM. Исправляемые корабли как часть LLVM 2.9. Первая версия Xcode, которая содержит исправление, - это Xcode 4.2, отправляемая с LLVM 3.0. Использование -all_load или -force_load больше не требуется при работе с XCode 4.2 -ObjC.

Ответ 4

Вот что вам нужно сделать, чтобы полностью решить эту проблему при компиляции вашей статической библиотеки:

Перейдите в "Настройки сборки Xcode" и установите "Выполнять предварительную ссылку для одного объекта" на "Да" или " GENERATE_MASTER_OBJECT_FILE = YES в файле конфигурации сборки.

По умолчанию компоновщик создает файл .o для каждого файла .m. Таким образом, категории имеют разные файлы .o. Когда компоновщик смотрит на статические библиотеки .o файлов, он не создает индекс всех символов для каждого класса (Runtime будет, не имеет значения, что).

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

Надеюсь, что это разъяснит.

Ответ 5

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

Apple также не подчеркивает этот факт в недавно опубликованном использовании статических библиотек в iOS.

Я целый день пробовал всевозможные вариации -objC и -all_load и т.д., но ничего не вышло из этого. этот вопрос привел эту проблему к мое внимание. (не поймите меня неправильно.. вам все равно придется делать вещи -objC.. но это больше, чем просто).

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

Ответ 6

Вероятно, вам нужна категория, в которой вы являетесь статичной библиотекой: "public" header: #import "MyStaticLib.h"