Objective - C categories in static library

Czy możesz mi podpowiedzieć, jak poprawnie połączyć statyczną bibliotekę z projektem iPhone ' a. Używam statyczny projekt biblioteki dodany do projektu aplikacji jako bezpośrednie zależności (target - > ogólne - > bezpośrednie zależności) i wszystko działa OK, ale kategorie. Kategoria zdefiniowana w bibliotece statycznej nie działa w aplikacji.

Więc moje pytanie brzmi jak dodać statyczną bibliotekę z niektórymi kategoriami do innego projektu?

I ogólnie, jakie są najlepsze praktyki w kodzie projektu aplikacji z innych projektów?

Author: ThomasW, 2010-04-02

6 answers

Rozwiązanie: począwszy od Xcode 4.2, wystarczy przejść do aplikacji, która łączy się z biblioteką (nie samą biblioteką) i kliknąć projekt w Nawigatorze projektu, kliknąć cel aplikacji, a następnie zbudować Ustawienia, a następnie wyszukać "inne flagi linkera", kliknąć przycisk + i dodać "- ObjC". '- all_load ' i '- force_load ' nie są już potrzebne.

Szczegóły: Znalazłem kilka odpowiedzi na różnych forach, blogach i dokumentach apple. Teraz staram się zrobić krótkie podsumowanie mojego poszukiwania i eksperymenty.

Problem został spowodowany przez (cytat z Apple Technical Q & A QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html):

Objective-C nie definiuje linkera symbole dla każdej funkcji (lub metody, w Objective-C) - zamiast tego linker symbole są generowane tylko dla każdego klasy. W przypadku rozszerzenia istniejącego wcześniej klasa z kategoriami, linker robi Nie wiem, jak powiązać kod obiektowy z klasy podstawowej realizacja i realizacja kategorii. To zapobiega obiektom utworzonym w wynikająca z odpowiedzi aplikacja do selektora zdefiniowanego w Kategoria.

I ich rozwiÄ…zanie:

Aby rozwiązać ten problem, statyczne biblioteka powinna przejść opcję-ObjC do łącznika. Flaga ta powoduje linker do wczytania KAŻDEGO pliku Obiektowego w biblioteka, która definiuje Objective-klasa lub Kategoria C. While opcja ta zazwyczaj skutkuje większy plik wykonywalny (ze względu na dodatkowe kod obiektu załadowany do aplikacji), pozwoli na skuteczne tworzenie skutecznych Objective - C biblioteki statyczne, które zawiera Kategorie na istniejących klasy.

I jest też rekomendacja w FAQ rozwoju iPhone ' a:

Jak połączyć wszystkie Objective-C zajęcia w bibliotece statycznej? Set the Inne flagi linkera budują ustawienie do - ObjC.

Oraz opisy FLAG:

-all_load wczytuje wszystkich członków statycznych bibliotek archiwalnych.

-ObjC wczytuje wszystkich członków statycznych bibliotek archiwalnych, które implementują Objective-klasa lub Kategoria C.

-force_load (path_to_archive) wczytuje wszystkie elementy statyczne biblioteka archiwum. Uwaga: - all_load zmusza wszystkich członków wszystkich archiwów do być załadowany. Ta opcja pozwala na celować w konkretne archiwum.

* możemy użyć force_load, aby zmniejszyć app rozmiar binarny i aby uniknąć konfliktów, które all_load może powodować w niektórych przypadkach.

Tak, działa z *.plik dodany do projektu. Jednak miałem problemy z projektem lib dodanym jako bezpośrednia zależność. Ale później okazało się, że to moja wina - bezpośredni projekt zależności prawdopodobnie nie został dodany poprawnie. Kiedy go usunę i dodam jeszcze raz krokami:

  1. przeciągnij i upuść plik projektu lib w projekcie aplikacji (lub dodaj go za pomocą projektu->Dodaj do projektu...).
  2. kliknij strzałkę w ikonę projektu lib - mylib.nazwa pliku pokazana, przeciągnij ten mylib.plik i wrzuć go do Target - > Link Binary z grupą Library.
  3. Otwórz informacje o celu na stronie pięści (ogólne) i dodaj my lib do listy zależności
