Pouvez-vous me guider comment lier correctement la bibliothèque statique au projet iPhone. J'utilise le projet de bibliothèque statique ajouté au projet d'application en tant que dépendance directe (cible -> général -> dépendances directes) et tout fonctionne bien, mais les catégories. Une catégorie définie dans la bibliothèque statique ne fonctionne pas dans l'application.
Ma question est donc de savoir comment ajouter une bibliothèque statique avec certaines catégories dans un autre projet?
Et en général, quelles sont les meilleures pratiques à utiliser dans le code de projet d'application d'autres projets?
iphone
objective-c
static-libraries
categories
Vladimir
la source
la source
Réponses:
Solution: à partir de Xcode 4.2, il vous suffit d'accéder à l'application qui relie la bibliothèque (pas la bibliothèque elle-même) et de cliquer sur le projet dans le navigateur de projet, de cliquer sur la cible de votre application, puis de créer les paramètres, puis de rechercher "Autre Linker Flags ", cliquez sur le bouton + et ajoutez" -ObjC ". "-all_load" et "-force_load" ne sont plus nécessaires.
Détails: J'ai trouvé des réponses sur divers forums, blogs et docs Apple. J'essaye maintenant de faire un bref résumé de mes recherches et expériences.
Le problème a été causé par (citation de Apple Q&R technique QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html ):
Et leur solution:
et il y a aussi des recommandations dans la FAQ sur le développement iPhone:
et les descriptions des drapeaux:
* nous pouvons utiliser force_load pour réduire la taille binaire de l'application et pour éviter les conflits que all_load peut provoquer dans certains cas.
Oui, cela fonctionne avec les fichiers * .a ajoutés au projet. Pourtant, j'ai eu des problèmes avec le projet lib ajouté en tant que dépendance directe. Mais plus tard, j'ai découvert que c'était de ma faute - le projet de dépendance directe n'a peut-être pas été ajouté correctement. Lorsque je le supprime et que je le rajoute avec les étapes:
après cela, tout fonctionne bien. Le drapeau "-ObjC" était suffisant dans mon cas.
J'ai également été intéressé par l'idée du blog http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html . L'auteur dit qu'il peut utiliser la catégorie de la lib sans définir l'option -all_load ou -ObjC. Il vient d'ajouter à la catégorie h / m les fichiers interface / implémentation de classe factice vide pour forcer l'éditeur de liens à utiliser ce fichier. Et oui, cette astuce fait l'affaire.
Mais l'auteur a également déclaré qu'il n'avait même pas instancié l'objet factice. Mm… Comme je l'ai trouvé, nous devrions explicitement appeler du code "réel" à partir d'un fichier de catégorie. Donc au moins la fonction de classe devrait être appelée. Et nous n'avons même pas besoin de cours factices. Une seule fonction c fait de même.
Donc, si nous écrivons les fichiers lib comme:
et si nous appelons useMyLib (); n'importe où dans le projet App puis dans n'importe quelle classe, nous pouvons utiliser la méthode de catégorie logSelf;
Et plus de blogs sur le thème:
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
la source
La réponse de Vladimir est en fait plutôt bonne, cependant, j'aimerais vous donner plus de connaissances de base ici. Peut-être qu'un jour quelqu'un trouvera ma réponse et la trouvera utile.
Le compilateur transforme les fichiers source (.c, .cc, .cpp, .m) en fichiers objets (.o). Il existe un fichier objet par fichier source. Les fichiers objets contiennent des symboles, du code et des données. Les fichiers objets ne sont pas directement utilisables par le système d'exploitation.
Maintenant, lors de la construction d'une bibliothèque dynamique (.dylib), d'un framework, d'un bundle chargeable (.bundle) ou d'un exécutable binaire, ces fichiers objets sont liés entre eux par l'éditeur de liens pour produire quelque chose que le système d'exploitation considère comme "utilisable", par exemple quelque chose qu'il peut charger directement à une adresse mémoire spécifique.
Cependant lors de la construction d'une bibliothèque statique, tous ces fichiers objets sont simplement ajoutés à un gros fichier archive, d'où l'extension de bibliothèques statiques (.a pour archive). Ainsi, un fichier .a n'est rien qu'une archive de fichiers objet (.o). Pensez à une archive TAR ou à une archive ZIP sans compression. Il est simplement plus facile de copier un seul fichier .a que tout un tas de fichiers .o (similaire à Java, où vous regroupez les fichiers .class dans une archive .jar pour une distribution facile).
Lors de la liaison d'un binaire à une bibliothèque statique (= archive), l'éditeur de liens obtiendra une table de tous les symboles de l'archive et vérifiera lesquels de ces symboles sont référencés par les binaires. Seuls les fichiers objets contenant des symboles référencés sont effectivement chargés par l'éditeur de liens et sont pris en compte par le processus de liaison. Par exemple, si votre archive contient 50 fichiers objets, mais que seulement 20 contiennent des symboles utilisés par le binaire, seuls ces 20 sont chargés par l'éditeur de liens, les 30 autres sont entièrement ignorés dans le processus de liaison.
Cela fonctionne assez bien pour le code C et C ++, car ces langages essaient d'en faire autant que possible au moment de la compilation (bien que C ++ ait également des fonctionnalités d'exécution uniquement). Obj-C, cependant, est un autre type de langage. Obj-C dépend fortement des fonctionnalités d'exécution et de nombreuses fonctionnalités d'Obj-C sont en fait des fonctionnalités uniquement d'exécution. Les classes Obj-C ont en fait des symboles comparables aux fonctions C ou aux variables C globales (au moins dans le runtime Obj-C actuel). Un éditeur de liens peut voir si une classe est référencée ou non, afin de déterminer une classe en cours d'utilisation ou non. Si vous utilisez une classe à partir d'un fichier objet dans une bibliothèque statique, ce fichier objet sera chargé par l'éditeur de liens car l'éditeur de liens voit un symbole en cours d'utilisation. Les catégories sont une fonctionnalité uniquement à l'exécution, les catégories ne sont pas des symboles comme des classes ou des fonctions et cela signifie également qu'un éditeur de liens ne peut pas déterminer si une catégorie est utilisée ou non.
Si l'éditeur de liens charge un fichier objet contenant du code Obj-C, toutes les parties Obj-C de celui-ci font toujours partie de l'étape de liaison. Donc, si un fichier objet contenant des catégories est chargé parce que tout symbole de celui-ci est considéré comme "en cours d'utilisation" (que ce soit une classe, que ce soit une fonction, que ce soit une variable globale), les catégories sont également chargées et seront disponibles au moment de l'exécution . Pourtant, si le fichier objet lui-même n'est pas chargé, les catégories qu'il contient ne seront pas disponibles au moment de l'exécution. Un fichier objet contenant uniquement des catégories est jamais chargé , car il contient pas de symboles l'éditeur de liens ne jamais considérer « en usage ». Et c'est là tout le problème.
Plusieurs solutions ont été proposées et maintenant que vous savez comment tout cela joue ensemble, revoyons la solution proposée:
Une solution consiste à ajouter
-all_load
à l'appel de l'éditeur de liens. Que fera réellement cet indicateur de l'éditeur de liens? En fait, il indique à l'éditeur de liens ce qui suit: « Chargez tous les fichiers objets de toutes les archives, que vous voyiez un symbole en cours d'utilisation ou non .» Bien sûr, cela fonctionnera, mais cela peut aussi produire des binaires assez volumineux.Une autre solution consiste à ajouter
-force_load
à l'appel de l'éditeur de liens en incluant le chemin d'accès à l'archive. Cet indicateur fonctionne exactement comme-all_load
, mais uniquement pour l'archive spécifiée. Bien sûr, cela fonctionnera également.La solution la plus populaire consiste à ajouter
-ObjC
à l'appel de l'éditeur de liens. Que fera réellement cet indicateur de l'éditeur de liens? Cet indicateur indique à l'éditeur de liens " Chargez tous les fichiers objets de toutes les archives si vous voyez qu'ils contiennent du code Obj-C ". Et "tout code Obj-C" comprend des catégories. Cela fonctionnera également et ne forcera pas le chargement de fichiers objets ne contenant pas de code Obj-C (ceux-ci ne sont toujours chargés qu'à la demande).Une autre solution est le nouveau paramètre de construction Xcode
Perform Single-Object Prelink
. Que fera ce paramètre? Si cette option est activée, tous les fichiers objets (rappelez-vous, il y en a un par fichier source) sont fusionnés en un seul fichier objet (qui n'est pas une véritable liaison, d'où le nom PreLink ) et ce fichier objet unique (parfois également appelé "objet maître file ") est ensuite ajouté à l'archive. Si maintenant un symbole du fichier objet maître est considéré comme utilisé, le fichier objet maître entier est considéré comme utilisé et donc toutes les parties Objective-C de celui-ci sont toujours chargées. Et comme les classes sont des symboles normaux, il suffit d'utiliser une seule classe d'une telle bibliothèque statique pour obtenir également toutes les catégories.La solution finale est l'astuce que Vladimir a ajoutée à la toute fin de sa réponse. Placez un " faux symbole " dans n'importe quel fichier source ne déclarant que des catégories. Si vous souhaitez utiliser l'une des catégories au moment de l'exécution, assurez-vous de référencer d'une manière ou d'une autre le faux symbole au moment de la compilation, car cela entraîne le chargement du fichier objet par l'éditeur de liens et donc également de tout le code Obj-C qu'il contient. Par exemple, il peut s'agir d'une fonction avec un corps de fonction vide (qui ne fera rien lors de l'appel) ou d'une variable globale accessible (par exemple
int
une fois lu ou écrit, cela suffit). Contrairement à toutes les autres solutions ci-dessus, cette solution déplace le contrôle sur les catégories disponibles au moment de l'exécution vers le code compilé (s'il veut qu'elles soient liées et disponibles, il accède au symbole, sinon il n'accède pas au symbole et l'éditeur de liens ignorera il).Ce sont tous des gens.
Oh, attendez, il y a encore une chose:
l'éditeur de liens a une option nommée
-dead_strip
. Que fait cette option? Si l'éditeur de liens décide de charger un fichier objet, tous les symboles du fichier objet font partie du binaire lié, qu'ils soient utilisés ou non. Par exemple, un fichier objet contient 100 fonctions, mais une seule d'entre elles est utilisée par le binaire, les 100 fonctions sont toujours ajoutées au binaire car les fichiers objets sont soit ajoutés dans leur ensemble, soit ils ne sont pas ajoutés du tout. L'ajout partiel d'un fichier objet n'est généralement pas pris en charge par les éditeurs de liens.Cependant, si vous indiquez à l'éditeur de liens "dead strip", l'éditeur de liens ajoutera d'abord tous les fichiers objets au binaire, résoudra toutes les références et analysera enfin le binaire pour les symboles non utilisés (ou uniquement utilisés par d'autres symboles non utilisation). Tous les symboles qui ne sont pas utilisés sont ensuite supprimés dans le cadre de l'étape d'optimisation. Dans l'exemple ci-dessus, les 99 fonctions inutilisées sont à nouveau supprimées. Ceci est très utile si vous utilisez des options telles que
-load_all
,-force_load
ouPerform Single-Object Prelink
parce que ces options peuvent facilement augmenter considérablement les tailles binaires dans certains cas et le décapage mort supprimera à nouveau le code et les données inutilisés.Le décapage mort fonctionne très bien pour le code C (par exemple, les fonctions, les variables et les constantes inutilisées sont supprimées comme prévu) et cela fonctionne également assez bien pour C ++ (par exemple, les classes inutilisées sont supprimées). Ce n'est pas parfait, dans certains cas, certains symboles ne sont pas supprimés même si ce serait correct de les supprimer, mais dans la plupart des cas, cela fonctionne assez bien pour ces langues.
Et Obj-C? Oublie ça! Il n'y a pas de décapage mort pour Obj-C. Comme Obj-C est un langage à fonctionnalités d'exécution, le compilateur ne peut pas dire au moment de la compilation si un symbole est réellement utilisé ou non. Par exemple, une classe Obj-C n'est pas utilisée s'il n'y a pas de code la référençant directement, n'est-ce pas? Faux! Vous pouvez créer dynamiquement une chaîne contenant un nom de classe, demander un pointeur de classe pour ce nom et allouer dynamiquement la classe. Par exemple au lieu de
Je pourrais aussi écrire
Dans les deux cas, il
mmc
y a une référence à un objet de la classe «MyCoolClass», mais il n'y a aucune référence directe à cette classe dans le deuxième exemple de code (pas même le nom de classe sous forme de chaîne statique). Tout se passe uniquement au moment de l'exécution. Et c'est même si les classes sont en fait de vrais symboles. C'est encore pire pour les catégories, car ce ne sont même pas de vrais symboles.Donc, si vous avez une bibliothèque statique avec des centaines d'objets, mais que la plupart de vos binaires n'en ont besoin que de quelques-uns, vous préférerez peut-être ne pas utiliser les solutions (1) à (4) ci-dessus. Sinon, vous vous retrouvez avec de très gros binaires contenant toutes ces classes, même si la plupart d'entre elles ne sont jamais utilisées. Pour les classes, vous n'avez généralement pas besoin de solution spéciale car les classes ont de vrais symboles et tant que vous les référencez directement (pas comme dans le deuxième exemple de code), l'éditeur de liens identifiera assez bien leur utilisation de lui-même. Pour les catégories, cependant, considérez la solution (5), car elle permet d'inclure uniquement les catégories dont vous avez vraiment besoin.
Par exemple, si vous voulez une catégorie pour NSData, par exemple en y ajoutant une méthode de compression / décompression, vous créez un fichier d'en-tête:
et un dossier d'implémentation
Maintenant, assurez-vous simplement que n'importe où dans votre code
import_NSData_Compression()
est appelé. Peu importe où il est appelé ou à quelle fréquence il est appelé. En fait, il n'a pas vraiment besoin d'être appelé du tout, c'est suffisant si l'éditeur de liens le pense. Par exemple, vous pouvez mettre le code suivant n'importe où dans votre projet:Vous n'avez jamais besoin d'appeler
importCategories()
votre code, l'attribut fera croire au compilateur et à l'éditeur de liens qu'il est appelé, même si ce n'est pas le cas.Et un dernier conseil:
si vous ajoutez
-whyload
à l'appel de lien final, l'éditeur de liens imprimera dans le journal de construction quel fichier objet à partir de quelle bibliothèque il a chargé en raison du symbole utilisé. Il n'imprimera que le premier symbole considéré en cours d'utilisation, mais ce n'est pas nécessairement le seul symbole utilisé de ce fichier objet.la source
-whyload
, essayer de déboguer pourquoi l'éditeur de liens fait quelque chose peut être assez difficile!Dead Code Stripping
dansBuild Settings>Linking
. Est-ce le même que celui-dead_strip
ajoutéOther Linker Flags
?-ObjC
, alors j'ai essayé ton hack mais ça se plaint"import_NSString_jsonObject()", referenced from: importCategories() in main.o ld: symbol(s) not found
. J'ai misimport_NSString_jsonObject
dans mon Framework intégré nomméUtility
, et ajouter#import <Utility/Utility.h>
avec une__attribute__
déclaration à la fin de monAppDelegate.h
.Ce problème a été résolu dans LLVM . Le correctif fait partie de LLVM 2.9 La première version de Xcode à contenir le correctif est Xcode 4.2 livré avec LLVM 3.0. L'utilisation de
-all_load
ou-force_load
n'est plus nécessaire lorsque vous travaillez avec XCode 4.2-ObjC
est toujours nécessaire.la source
-ObjC
drapeau est toujours nécessaire et le sera toujours. La solution de contournement était l'utilisation de-all_load
ou-force_load
. Et ce n'est plus nécessaire. J'ai fixé ma réponse ci-dessus.Voici ce que vous devez faire pour résoudre complètement ce problème lors de la compilation de votre bibliothèque statique:
Accédez aux paramètres de construction Xcode et définissez Effectuer la liaison préliminaire d'un seul objet sur OUI ou
GENERATE_MASTER_OBJECT_FILE = YES
dans votre fichier de configuration de construction.Par défaut, l'éditeur de liens génère un fichier .o pour chaque fichier .m. Les catégories obtiennent donc différents fichiers .o. Lorsque l'éditeur de liens examine les fichiers .o d'une bibliothèque statique, il ne crée pas d'index de tous les symboles par classe (Runtime le fera, peu importe quoi).
Cette directive demandera à l'éditeur de liens de regrouper tous les objets dans un seul gros fichier .o et de ce fait, il force l'éditeur de liens qui traite la bibliothèque statique à obtenir l'index de toutes les catégories de classe.
J'espère que cela clarifie cela.
la source
Un facteur qui est rarement mentionné chaque fois que la discussion sur les liens de la bibliothèque statique survient est le fait que vous devez également inclure les catégories elles-mêmes dans les phases de construction -> copier les fichiers et compiler les sources de la bibliothèque statique elle-même .
Apple n'insiste pas non plus sur ce fait dans leur publication récente intitulée Using Static Libraries in iOS .
J'ai passé une journée entière à essayer toutes sortes de variantes de -objC et -all_load etc. mais rien n'en est ressorti .. cette question a attiré mon attention sur ce problème. (ne vous méprenez pas ... vous devez encore faire les choses -objC ... mais c'est plus que ça).
aussi une autre action qui m'a toujours aidé est que je construis toujours la bibliothèque statique incluse d'abord seule .. puis je construis l'application englobante ..
la source
Vous devez probablement avoir la catégorie dans l'en-tête "public" de votre bibliothèque statique: #import "MyStaticLib.h"
la source