Czy możesz mi pomóc, jak poprawnie połączyć bibliotekę statyczną z projektem iPhone'a. Używam projektu biblioteki statycznej dodanego do projektu aplikacji jako bezpośredniej zależności (cel -> 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ć bibliotekę statyczną z niektórymi kategoriami do innego projektu?
I ogólnie, jakie są najlepsze praktyki dotyczące używania w kodzie projektu aplikacji z innych projektów?
iphone
objective-c
static-libraries
categories
Vladimir
źródło
źródło
Odpowiedzi:
Rozwiązanie: od wersji Xcode 4.2 wystarczy przejść do aplikacji łączącej się z biblioteką (a nie samej biblioteki) i kliknąć projekt w Nawigatorze projektu, kliknąć element docelowy aplikacji, a następnie utworzyć ustawienia, a następnie wyszukać „Inne Linker Flags ”, kliknij przycisk + i dodaj„ -ObjC ”. „-all_load” i „-force_load” nie są już potrzebne.
Szczegóły: znalazłem odpowiedzi na różnych forach, blogach i w dokumentach Apple. Teraz spróbuję zrobić krótkie podsumowanie moich poszukiwań i eksperymentów.
Problem został spowodowany przez (cytat z Apple Technical Q&A QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html ):
I ich rozwiązanie:
i jest też zalecenie w FAQ rozwoju iPhone'a:
i opisy flag:
* możemy użyć force_load, aby zmniejszyć rozmiar binarny aplikacji i uniknąć konfliktów, które all_load może powodować w niektórych przypadkach.
Tak, działa z plikami * .a dodanymi do projektu. Jednak miałem problemy z dodaniem projektu lib jako bezpośredniej zależności. Ale później odkryłem, że to moja wina - projekt zależności bezpośredniej prawdopodobnie nie został poprawnie dodany. Kiedy go usunę i dodaję ponownie, wykonując kroki:
potem wszystko działa OK. W moim przypadku wystarczyła flaga „-ObjC”.
Interesował mnie również pomysł z bloga http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html . Autor twierdzi, że może używać kategorii z biblioteki bez ustawiania opcji -all_load lub -ObjC. Po prostu dodaje do kategorii h / m pliki pusty interfejs / implementację klasy fikcyjnej, aby zmusić linker do użycia tego pliku. I tak, ta sztuczka działa.
Ale autor powiedział również, że nawet nie utworzył instancji obiektu zastępczego. Mm… Jak odkryłem, powinniśmy wprost nazwać jakiś „prawdziwy” kod z pliku kategorii. Powinna więc zostać wywołana przynajmniej funkcja klasy. I nawet nie potrzebujemy atrapy klasy. Pojedyncza funkcja c robi to samo.
Więc jeśli napiszemy pliki lib jako:
i jeśli wywołamy useMyLib (); w dowolnym miejscu projektu aplikacji, w dowolnej klasie możemy użyć metody kategorii logSelf;
I więcej blogów na ten 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
źródło
Odpowiedź Vladimira jest właściwie całkiem dobra, jednak chciałbym tutaj podać trochę więcej podstawowej wiedzy. Może któregoś dnia ktoś znajdzie moją odpowiedź i uzna ją za pomocną.
Kompilator przekształca pliki źródłowe (.c, .cc, .cpp, .m) na pliki obiektowe (.o). Istnieje jeden plik obiektowy na plik źródłowy. Pliki obiektów zawierają symbole, kod i dane. Pliki obiektowe nie są bezpośrednio używane przez system operacyjny.
Teraz podczas budowania biblioteki dynamicznej (.dylib), frameworka, ładowalnego pakietu (.bundle) lub wykonywalnego pliku binarnego, te pliki obiektowe są łączone ze sobą przez konsolidator w celu utworzenia czegoś, 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). Zatem plik .a to nic innego jak archiwum plików obiektowych (.o). Pomyśl o archiwum TAR lub archiwum ZIP bez kompresji. Po prostu łatwiej jest skopiować pojedynczy plik .a niż całą masę plików .o (podobnie jak w Javie, gdzie pakujesz pliki .class do archiwum .jar w celu łatwej dystrybucji).
Podczas łączenia pliku binarnego z biblioteką statyczną (= archiwum), konsolidator otrzyma tablicę wszystkich symboli w archiwum i sprawdzi, do których z tych symboli odwołują się pliki binarne. Tylko pliki obiektowe zawierające symbole, do których istnieją odniesienia, są w rzeczywistości ładowane przez konsolidator i są uwzględniane w procesie łączenia. Np. Jeśli twoje archiwum ma 50 plików obiektowych, ale tylko 20 zawiera symbole używane przez plik binarny, tylko tych 20 jest ładowanych przez konsolidator, pozostałe 30 są całkowicie ignorowane w procesie łączenia.
Działa to całkiem dobrze w przypadku kodu C i C ++, ponieważ języki te starają się robić jak najwięcej w czasie kompilacji (chociaż C ++ ma również pewne funkcje tylko w czasie wykonywania). Jednak Obj-C to inny rodzaj języka. Obj-C w dużym stopniu zależy od funkcji środowiska uruchomieniowego, a wiele funkcji Obj-C to w rzeczywistości funkcje tylko w czasie wykonywania. Klasy Obj-C w rzeczywistości mają symbole porównywalne z funkcjami C lub globalnymi zmiennymi C (przynajmniej w obecnym środowisku wykonawczym Obj-C). Konsolidator może sprawdzić, czy istnieje odwołanie do klasy, czy nie, więc może określić, która klasa jest używana, czy nie. Jeśli używasz klasy z pliku obiektowego w bibliotece statycznej, ten plik obiektowy zostanie załadowany przez konsolidator, ponieważ konsolidator widzi używany symbol. Kategorie są funkcją tylko w czasie wykonywania, kategorie nie są symbolami, takimi jak klasy lub funkcje, a to oznacza również, że konsolidator nie może określić, czy kategoria jest używana, czy nie.
Jeśli konsolidator ł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ż dowolny jego symbol jest uważany za „używany” (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 zawarte nie będą dostępne w czasie wykonywania. Plik obiektowy zawierający tylko kategorie nigdy nie jest ładowany, ponieważ nie zawiera symboli, które konsolidator kiedykolwiek uznałby za „w użyciu”. I to jest cały problem tutaj.
Zaproponowano kilka rozwiązań i teraz, gdy już wiesz, jak to wszystko gra razem, spójrzmy jeszcze raz na proponowane rozwiązanie:
Jednym z rozwiązań jest dodanie
-all_load
do wywołania konsolidatora. Co właściwie zrobi ta flaga konsolidatora? Właściwie to mówi linkerowi, co następuje: „ Załaduj wszystkie pliki obiektowe wszystkich archiwów, niezależnie od tego, czy widzisz jakiś używany symbol, czy nie .” Oczywiście to zadziała, ale może również dać dość duże pliki binarne.Innym rozwiązaniem jest dodanie
-force_load
do wywołania konsolidatora, w tym ścieżki do archiwum. Ta flaga działa dokładnie tak samo-all_load
, ale tylko dla określonego archiwum. Oczywiście to również zadziała.Najpopularniejszym rozwiązaniem jest dodanie
-ObjC
do wywołania konsolidatora. Co właściwie zrobi ta flaga konsolidatora? Ta flaga mówi konsolidatorowi " Załaduj wszystkie pliki obiektów ze wszystkich archiwów, jeśli zobaczysz, że zawierają one kod Obj-C ". „Każdy kod Obj-C” obejmuje kategorie. To również zadziała i nie wymusi ładowania plików obiektowych, które nie zawierają kodu Obj-C (są one nadal ładowane tylko na żądanie).Innym rozwiązaniem jest dość nowe ustawienie kompilacji Xcode
Perform Single-Object Prelink
. Co zrobi to ustawienie? Jeśli ta opcja jest włączona, wszystkie pliki obiektowe (pamiętaj, że jest jeden na plik źródłowy) są łączone razem w jeden plik obiektowy (to nie jest prawdziwe łączenie, stąd nazwa PreLink ) i ten pojedynczy plik obiektowy (czasami nazywany także „obiektem głównym plik ”) jest następnie dodawany do archiwum. Jeśli teraz jakikolwiek symbol głównego pliku obiektowego jest uważany za używany, cały główny plik obiektowy jest uważany za używany, a zatem wszystkie jego części Objective-C są zawsze ładowane. A ponieważ klasy są zwykłymi symbolami, wystarczy użyć jednej klasy z takiej biblioteki statycznej, aby uzyskać również wszystkie kategorie.Ostatnim rozwiązaniem jest sztuczka, którą Vladimir dodał na samym końcu swojej odpowiedzi. Umieść „ fałszywy symbol ” w dowolnym pliku źródłowym, deklarując tylko kategorie. Jeśli chcesz użyć którejkolwiek z kategorii w czasie wykonywania, upewnij się, że w jakiś sposób odwołujesz się do fałszywego symbolu w czasie kompilacji, ponieważ powoduje to, że plik obiektowy jest ładowany przez konsolidator, a tym samym cały kod Obj-C w nim. Np. Może to być funkcja z pustym ciałem funkcji (która nie zrobi nic po wywołaniu) lub może to być zmienna globalna, do której można uzyskać dostęp (np.
int
po przeczytaniu lub napisaniu jest to wystarczające). W przeciwieństwie do wszystkich innych rozwiązań powyżej, to rozwiązanie przenosi kontrolę nad tym, które kategorie są dostępne w czasie wykonywania, na skompilowany kod (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 konsolidator zignoruje) to).To wszystko ludzie.
Och, czekaj, jest jeszcze jedna rzecz:
konsolidator ma opcję o nazwie
-dead_strip
. Co robi ta opcja? Jeśli konsolidator zdecyduje się załadować plik obiektowy, wszystkie symbole pliku obiektowego staną 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 jest nadal dodawanych do pliku binarnego, ponieważ pliki obiektowe są albo dodawane jako całość, albo w ogóle nie są dodawane. Częściowe dodawanie pliku obiektowego nie jest zwykle obsługiwane przez konsolidatory.Jednakże, jeśli powiesz konsolidatorowi "martwy pasek", konsolidator najpierw doda wszystkie pliki obiektowe do pliku binarnego, rozwiąże wszystkie odniesienia i na koniec przeskanuje plik binarny w poszukiwaniu symboli nieużywanych (lub używanych tylko przez inne symbole nie w posługiwać się). Wszystkie symbole, które nie są używane, są następnie usuwane w ramach etapu optymalizacji. W powyższym przykładzie 99 nieużywanych funkcji zostało ponownie usuniętych. Jest to bardzo przydatne, jeśli używasz opcji takich jak
-load_all
,-force_load
lubPerform Single-Object Prelink
ponieważ te opcje mogą w niektórych przypadkach łatwo znacznie zwiększyć rozmiary binarne, a martwe usuwanie usunie nieużywany kod i dane ponownie.Dead stripping działa bardzo dobrze w przypadku kodu C (np. Nieużywane funkcje, zmienne i stałe są usuwane zgodnie z oczekiwaniami), a także działa całkiem dobrze w C ++ (np. Nieużywane klasy są usuwane). Nie jest doskonały, 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 całkiem dobrze w tych językach.
A co z Obj-C? Zapomnij o tym! Nie ma martwego strippingu dla Obj-C. Ponieważ Obj-C jest językiem funkcji środowiska uruchomieniowego, 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 bezpośredniego odniesienia do niej, prawda? Źle! Możesz dynamicznie zbudować ciąg zawierający nazwę klasy, zażądać wskaźnika klasy dla tej nazwy i dynamicznie przydzielić klasę. Np. Zamiast
Mógłbym też pisać
W obu przypadkach
mmc
jest to odniesienie do obiektu klasy „MyCoolClass”, ale w drugim przykładzie kodu nie ma bezpośredniego odniesienia do tej klasy (nawet nazwa klasy jako ciąg statyczny). Wszystko dzieje się tylko w czasie wykonywania. I to pomimo tego, że klasy są w rzeczywistości prawdziwymi symbolami. Jeszcze gorzej jest z kategoriami, ponieważ nie są one nawet prawdziwymi symbolami.Więc jeśli masz bibliotekę statyczną z setkami obiektów, ale większość plików binarnych potrzebuje tylko kilku z nich, możesz nie chcieć używać powyższych rozwiązań (1) do (4). W przeciwnym razie otrzymasz bardzo duże pliki binarne zawierające wszystkie te klasy, mimo że większość z nich nigdy nie jest używana. W przypadku 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 drugim przykładzie kodu), konsolidator samodzielnie zidentyfikuje ich użycie. Jednak w przypadku kategorii rozważ rozwiązanie (5), ponieważ umożliwia ono uwzględnienie tylko tych kategorii, których naprawdę potrzebujesz.
Np. Jeśli chcesz mieć kategorię dla NSData, np. Dodając do niej metodę kompresji / dekompresji, możesz utworzyć plik nagłówkowy:
i plik implementacji
Teraz po prostu upewnij się, że
import_NSData_Compression()
wywoływane jest dowolne miejsce w kodzie . Nie ma znaczenia, gdzie jest nazywany ani jak często się go nazywa. Właściwie to wcale nie musi być wywoływane, wystarczy, że linker tak myśli. Np. Możesz umieścić następujący kod w dowolnym miejscu projektu:Nie musisz nigdy wywoływać
importCategories()
swojego kodu, atrybut sprawi, że kompilator i linker uwierzą, że jest wywoływany, nawet jeśli tak nie jest.I ostatnia wskazówka:
jeśli dodasz
-whyload
do końcowego wywołania łącza, konsolidator wydrukuje w dzienniku kompilacji, który plik obiektowy, z której biblioteki załadował z powodu używanego symbolu. Wyświetli tylko pierwszy symbol rozważany jako używany, ale niekoniecznie jest to jedyny symbol używany w tym pliku obiektowym.źródło
-whyload
, próba debugowania, dlaczego linker coś robi, może być dość trudna!Dead Code Stripping
wBuild Settings>Linking
. Czy to jest to samo, co-dead_strip
dodane wOther Linker Flags
?-ObjC
, więc próbowałem twojego hacka, ale narzeka"import_NSString_jsonObject()", referenced from: importCategories() in main.o ld: symbol(s) not found
. Umieściłemimport_NSString_jsonObject
w moim osadzonym Framework o nazwieUtility
i dodaję#import <Utility/Utility.h>
z__attribute__
instrukcją na końcu mojegoAppDelegate.h
.Ten problem został rozwiązany w LLVM . Poprawka jest dostarczana jako część LLVM 2.9. Pierwsza wersja Xcode zawierająca poprawkę to Xcode 4.2 dostarczany z LLVM 3.0. Użycie
-all_load
lub-force_load
nie jest już potrzebne podczas pracy z XCode 4.2-ObjC
jest nadal potrzebne.źródło
-ObjC
Flaga jest nadal potrzebne i zawsze będzie. Obejście polegało na użyciu-all_load
lub-force_load
. I to już nie jest potrzebne. Poprawiłem odpowiedź powyżej.Oto, co musisz zrobić, aby całkowicie rozwiązać ten problem podczas kompilowania biblioteki statycznej:
Przejdź do ustawień kompilacji Xcode i ustaw opcję Perform Single-Object Prelink na YES lub
GENERATE_MASTER_OBJECT_FILE = YES
w pliku konfiguracyjnym kompilacji.Domyślnie konsolidator generuje plik .o dla każdego pliku .m. Dlatego kategorie otrzymują różne pliki .o. Kiedy konsolidator patrzy na pliki .o biblioteki statycznej, nie tworzy indeksu wszystkich symboli na klasę (Runtime zrobi, nie ma znaczenia co).
Ta dyrektywa poprosi konsolidator o spakowanie wszystkich obiektów razem w jeden duży plik .o i przez to zmusi konsolidator, który przetwarza bibliotekę statyczną, aby uzyskać indeks wszystkich kategorii klas.
Mam nadzieję, że to wyjaśnia.
źródło
Jednym z czynników, o których rzadko się wspomina, gdy pojawia się dyskusja na temat łączenia bibliotek statycznych, jest fakt, że należy również uwzględnić same kategorie w fazach kompilacji -> kopiuj pliki i kompiluj źródła samej biblioteki statycznej .
Apple również nie podkreśla tego faktu w niedawno opublikowanych bibliotekach Using Static Libraries w iOS .
Spędziłem cały dzień próbując różnych odmian -objC i -all_load itd., Ale nic z tego nie wyszło ... to pytanie zwróciło moją uwagę na ten problem. (nie zrozum mnie źle… nadal musisz robić rzeczy -objC… ale to coś więcej niż tylko to).
Inną czynnością, która zawsze mi pomagała, jest to, że zawsze najpierw samodzielnie buduję dołączoną bibliotekę statyczną, a następnie buduję aplikację zamykającą.
źródło
Prawdopodobnie musisz mieć kategorię w nagłówku „public” biblioteki statycznej: #import „MyStaticLib.h”
źródło