Po tym Wszystko działa OK. W moim przypadku wystarczyła flaga "- ObjC".

Mnie też interesował pomysł z http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html{[12]blog. Autor mówi, że może używać kategorii z lib bez ustawiania-all_load flaga or-ObjC. On po prostu dodać do kategorii H / M pliki puste atrapy class interface / implementacja wymusić linker użyć tego pliku. I tak, ta sztuczka wykona robotę.

Ale autor powiedział również, że nawet nie stworzył instancji atrapy obiektu. Jak już znalazłem, powinniśmy jawnie nazwać jakiś "prawdziwy" kod z pliku kategorii. Więc przynajmniej funkcja klasy powinna być wywołana. A my nawet nie potrzebujemy zajęć. Pojedyncza funkcja c robi to samo.

Więc jeśli zapiszemy pliki lib jako:

// 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

I jeśli wywołamy useMyLib (); anywhere in App project wtedy w dowolnej klasie możemy użyć metody logself category;

[self logSelf];

I więcej blogów na temat:

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

 222
Author: Vladimir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-04 05:48:36

Odpowiedź od Vladimira jest całkiem dobra, jednak chciałbym dać trochę więcej wiedzy na ten temat. Może kiedyś ktoś znajdzie moją odpowiedź i może okazać się pomocna.

Kompilator przekształca pliki źródłowe (.c,. cc,.cpp,m) do plików obiektowych (.o). Na jeden plik źródłowy przypada jeden plik obiektowy. Pliki obiektowe zawierają symbole, kod i dane. Pliki obiektowe nie są używane bezpośrednio przez system operacyjny.

Teraz podczas budowania dynamicznej biblioteki (.dylib), ramy, ładowany pakiet (.bundle) lub wykonywalny plik binarny, te pliki obiektowe są połączone ze sobą przez łącznik, aby wytworzyć coś, co system operacyjny uważa za" użyteczne", np. coś, co może bezpośrednio załadować do określonego adresu pamięci.

Jednak podczas budowania biblioteki statycznej, wszystkie te pliki obiektowe są po prostu dodawane do dużego pliku archiwum, stąd rozszerzenie bibliotek statycznych (.a dla archiwum). Więc an .plik jest niczym innym jak archiwum obiektu (.o) pliki. Pomyśl o archiwum TAR lub Archiwum ZIP bez kompresji. Po prostu łatwiej jest skopiować pojedynczy ./ align = "left" / o pliki (podobne do Javy, gdzie się pakuje .pliki klas do a .archiwum jar dla łatwej dystrybucji).

Podczas linkowania binarnego do statycznej biblioteki (=archiwum), linker otrzyma tabelę wszystkich symboli w archiwum i sprawdzi, które z tych symboli są odwołane przez binaria. Tylko pliki obiektowe zawierające odniesienia symboli są faktycznie ładowane przez łącznik i są brane pod uwagę w procesie łączenia. Np. jeśli Twoje archiwum ma 50 plików obiektowych, ale tylko 20 zawiera symbole używane przez binarne, tylko te 20 jest ładowane przez łącznik, pozostałe 30 są całkowicie ignorowane w procesie łączenia.

Działa to całkiem dobrze dla kodu C i C++, ponieważ te języki starają się zrobić jak najwięcej podczas kompilacji (choć C++ ma również pewne funkcje tylko dla środowiska uruchomieniowego). Obj-C to jednak inny rodzaj języka. Obj-C w dużym stopniu zależy od funkcji runtime i wielu Obj-C funkcje są w rzeczywistości funkcjami tylko uruchomieniowymi. Klasy Obj-C mają w rzeczywistości symbole porównywalne z funkcjami C lub globalnymi zmiennymi C (przynajmniej w bieżącym uruchomieniu Obj-C). Linker może sprawdzić, czy klasa jest odwołana, czy nie, więc może określić klasę, która jest w użyciu, czy nie. Jeśli używasz klasy z pliku Obiektowego w bibliotece statycznej, ten plik obiektowy zostanie załadowany przez łącznik, ponieważ łącznik widzi używany symbol. Kategorie to funkcja tylko dla środowiska uruchomieniowego, Kategorie nie są symbolami takimi jak Klasy czy funkcje, a to również oznacza, że linker nie może określić, czy kategoria jest używana, czy nie.

Jeśli łącznik ładuje plik obiektowy zawierający kod Obj - C, wszystkie jego części Obj-C są zawsze częścią etapu łączenia. Więc jeśli plik obiektowy zawierający Kategorie jest ładowany, ponieważ każdy symbol z niego jest uważany za "w użyciu" (czy to klasa, czy to funkcja, czy to zmienna globalna), kategorie są również ładowane i będą dostępne w czasie wykonywania. Jednak jeśli sam plik obiektowy nie zostanie załadowany, kategorie w nim nie będą dostępne w czasie wykonywania. Plik obiektowy zawierający tylko Kategorie jest nigdy załadowany, ponieważ zawiera brak symboli linker mógłby kiedykolwiek rozważyć "w użyciu". I to jest cały problem.

Zaproponowano kilka rozwiązań, a teraz, gdy wiesz, jak to wszystko gra razem, spójrzmy jeszcze raz na proponowane rozwiązanie:]}

  1. Jednym z rozwiązań jest dodanie -all_load do wywołania linkera. Co? czy ta flaga naprawdę wystarczy? W rzeczywistości mówi linkerowi następujące "Load all object files of all archives niezależnie od tego, czy widzisz jakiś symbol w użyciu, czy nie ". Oczywiście, to zadziała, ale może również produkować dość duże pliki binarne.

  2. Innym rozwiązaniem jest dodanie -force_load do wywołania linkera wraz ze ścieżką do archiwum. Ta flaga działa dokładnie tak jak -all_load, ale tylko dla określonego archiwum. Oczywiście to również zadziała.

  3. Na najpopularniejszym rozwiązaniem jest dodanie -ObjC do wywołania linkera. Co właściwie zrobi ta flaga linkera? Ta flaga mówi linkerowi " załaduj wszystkie pliki obiektowe ze wszystkich archiwów, jeśli widzisz, że zawierają one dowolny kod Obj-C ". I "dowolny kod Obj-C" obejmuje kategorie. To również zadziała i nie wymusi załadowania plików obiektowych, które nie zawierają kodu Obj-C (są one ładowane tylko na żądanie).

  4. Innym rozwiązaniem jest raczej nowe ustawienie kompilacji Xcode Perform Single-Object Prelink. Co będzie to ustawienie wystarczy? Jeśli jest włączona, wszystkie pliki obiektowe (pamiętaj, że jest jeden na plik źródłowy) są scalane razem w jeden plik obiektowy (który nie jest prawdziwym połączeniem, stąd nazwa PreLink) i ten pojedynczy plik obiektowy (czasami nazywany również "plikiem obiektu głównego") jest następnie dodawany do archiwum. Jeśli teraz jakikolwiek symbol głównego pliku obiektowego jest rozważany w użyciu, cały plik obiektu głównego jest rozważany w użyciu, a zatem wszystkie jego części Objective-C są zawsze ładowane. A ponieważ zajęcia są normalne symbole, wystarczy użyć jednej klasy z takiej statycznej biblioteki, aby również uzyskać wszystkie kategorie.

  5. Ostatecznym rozwiązaniem jest trik, który Vladimir dodał na samym końcu swojej odpowiedzi. Umieść " fałszywy symbol " w dowolnym pliku źródłowym deklarującym tylko Kategorie. Jeśli chcesz użyć którejś z kategorii w czasie wykonywania, upewnij się, że w jakiś sposób odwołujesz się do fałszywego symbolu podczas kompilacji, ponieważ powoduje to, że plik obiektowy jest ładowany przez linker, a tym samym wszystkie Kod Obj-C w nim. Np. może to być funkcja z pustym ciałem funkcji (która nic nie zrobi, gdy zostanie wywołana) lub może to być zmienna globalna, do której można uzyskać dostęp (np. globalna int raz przeczytana lub raz zapisana, to wystarczy). W przeciwieństwie do wszystkich innych powyższych rozwiązań, To rozwiązanie przenosi kontrolę nad kategoriami dostępnymi w czasie wykonywania do skompilowanego kodu (jeśli chce, aby były one połączone i dostępne, uzyskuje dostęp do symbolu, w przeciwnym razie nie uzyskuje dostępu do symbolu i linker zignoruje it).

