Können Sie mir zeigen, wie ich die statische Bibliothek richtig mit dem iPhone-Projekt verknüpfe? Ich verwende ein statisches Bibliotheksprojekt, das dem App-Projekt hinzugefügt wurde, als direkte Abhängigkeit (Ziel -> Allgemein -> Direkte Abhängigkeiten) und alles funktioniert in Ordnung, aber Kategorien. Eine in der statischen Bibliothek definierte Kategorie funktioniert in der App nicht.
Meine Frage ist also, wie man eine statische Bibliothek mit einigen Kategorien in ein anderes Projekt einfügt.
Und was ist im Allgemeinen die beste Vorgehensweise für App-Projektcode aus anderen Projekten?
iphone
objective-c
static-libraries
categories
Vladimir
quelle
quelle
Antworten:
Lösung: Ab Xcode 4.2 müssen Sie nur noch zu der Anwendung wechseln, die mit der Bibliothek verknüpft ist (nicht zur Bibliothek selbst), im Projektnavigator auf das Projekt klicken, auf das Ziel Ihrer App klicken, Einstellungen erstellen und dann nach "Andere" suchen Linker Flags ", klicken Sie auf die Schaltfläche + und fügen Sie '-ObjC' hinzu. '-all_load' und '-force_load' werden nicht mehr benötigt.
Details: Ich habe einige Antworten in verschiedenen Foren, Blogs und Apple-Dokumenten gefunden. Jetzt versuche ich, meine Suchanfragen und Experimente kurz zusammenzufassen.
Das Problem wurde verursacht durch (Zitat aus Apple Technische Fragen und Antworten QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html ):
Und ihre Lösung:
und es gibt auch Empfehlungen in den iPhone Development FAQ:
und Flaggenbeschreibungen:
* Wir können force_load verwenden, um die Binärgröße der App zu reduzieren und Konflikte zu vermeiden, die all_load in einigen Fällen verursachen kann.
Ja, es funktioniert mit * .a-Dateien, die dem Projekt hinzugefügt wurden. Ich hatte jedoch Probleme mit dem lib-Projekt, das als direkte Abhängigkeit hinzugefügt wurde. Aber später stellte ich fest, dass es meine Schuld war - das direkte Abhängigkeitsprojekt wurde möglicherweise nicht richtig hinzugefügt. Wenn ich es entferne und mit Schritten wieder hinzufüge:
danach funktioniert alles OK. "-ObjC" -Flag war in meinem Fall genug.
Ich war auch an einer Idee aus dem Blog http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html interessiert . Der Autor sagt, er kann die Kategorie aus lib verwenden, ohne das Flag -all_load oder -ObjC zu setzen. Er fügt der Kategorie h / m-Dateien nur leere Dummy-Klassenschnittstellen / Implementierungen hinzu, um den Linker zu zwingen, diese Datei zu verwenden. Und ja, dieser Trick macht den Job.
Der Autor sagte aber auch, er habe sogar kein Dummy-Objekt instanziiert. Mm ... Wie ich festgestellt habe, sollten wir explizit einen "echten" Code aus der Kategoriedatei aufrufen. Es sollte also zumindest die Klassenfunktion aufgerufen werden. Und wir brauchen sogar keine Dummy-Klasse. Einzelne c-Funktionen machen dasselbe.
Wenn wir also lib-Dateien schreiben als:
und wenn wir useMyLib () aufrufen; Überall im App-Projekt und dann in jeder Klasse können wir die logSelf-Kategoriemethode verwenden.
Und noch mehr Blogs zum Thema:
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
quelle
Die Antwort von Vladimir ist eigentlich ziemlich gut, aber ich möchte hier etwas mehr Hintergrundwissen geben. Vielleicht findet eines Tages jemand meine Antwort und findet sie hilfreich.
Der Compiler wandelt Quelldateien (.c, .cc, .cpp, .m) in Objektdateien (.o) um. Es gibt eine Objektdatei pro Quelldatei. Objektdateien enthalten Symbole, Code und Daten. Objektdateien können vom Betriebssystem nicht direkt verwendet werden.
Beim Erstellen einer dynamischen Bibliothek (.dylib), eines Frameworks, eines ladbaren Bundles (.bundle) oder einer ausführbaren Binärdatei werden diese Objektdateien vom Linker miteinander verknüpft, um etwas zu erzeugen, das das Betriebssystem als "verwendbar" erachtet, z. B. etwas, das es kann direkt in eine bestimmte Speicheradresse laden.
Beim Erstellen einer statischen Bibliothek werden jedoch alle diese Objektdateien einfach zu einer großen Archivdatei hinzugefügt, daher die Erweiterung der statischen Bibliotheken (.a für Archiv). Eine .a-Datei ist also nichts anderes als ein Archiv von Objektdateien (.o). Stellen Sie sich ein TAR-Archiv oder ein ZIP-Archiv ohne Komprimierung vor. Es ist einfach einfacher, eine einzelne .a-Datei zu kopieren als eine ganze Reihe von .o-Dateien (ähnlich wie bei Java, wo Sie .class-Dateien zur einfachen Verteilung in ein .jar-Archiv packen).
Beim Verknüpfen einer Binärdatei mit einer statischen Bibliothek (= Archiv) erhält der Linker eine Tabelle aller Symbole im Archiv und überprüft, auf welche dieser Symbole die Binärdateien verweisen. Nur die Objektdateien, auf die verwiesen wird, werden vom Linker tatsächlich geladen und vom Verknüpfungsprozess berücksichtigt. Wenn Ihr Archiv beispielsweise 50 Objektdateien enthält, aber nur 20 Symbole enthalten, die von der Binärdatei verwendet werden, werden nur die 20 vom Linker geladen, die anderen 30 werden beim Verknüpfungsprozess vollständig ignoriert.
Dies funktioniert recht gut für C- und C ++ - Code, da diese Sprachen versuchen, beim Kompilieren so viel wie möglich zu tun (obwohl C ++ auch einige Nur-Laufzeit-Funktionen bietet). Obj-C ist jedoch eine andere Art von Sprache. Obj-C hängt stark von den Laufzeitfunktionen ab, und viele Obj-C-Funktionen sind eigentlich nur Laufzeitfunktionen. Obj-C-Klassen haben tatsächlich Symbole, die mit C-Funktionen oder globalen C-Variablen vergleichbar sind (zumindest in der aktuellen Obj-C-Laufzeit). Ein Linker kann sehen, ob auf eine Klasse verwiesen wird oder nicht, sodass er bestimmen kann, welche Klasse verwendet wird oder nicht. Wenn Sie eine Klasse aus einer Objektdatei in einer statischen Bibliothek verwenden, wird diese Objektdatei vom Linker geladen, da der Linker ein verwendetes Symbol sieht. Kategorien sind nur zur Laufzeit verfügbar. Kategorien sind keine Symbole wie Klassen oder Funktionen. Dies bedeutet auch, dass ein Linker nicht feststellen kann, ob eine Kategorie verwendet wird oder nicht.
Wenn der Linker eine Objektdatei mit Obj-C-Code lädt, sind alle Obj-C-Teile immer Teil der Verknüpfungsstufe. Wenn also eine Objektdatei mit Kategorien geladen wird, weil ein Symbol daraus als "in Verwendung" betrachtet wird (sei es eine Klasse, sei es eine Funktion, sei es eine globale Variable), werden die Kategorien ebenfalls geladen und sind zur Laufzeit verfügbar . Wenn die Objektdatei selbst jedoch nicht geladen wird, sind die darin enthaltenen Kategorien zur Laufzeit nicht verfügbar. Eine Objektdatei, die nur Kategorien enthält, wird niemals geladen, da sie keine Symbole enthält, die der Linker jemals als "in Verwendung" betrachten würde. Und das ist das ganze Problem hier.
Es wurden mehrere Lösungen vorgeschlagen. Nachdem Sie nun wissen, wie dies alles zusammenspielt, werfen wir einen weiteren Blick auf die vorgeschlagene Lösung:
Eine Lösung besteht darin
-all_load
, dem Linker-Aufruf etwas hinzuzufügen . Was wird diese Linker-Flagge tatsächlich tun? Tatsächlich teilt es dem Linker Folgendes mit: " Alle Objektdateien aller Archive laden, unabhängig davon, ob ein Symbol verwendet wird oder nicht ". Das funktioniert natürlich, kann aber auch ziemlich große Binärdateien erzeugen.Eine andere Lösung besteht darin
-force_load
, dem Linker-Aufruf den Pfad zum Archiv hinzuzufügen . Dieses Flag funktioniert genauso-all_load
, jedoch nur für das angegebene Archiv. Das wird natürlich auch funktionieren.Die beliebteste Lösung ist das Hinzufügen
-ObjC
zum Linker-Aufruf. Was wird diese Linker-Flagge tatsächlich tun? Dieses Flag teilt dem Linker mit, dass alle Objektdateien aus allen Archiven geladen werden sollen, wenn Sie feststellen, dass sie Obj-C-Code enthalten . Und "jeder Obj-C-Code" enthält Kategorien. Dies funktioniert auch und erzwingt nicht das Laden von Objektdateien, die keinen Obj-C-Code enthalten (diese werden immer noch nur bei Bedarf geladen).Eine andere Lösung ist die ziemlich neue Xcode-Build-Einstellung
Perform Single-Object Prelink
. Was macht diese Einstellung? Wenn diese Option aktiviert ist, werden alle Objektdateien (denken Sie daran, es gibt eine pro Quelldatei) zu einer einzelnen Objektdatei (die keine echte Verknüpfung darstellt, daher der Name PreLink ) und dieser einzelnen Objektdatei (manchmal auch als "Master-Objekt" bezeichnet) zusammengeführt Datei ") wird dann zum Archiv hinzugefügt. Wenn nun ein Symbol der Master-Objektdatei als verwendet betrachtet wird, wird die gesamte Master-Objektdatei als verwendet betrachtet und somit werden alle Objective-C-Teile davon immer geladen. Und da Klassen normale Symbole sind, reicht es aus, eine einzelne Klasse aus einer solchen statischen Bibliothek zu verwenden, um auch alle Kategorien abzurufen.Die endgültige Lösung ist der Trick, den Vladimir ganz am Ende seiner Antwort hinzugefügt hat. Platzieren Sie ein " falsches Symbol " in einer Quelldatei, die nur Kategorien deklariert. Wenn Sie zur Laufzeit eine der Kategorien verwenden möchten, stellen Sie sicher, dass Sie zur Kompilierungszeit auf das gefälschte Symbol verweisen , da dies dazu führt, dass die Objektdatei vom Linker und damit auch der gesamte darin enthaltene Obj-C-Code geladen wird. Beispielsweise könnte es sich um eine Funktion mit einem leeren Funktionskörper handeln (die beim Aufruf nichts bewirkt), oder es kann sich um eine globale Variable handeln, auf die zugegriffen wird (z. B. eine globale Variable)
int
einmal gelesen oder einmal geschrieben, ist dies ausreichend). Im Gegensatz zu allen anderen oben genannten Lösungen verlagert diese Lösung die Kontrolle darüber, welche Kategorien zur Laufzeit verfügbar sind, auf den kompilierten Code (wenn sie verknüpft und verfügbar sein sollen, greift sie auf das Symbol zu, andernfalls greift sie nicht auf das Symbol zu und der Linker ignoriert sie es).Das war's Leute.
Oh, warte, es gibt noch eine Sache:
Der Linker hat eine Option namens
-dead_strip
. Was macht diese Option? Wenn der Linker beschlossen hat, eine Objektdatei zu laden, werden alle Symbole der Objektdatei Teil der verknüpften Binärdatei, unabhängig davon, ob sie verwendet werden oder nicht. Beispiel: Eine Objektdatei enthält 100 Funktionen, aber nur eine davon wird von der Binärdatei verwendet. Alle 100 Funktionen werden weiterhin zur Binärdatei hinzugefügt, da Objektdateien entweder als Ganzes oder gar nicht hinzugefügt werden. Das teilweise Hinzufügen einer Objektdatei wird von Linkern normalerweise nicht unterstützt.Wenn Sie dem Linker jedoch "Dead Strip" mitteilen, fügt der Linker zuerst alle Objektdateien zur Binärdatei hinzu, löst alle Referenzen auf und durchsucht die Binärdatei schließlich nach Symbolen, die nicht verwendet werden (oder nur von anderen Symbolen verwendet werden, die nicht verwendet werden) verwenden). Alle nicht verwendeten Symbole werden dann im Rahmen der Optimierungsphase entfernt. Im obigen Beispiel werden die 99 nicht verwendeten Funktionen wieder entfernt. Dies ist sehr nützlich , wenn Sie Optionen verwenden , wie
-load_all
,-force_load
oderPerform Single-Object Prelink
weil diese Optionen können leicht binäre Größen dramatisch in einigen Fällen und die Toten Strippen wieder nicht verwendeten Code und Daten entfernen sprengen.Dead Stripping funktioniert sehr gut für C-Code (z. B. werden nicht verwendete Funktionen, Variablen und Konstanten wie erwartet entfernt) und funktioniert auch für C ++ recht gut (z. B. werden nicht verwendete Klassen entfernt). Es ist nicht perfekt, in einigen Fällen werden einige Symbole nicht entfernt, obwohl es in Ordnung wäre, sie zu entfernen, aber in den meisten Fällen funktioniert es für diese Sprachen recht gut.
Was ist mit Obj-C? Vergiss es! Für Obj-C gibt es kein totes Strippen. Da Obj-C eine Laufzeit-Feature-Sprache ist, kann der Compiler zur Kompilierungszeit nicht sagen, ob ein Symbol wirklich verwendet wird oder nicht. ZB wird eine Obj-C-Klasse nicht verwendet, wenn kein Code vorhanden ist, der direkt darauf verweist, richtig? Falsch! Sie können dynamisch eine Zeichenfolge erstellen, die einen Klassennamen enthält, einen Klassenzeiger für diesen Namen anfordern und die Klasse dynamisch zuweisen. ZB statt
Ich könnte auch schreiben
In beiden Fällen
mmc
handelt es sich um einen Verweis auf ein Objekt der Klasse "MyCoolClass", im zweiten Codebeispiel gibt es jedoch keinen direkten Verweis auf diese Klasse (nicht einmal den Klassennamen als statische Zeichenfolge). Alles passiert nur zur Laufzeit. Und das ist , obwohl Klassen sind tatsächlich echte Symbole. Für Kategorien ist es noch schlimmer, da es sich nicht einmal um echte Symbole handelt.Wenn Sie also eine statische Bibliothek mit Hunderten von Objekten haben, die meisten Ihrer Binärdateien jedoch nur wenige benötigen, ziehen Sie es möglicherweise vor, die obigen Lösungen (1) bis (4) nicht zu verwenden. Andernfalls erhalten Sie sehr große Binärdateien, die alle diese Klassen enthalten, obwohl die meisten von ihnen nie verwendet werden. Für Klassen benötigen Sie normalerweise überhaupt keine spezielle Lösung, da Klassen echte Symbole haben. Solange Sie diese direkt referenzieren (nicht wie im zweiten Codebeispiel), erkennt der Linker ihre Verwendung ziemlich gut. Berücksichtigen Sie für Kategorien jedoch Lösung (5), da nur die Kategorien berücksichtigt werden können, die Sie wirklich benötigen.
Wenn Sie beispielsweise eine Kategorie für NSData möchten, z. B. eine Komprimierungs- / Dekomprimierungsmethode hinzufügen, erstellen Sie eine Header-Datei:
und eine Implementierungsdatei
Stellen Sie jetzt sicher, dass irgendwo in Ihrem Code
import_NSData_Compression()
aufgerufen wird. Es spielt keine Rolle, wo es aufgerufen wird oder wie oft es aufgerufen wird. Eigentlich muss es gar nicht aufgerufen werden, es reicht, wenn der Linker das glaubt. Sie können beispielsweise den folgenden Code an einer beliebigen Stelle in Ihrem Projekt einfügen:Sie müssen
importCategories()
Ihren Code nie aufrufen , das Attribut lässt den Compiler und Linker glauben, dass er aufgerufen wird, auch wenn dies nicht der Fall ist.Und noch ein letzter Tipp:
Wenn Sie
-whyload
dem letzten Linkaufruf hinzufügen , druckt der Linker im Build-Protokoll, welche Objektdatei aus welcher Bibliothek aufgrund des verwendeten Symbols geladen wurde. Es wird nur das erste Symbol gedruckt, das als verwendet betrachtet wird. Dies ist jedoch nicht unbedingt das einzige Symbol, das für diese Objektdatei verwendet wird.quelle
-whyload
, der Versuch zu debuggen, warum der Linker etwas tut, kann ziemlich schwierig sein!Dead Code Stripping
inBuild Settings>Linking
. Ist es dasselbe wie-dead_strip
hinzugefügtOther Linker Flags
?-ObjC
, also habe ich versucht, Ihren Hack, aber es beschwert sich"import_NSString_jsonObject()", referenced from: importCategories() in main.o ld: symbol(s) not found
. Ich habeimport_NSString_jsonObject
in meinem Embedded - Framework genanntUtility
, und fügen Sie#import <Utility/Utility.h>
mit__attribute__
am Ende meiner AussageAppDelegate.h
.Dieses Problem wurde in LLVM behoben . Der Fix wird als Teil von LLVM 2.9 ausgeliefert. Die erste Xcode-Version, die den Fix enthält, ist Xcode 4.2, der mit LLVM 3.0 ausgeliefert wird. Die Verwendung von
-all_load
oder-force_load
wird bei der Arbeit mit XCode 4.2 nicht mehr-ObjC
benötigt.quelle
-ObjC
Flagge wird noch benötigt und wird es immer sein. Die Problemumgehung war die Verwendung von-all_load
oder-force_load
. Und das wird nicht mehr benötigt. Ich habe meine Antwort oben korrigiert.Folgendes müssen Sie tun, um dieses Problem beim Kompilieren Ihrer statischen Bibliothek vollständig zu beheben:
Gehen Sie entweder zu Xcode Build Settings und setzen Sie Perform Single-Object Prelink auf YES oder
GENERATE_MASTER_OBJECT_FILE = YES
in Ihrer Build-Konfigurationsdatei.Standardmäßig generiert der Linker für jede .m-Datei eine .o-Datei. Kategorien erhalten also unterschiedliche .o-Dateien. Wenn der Linker eine statische Bibliothek .o-Dateien betrachtet, erstellt er nicht einen Index aller Symbole pro Klasse (Laufzeit wird, egal was).
Diese Anweisung fordert den Linker auf, alle Objekte in einer großen .o-Datei zusammenzufassen. Dadurch wird der Linker, der die statische Bibliothek verarbeitet, gezwungen, alle Klassenkategorien zu indizieren.
Hoffe das klärt es.
quelle
Ein Faktor, der selten erwähnt wird, wenn die Diskussion über die Verknüpfung statischer Bibliotheken auftaucht, ist die Tatsache, dass Sie auch die Kategorien selbst in die Erstellungsphasen aufnehmen müssen -> Dateien kopieren und Quellen der statischen Bibliothek selbst kompilieren müssen .
Apple betont diese Tatsache auch nicht in seinen kürzlich veröffentlichten " Using Static Libraries" in iOS .
Ich habe einen ganzen Tag damit verbracht, alle möglichen Variationen von -objC und -all_load usw. auszuprobieren. Aber es kam nichts dabei heraus. Diese Frage machte mich auf dieses Problem aufmerksam. (Versteh mich nicht falsch. Du musst immer noch das -objC-Zeug machen. Aber es ist mehr als nur das.)
Eine weitere Aktion, die mir immer geholfen hat, ist, dass ich die enthaltene statische Bibliothek immer zuerst selbst erstelle. Dann erstelle ich die umschließende Anwendung.
quelle
Sie müssen wahrscheinlich die Kategorie im "öffentlichen" Header Ihrer statischen Bibliothek haben: #import "MyStaticLib.h"
quelle