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 exempleint
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
ou Perform 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
MyCoolClass * mcc = [[MyCoolClass alloc] init];
Je pourrais aussi écrire
NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];
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:
// NSData+Compress.h
@interface NSData (Compression)
- (NSData *)compressedData;
- (NSData *)decompressedData;
@end
void import_NSData_Compression ( );
et un dossier d'implémentation
// NSData+Compress
@implementation NSData (Compression)
- (NSData *)compressedData
{
// ... magic ...
}
- (NSData *)decompressedData
{
// ... magic ...
}
@end
void import_NSData_Compression ( ) { }
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:
__attribute__((used)) static void importCategories ()
{
import_NSData_Compression();
// add more import calls here
}
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.