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


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

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

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

6 149

6 ответов:

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

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

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

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

И их решение:

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

И есть также рекомендация в FAQ по разработке iPhone:

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

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

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

-ObjC загружает все члены библиотек статического архива, реализующих Цель-класс или категория С.

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

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

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

  1. перетащите файл проекта lib в проект приложения (или добавьте его с помощью команды Project- > Add to project...).
  2. нажмите на стрелку на значке проекта lib - библиотеки MyLib.имя файла показано, перетащите этот mylib.файл и поместите его в Target -> Link Binary With Library group.
  3. откройте информацию о цели на странице fist (General) и добавьте мой lib в список зависимостей

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

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

Но автор также сказал, что он даже не создал экземпляр фиктивного объекта. Мм... как я обнаружил, мы должны явно вызвать некоторый "реальный" код из файла категории. Так что, по крайней мере, функция класса должна быть вызвана. И нам даже не нужен фиктивный класс. Одиночная функция 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 (); в любом месте проекта приложения тогда в любом классе мы можем использовать метод категории 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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это все люди.

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

Однако, если вы скажете компоновщику "dead strip", компоновщик сначала добавит все объектные файлы в двоичный файл, разрешит все ссылки и, наконец, проверит двоичный файл на наличие неиспользуемых символов (или только используемых другими неиспользуемыми символами). Все символы, обнаруженные неиспользуемыми, затем удаляются в рамках этапа оптимизации. В приведенном выше примере удаляются 99 неиспользуемых функций снова. Это очень полезно, если вы используете такие опции, как -load_all, -force_load или Perform Single-Object Prelink, потому что эти опции могут легко взорвать двоичные размеры резко в некоторых случаях, и мертвая зачистка снова удалит неиспользуемый код и данные. Dead stripping очень хорошо работает для кода на языке Си (например, неиспользуемые функции, переменные и константы удаляются, как и ожидалось), а также довольно хорошо работает для 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", но во втором примере кода Нет прямой ссылки на этот класс (нет даже имени класса в виде статической строки). Все происходит только во время выполнения. И это несмотря на то, что классы являются на самом деле реальными символами. Это еще хуже для категорий, так как они даже не реальны атрибутика.

Таким образом, если у вас есть статическая библиотека с сотнями объектов, но большинство ваших двоичных файлов нуждаются только в нескольких из них, вы можете предпочесть не использовать решения (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 к последнему вызову link, компоновщик напечатает в журнале сборки, какой объектный файл из какой библиотеки он загрузил, потому что о каком символе идет речь. Он будет печатать только первый символ, рассматриваемый в использовании, но это не обязательно единственный символ, используемый в этом объектном файле.

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

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

Как зайти в настройки сборки в Xcode и установить выполнить один-объект, механизм предварительного связывания " да " или GENERATE_MASTER_OBJECT_FILE = YES в файле конфигурации сборки.

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

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

Надеюсь, что это проясняет его.

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

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

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

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

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