To wszystko. [[19]} Oh, czekaj, jest jeszcze jedna rzecz:
Linker ma opcję o nazwie -dead_strip. Do czego służy ta opcja? Jeśli linker zdecydował się załadować plik obiektowy, wszystkie symbole pliku obiektowego stają się częścią połączonego pliku binarnego, niezależnie od tego, czy są używane, czy nie. Np. plik obiektowy zawiera 100 funkcji, ale tylko jedna z nich jest używana przez plik binarny, wszystkie 100 funkcji są nadal dodawane do pliku binarnego, Ponieważ pliki obiektowe są dodawane jako całość lub w ogóle nie są dodawane. Częściowe dodawanie pliku obiektowego zwykle nie jest obsługiwane przez linkery.

Jeśli jednak powiesz linkerowi "martwy Pasek", linker najpierw doda wszystkie pliki obiektowe do pliku binarnego, rozwiąże wszystkie odwołania i na koniec przeskanuje binarne symbole w poszukiwaniu nieużywanych (lub tylko używanych przez inne symbole nieużywane). Wszystkie symbole, które nie są używane, są następnie usuwane w ramach etapu optymalizacji. W powyższym przykładzie usuwa się 99 nieużywanych funkcji jeszcze raz. Jest to bardzo przydatne, jeśli używasz opcji takich jak -load_all, -force_load lub Perform Single-Object Prelink ponieważ te opcje mogą łatwo wysadzić rozmiary binarne dramatycznie w niektórych przypadkach i martwy stripping usunie nieużywany kod i dane ponownie.

