La réponse de Vladimir est en fait assez bonne, mais j'aimerais donner un peu plus d'informations de base ici. Peut-être qu'un jour quelqu'un trouvera ma réponse et la trouvera utile.
Le compilateur transforme les fichiers sources (.c, .cc, .cpp, .m) en fichiers objets (.o). Il y a 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 binaire exécutable, ces fichiers objets sont liés ensemble par l'éditeur de liens pour produire quelque chose que le système d'exploitation considère comme "utilisable", c'est-à-dire 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 d'archive, d'où l'extension des bibliothèques statiques (.a pour archive). Un fichier .a n'est donc rien d'autre qu'une archive de fichiers objets (.o). Pensez à une archive TAR ou à une archive ZIP sans compression. Il est simplement plus facile de copier un seul fichier .a qu'un grand nombre de fichiers .o (comme en Java, où l'on regroupe les fichiers .class dans une archive .jar pour faciliter la distribution).
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 les symboles référencés sont effectivement chargés par le linker et sont pris en compte par le processus de liaison. Par exemple, si votre archive contient 50 fichiers objets, mais que seuls 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 très bien pour le code C et C++, car ces langages essaient d'en faire le plus possible au moment de la compilation (bien que le C++ ait aussi des caractéristiques réservées à l'exécution). 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 Obj-C sont en fait des fonctionnalités d'exécution uniquement. Les classes Obj-C ont en fait des symboles comparables aux fonctions C ou aux variables C globales (du moins dans le runtime Obj-C actuel). Un linker peut voir si une classe est référencée ou non, il peut donc déterminer si une classe est utilisée 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 parce que l'éditeur de liens voit qu'un symbole est utilisé. Les catégories sont une fonctionnalité d'exécution uniquement, les catégories ne sont pas des symboles comme les classes ou les 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. Ainsi, si un fichier objet contenant des catégories est chargé parce qu'un de ses symboles est considéré comme "utilisé" (qu'il s'agisse d'une classe, d'une fonction ou d'une variable globale), les catégories sont également chargées et seront disponibles au moment de l'exécution. En revanche, 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 sólo Les catégories sont jamais chargé parce qu'il contient aucun symbole l'éditeur de liens jamais considérer comme "en service". Et c'est là tout le problème.
Plusieurs solutions ont été proposées et maintenant que vous savez comment tout cela s'articule, examinons à nouveau la solution proposée :
-
Une solution consiste à ajouter -all_load
à l'appel de l'éditeur de liens. Que va faire ce drapeau de l'éditeur de liens ? En fait, il dit à l'éditeur de liens ce qui suit : " Chargement de tous les fichiers objets de toutes les archives, qu'un symbole soit utilisé ou non. '. Bien sûr, cela fonctionnera, mais cela peut aussi produire des binaires plutôt gros.
-
Une autre solution consiste à ajouter -force_load
à l'appel de l'éditeur de liens incluant le chemin vers l'archive. Ce drapeau fonctionne exactement comme -all_load
mais uniquement pour l'archive spécifiée. Bien sûr, cela fonctionnera également.
-
La solution la plus courante consiste à ajouter -ObjC
à l'appel de l'éditeur de liens. Que fait réellement ce drapeau de l'éditeur de liens ? Ce drapeau dit à 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" inclut les catégories. Cela fonctionnera également et ne forcera pas le chargement des fichiers d'objets ne contenant pas de code Obj-C (ceux-ci ne sont toujours chargés que sur demande).
-
Une autre solution consiste à utiliser le nouveau paramètre de construction de Xcode. Perform Single-Object Prelink
. Que va faire ce paramètre ? S'il est activé, tous les fichiers d'objets (rappelez-vous, il y en a un par fichier source) sont fusionnés en un seul fichier d'objets (qui n'est pas une véritable liaison, d'où le nom de PreLink ) et ce fichier objet unique (parfois aussi appelé "fichier objet maître") est alors ajouté à l'archive. Si un symbole du fichier objet maître est considéré comme utilisé, c'est l'ensemble du fichier objet maître qui est considéré comme utilisé, et donc toutes les parties Objective-C de ce fichier 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 tout fichier source ne déclarant que des catégories. Si vous voulez utiliser l'une des catégories au moment de l'exécution, assurez-vous de faire référence d'une manière ou d'une autre à l'élément faux symbole au moment de la compilation, car cela entraîne le chargement du fichier objet par l'éditeur de liens et donc aussi 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 lorsqu'elle sera appelée) ou d'un accès à une variable globale (par exemple, une variable globale int
une fois lu ou une fois écrit, c'est suffisant). Contrairement à toutes les autres solutions ci-dessus, cette solution transfère le contrôle des catégories disponibles à l'exécution au 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 l'ignorera).
C'est tout, les gars.
Oh, attends, 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 a décidé de charger un fichier objet, tous les symboles du fichier objet deviennent partie intégrante 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 quand même ajoutées au binaire car les fichiers objets sont soit ajoutés dans leur ensemble, soit 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 demandez à l'éditeur de liens d'effectuer un "dead strip", l'éditeur de liens ajoutera d'abord tous les fichiers objets au binaire, résoudra toutes les références et enfin recherchera dans le binaire les symboles non utilisés (ou seulement utilisés par d'autres symboles non utilisés). Tous les symboles jugés inutiles 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 comme -load_all
, -force_load
o Perform Single-Object Prelink
car ces options peuvent facilement faire exploser la taille des binaires dans certains cas et le dépouillement des données supprimera à nouveau le code et les données inutilisés.
Le dépouillement fonctionne très bien pour le code C (par exemple, les fonctions, variables et constantes inutilisées sont supprimées comme prévu) et il fonctionne également très bien pour le 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 alors qu'il serait correct de les supprimer, mais dans la plupart des cas cela fonctionne assez bien pour ces langages.
Et Obj-C ? Oubliez-le ! Il n'y a pas de dead stripping pour Obj-C. Comme Obj-C est un langage à fonctionnalité d'exécution, le compilateur ne peut pas dire au moment de la compilation si un symbole est vraiment utilisé ou non. Par exemple, une classe Obj-C n'est pas utilisée s'il n'y a pas de code qui y fait directement référence, n'est-ce pas ? C'est faux ! Vous pouvez construire 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 mmc
est une référence à un objet de la classe "MyCoolClass", mais il y a pas de référence directe à cette classe dans le deuxième exemple de code (même pas le nom de la classe comme chaîne statique). Tout se passe uniquement au moment de l'exécution. Et ce, même si les classes sont sont en fait de vrais symboles. C'est encore pire pour les catégories, car elles ne sont même pas de vrais symboles.
Ainsi, si vous avez une bibliothèque statique avec des centaines d'objets, mais que la plupart de vos binaires n'ont besoin que de quelques-uns d'entre eux, 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 d'une solution spéciale puisque les classes ont des symboles réels et tant que vous les référencez directement (pas comme dans le deuxième exemple de code), le linker identifiera leur utilisation assez bien tout seul. Pour les catégories, cependant, considérez la solution (5), car elle permet d'inclure seulement les catégories dont vous avez vraiment besoin.
Par exemple, si vous souhaitez créer une catégorie pour NSData, par exemple en lui ajoutant une méthode de compression/décompression, vous créerez un fichier d'en-tête :
// NSData+Compress.h
@interface NSData (Compression)
- (NSData *)compressedData;
- (NSData *)decompressedData;
@end
void import_NSData_Compression ( );
et un fichier de mise en œuvre
// NSData+Compress
@implementation NSData (Compression)
- (NSData *)compressedData
{
// ... magic ...
}
- (NSData *)decompressedData
{
// ... magic ...
}
@end
void import_NSData_Compression ( ) { }
Maintenant, assurez-vous que n'importe où dans votre code import_NSData_Compression()
est appelé. Peu importe où il est appelé ou combien de fois il est appelé. En fait, il n'a pas besoin d'être appelé du tout, il suffit que 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 pas besoin d'appeler importCategories()
dans votre code, l'attribut fera croire au compilateur et à l'éditeur de liens qu'il est appelé, même s'il ne l'est pas.
Et un dernier conseil :
Si vous ajoutez -whyload
jusqu'à l'appel de lien final, l'éditeur de liens imprimera dans le journal de construction quel fichier objet de quelle bibliothèque il a chargé à cause de quel symbole utilisé. Il n'imprimera que le premier symbole considéré comme utilisé, mais ce n'est pas nécessairement le seul symbole utilisé pour ce fichier objet.
1 votes
J'ai trouvé quelques réponses et il semble que cette question ait déjà été traitée ici (désolé de l'avoir manqué). stackoverflow.com/questions/932856/ )