Dead stripping działa bardzo dobrze dla kodu C (np. nieużywane funkcje, zmienne i stałe są usuwane zgodnie z oczekiwaniami), a także działa całkiem dobrze dla C++ (np. nieużywane klasy są usuwane). Nie jest idealny, w niektórych przypadkach niektóre symbole nie są usuwane, mimo że byłoby w porządku, aby je usunąć, ale w większości przypadków działa to całkiem dobrze dla tych języków.

A co z Obj-C? Zapomnij o tym! Ponieważ Obj-C jest językiem runtime-feature, kompilator nie może powiedzieć w czasie kompilacji, czy symbol jest rzeczywiście używany, czy nie. Np. Klasa Obj-C nie jest używana, jeśli nie ma kodu bezpośrednio odsyłającego do niej, prawda? Źle! Możesz dynamicznie zbudować łańcuch zawierający nazwę klasy, zażądać wskaźnika klasy dla tej nazwy i dynamicznie przydzielaj klasę. Np. zamiast
MyCoolClass * mcc = [[MyCoolClass alloc] init];

Też bym napisał

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

W obu przypadkach {[15] } jest odniesieniem do obiektu klasy "MyCoolClass", ale nie ma bezpośredniego odniesienia do tej klasy w drugiej próbce kodu (nawet nazwa klasy jako statyczny łańcuch). Wszystko dzieje się tylko w czasie wykonywania. I to nawet jeśli klasy są w rzeczywistości prawdziwymi symbolami. Jeszcze gorzej jest z kategoriami, ponieważ nie są one nawet prawdziwe symbole.

Więc jeśli masz statyczną bibliotekę z setkami obiektów, ale większość twoich binariów potrzebuje tylko kilku z nich, możesz nie używać powyższych rozwiązań (1) do (4). W przeciwnym razie skończysz z bardzo dużymi binariami zawierającymi wszystkie te klasy, mimo że większość z nich nigdy nie jest używana. Dla klas zwykle nie potrzebujesz żadnego specjalnego rozwiązania, ponieważ klasy mają prawdziwe Symbole i tak długo, jak odwołujesz się do nich bezpośrednio (nie jak w drugiej próbce kodu), linker będzie zidentyfikuj ich użycie całkiem dobrze na własną rękę. W przypadku kategorii rozważ rozwiązanie (5), ponieważ umożliwia ono dołączanie tylko tych kategorii, których naprawdę potrzebujesz.

Np. jeśli chcesz mieć kategorię dla NSData, np. dodając do niej metodę kompresji/dekompresji, utworzysz plik nagłówkowy:

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

void import_NSData_Compression ( );

I plik implementacji

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

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

void import_NSData_Compression ( ) { }

Teraz upewnij się, że gdziekolwiek w Twoim kodzie import_NSData_Compression() jest wywołany. Nie ma znaczenia, gdzie jest wywoływany i jak często jest wywoływany. Właściwie to nie musi być w ogóle nazywany, wystarczy, jeśli linker tak myśli. Możesz np. umieścić w dowolnym miejscu swojego projektu następujący kod:

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

Nie musisz nigdy wywoływać importCategories() w swoim kodzie, atrybut sprawi, że kompilator i linker uwierzą, że został wywołany, nawet jeśli tak nie jest.

I ostatnia wskazówka:
Jeśli dodasz -whyload do końcowego wywołania łącza, linker wyświetli w dzienniku budowy plik obiektu, z którego biblioteki został załadowany, ponieważ w tym symbol w użyciu. Wyświetli tylko pierwszy symbol rozważany w użyciu, ale niekoniecznie jest to jedyny symbol używany w tym pliku obiektowym.

 110
Author: Mecki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-18 23:45:52

Ten problem został naprawiony w LLVM . Poprawka jest dostarczana jako część LLVM 2.9 pierwszą wersją Xcode zawierającą poprawkę jest Xcode 4.2 Wysyłka z LLVM 3.0. użycie -all_load LUB -force_load nie jest już potrzebne podczas pracy z XCode 4.2 -ObjC jest nadal potrzebny.

 24
Author: tonklon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-06 13:40:09

Oto, co musisz zrobić, aby całkowicie rozwiązać ten problem podczas kompilacji statycznej biblioteki:

Albo przejdź do Xcode Build Settings i ustaw Wykonywanie pojedynczego obiektu Prelink na yes lub GENERATE_MASTER_OBJECT_FILE = YES w pliku konfiguracyjnym build.

Domyślnie linker generuje .o plik dla każdego .plik M. Więc kategorie się różnią .o files. Gdy linker patrzy na statyczną bibliotekę .o plikach, nie tworzy indeksu wszystkich symboli na klasę (Runtime będzie, nie ma znaczenia co).

Ta dyrektywa poprosi linkera o spakowanie wszystkich obiektów w jeden duży .o pliku i przez to wymusza linker, który przetwarza bibliotekę statyczną, aby uzyskać indeks wszystkich kategorii klas.

Mam nadzieję, że to wyjaśni.
 16
Author: amosel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-13 19:40:11

Jednym z czynników, o którym rzadko się wspomina, gdy pojawia się dyskusja o linkowaniu biblioteki statycznej, jest fakt, że musiszrównież zawierać same kategorie w fazach budowania->Kopiuj pliki i kompilować źródła samej biblioteki statycznej .

Apple również nie podkreśla tego faktu w swoich niedawno opublikowanych przy użyciu bibliotek statycznych w iOS .

Spędziłem cały dzień próbując różnych odmian-objC i-all_load itp.. ale nic nie wyszło z to.. To pytanie zwróciło moją uwagę na tę kwestię. (nie zrozum mnie źle.. nadal musisz robić rzeczy objective.. ale to nie tylko to).

Kolejną akcją, która zawsze mi pomagała, jest to, że zawsze samodzielnie buduję dołączoną bibliotekę statyczną.. następnie buduję aplikację..

 9
Author: abbood,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:31

Prawdopodobnie musisz mieć kategorię w nagłówku "public" biblioteki statycznej: # import " MyStaticLib.h "

 -1
Author: christo16,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-02 15:34:36