Jawaban dari Vladimir sebenarnya cukup bagus, namun, saya ingin memberikan sedikit latar belakang pengetahuan di sini. Mungkin suatu hari seseorang menemukan balasan saya dan mungkin merasa terbantu.
Kompiler mengubah file sumber (.c, .cc, .cpp, .m) menjadi file objek (.o). Ada satu file objek per file sumber. File objek berisi simbol, kode, dan data. File objek tidak dapat langsung digunakan oleh sistem operasi.
Sekarang ketika membangun perpustakaan dinamis (.dylib), kerangka kerja, bundel yang dapat dimuat (.bundle) atau biner yang dapat dieksekusi, file objek ini dihubungkan bersama oleh linker untuk menghasilkan sesuatu yang sistem operasi anggap "dapat digunakan", misalnya sesuatu yang dapat langsung memuat ke alamat memori tertentu.
Namun ketika membangun perpustakaan statis, semua file objek ini hanya ditambahkan ke file arsip besar, maka ekstensi perpustakaan statis (.a untuk arsip). Jadi file .a tidak lebih dari arsip file objek (.o). Pikirkan arsip TAR atau arsip ZIP tanpa kompresi. Ini hanya lebih mudah untuk menyalin satu file .a sekitar dari sejumlah file .o (mirip dengan Jawa, di mana Anda mengemas file .class ke dalam arsip .jar untuk distribusi mudah).
Saat menautkan biner ke pustaka statis (= arsip), tautan akan mendapatkan tabel semua simbol dalam arsip dan memeriksa simbol mana yang dirujuk oleh binari. Hanya file objek yang mengandung simbol yang direferensikan yang sebenarnya dimuat oleh linker dan dianggap oleh proses penautan. Misalnya jika arsip Anda memiliki 50 file objek, tetapi hanya 20 yang mengandung simbol yang digunakan oleh biner, hanya 20 yang dimuat oleh linker, 30 lainnya sepenuhnya diabaikan dalam proses penautan.
Ini berfungsi cukup baik untuk kode C dan C ++, karena bahasa-bahasa ini mencoba melakukan sebanyak mungkin pada waktu kompilasi (meskipun C ++ juga memiliki beberapa fitur runtime-only). Obj-C, bagaimanapun, adalah jenis bahasa yang berbeda. Obj-C sangat tergantung pada fitur runtime dan banyak fitur Obj-C sebenarnya hanya fitur runtime. Kelas Obj-C sebenarnya memiliki simbol yang sebanding dengan fungsi C atau variabel global C (setidaknya dalam runtime Obj-C saat ini). Linker dapat melihat apakah suatu kelas direferensikan atau tidak, sehingga dapat menentukan kelas yang digunakan atau tidak. Jika Anda menggunakan kelas dari file objek di perpustakaan statis, file objek ini akan dimuat oleh linker karena linker melihat simbol sedang digunakan. Kategori adalah fitur runtime-only, kategori bukan simbol seperti kelas atau fungsi dan itu juga berarti penghubung tidak dapat menentukan apakah suatu kategori sedang digunakan atau tidak.
Jika tautan memuat file objek yang berisi kode Obj-C, semua bagian Obj-C selalu menjadi bagian dari tahap penautan. Jadi jika file objek yang berisi kategori dimuat karena simbol apa pun dari itu dianggap "sedang digunakan" (baik itu kelas, baik itu fungsi, baik itu variabel global), kategori tersebut akan dimuat juga dan akan tersedia saat runtime . Namun jika file objek itu sendiri tidak dimuat, kategori di dalamnya tidak akan tersedia saat runtime. File objek yang berisi hanya kategori yang tidak pernah dimuat karena mengandung tidak ada simbol linker akan pernah mempertimbangkan "digunakan". Dan inilah keseluruhan masalahnya di sini.
Beberapa solusi telah diajukan dan sekarang setelah Anda tahu bagaimana semua ini bekerja bersama, mari kita lihat lagi solusi yang diusulkan:
Salah satu solusinya adalah menambahkan -all_load
panggilan tautan. Apa yang sebenarnya akan dilakukan oleh bendera tautan? Sebenarnya itu memberitahu linker berikut ini " Muat semua file objek dari semua arsip terlepas dari apakah Anda melihat simbol yang digunakan atau tidak '. Tentu saja, itu akan berhasil; tetapi juga dapat menghasilkan biner yang agak besar.
Solusi lain adalah menambahkan -force_load
panggilan tautan termasuk jalur ke arsip. Bendera ini berfungsi persis seperti -all_load
, tetapi hanya untuk arsip yang ditentukan. Tentu saja ini akan berhasil juga.
Solusi paling populer adalah menambahkan -ObjC
panggilan tautan. Apa yang sebenarnya akan dilakukan oleh bendera tautan? Bendera ini memberi tahu penghubung " Muat semua file objek dari semua arsip jika Anda melihat bahwa mereka mengandung kode Obj-C ". Dan "kode Obj-C" termasuk kategori. Ini akan bekerja juga dan tidak akan memaksa memuat file objek yang tidak mengandung kode Obj-C (ini masih hanya dimuat sesuai permintaan).
Solusi lain adalah pengaturan build Xcode yang agak baru Perform Single-Object Prelink
. Apa yang akan dilakukan pengaturan ini? Jika diaktifkan, semua file objek (ingat, ada satu file per sumber) digabung bersama menjadi file objek tunggal (yang bukan penautan nyata, maka nama PreLink ) dan file objek tunggal ini (kadang-kadang juga disebut "objek master" file ") kemudian ditambahkan ke arsip. Jika sekarang simbol dari file objek master dianggap sedang digunakan, seluruh file objek master dianggap sedang digunakan dan dengan demikian semua bagian Objective-C selalu dimuat. Dan karena kelas adalah simbol normal, cukup menggunakan satu kelas dari pustaka statis untuk mendapatkan semua kategori.
Solusi terakhir adalah trik yang ditambahkan Vladimir di akhir jawabannya. Tempatkan " simbol palsu " ke file sumber apa pun yang hanya menyatakan kategori. Jika Anda ingin menggunakan salah satu kategori saat runtime, pastikan Anda entah bagaimana mereferensikan simbol palsu pada waktu kompilasi, karena ini menyebabkan file objek dimuat oleh linker dan dengan demikian juga semua kode Obj-C di dalamnya. Misalnya itu bisa fungsi dengan badan fungsi kosong (yang tidak akan melakukan apa-apa ketika dipanggil) atau itu bisa menjadi variabel global yang diakses (misalnya globalint
setelah membaca atau sekali ditulis, ini sudah cukup). Tidak seperti semua solusi lain di atas, solusi ini menggeser kontrol tentang kategori mana yang tersedia saat runtime ke kode yang dikompilasi (jika ia ingin mereka ditautkan dan tersedia, ia mengakses simbol, jika tidak mengakses simbol dan linker akan mengabaikan Itu).
Itu semua orang.
Oh, tunggu, ada satu hal lagi:
Linker memiliki opsi bernama -dead_strip
. Apa yang dilakukan opsi ini? Jika linker memutuskan untuk memuat file objek, semua simbol file objek menjadi bagian dari biner yang ditautkan, apakah mereka digunakan atau tidak. Misalnya file objek berisi 100 fungsi, tetapi hanya satu dari mereka yang digunakan oleh biner, semua 100 fungsi masih ditambahkan ke biner karena file objek baik ditambahkan secara keseluruhan atau tidak ditambahkan sama sekali. Menambahkan file objek sebagian biasanya tidak didukung oleh tautan.
Namun, jika Anda memberi tahu tautan ke "dead strip", tautan pertama akan menambahkan semua file objek ke biner, menyelesaikan semua referensi dan akhirnya memindai biner untuk simbol yang tidak digunakan (atau hanya digunakan oleh simbol lain yang tidak di menggunakan). Semua simbol yang ditemukan tidak digunakan kemudian dihapus sebagai bagian dari tahap optimasi. Dalam contoh di atas, 99 fungsi yang tidak digunakan dihapus lagi. Ini sangat berguna jika Anda menggunakan opsi seperti -load_all
, -force_load
atau Perform Single-Object Prelink
karena opsi ini dapat dengan mudah meledakkan ukuran biner secara dramatis dalam beberapa kasus dan pengupasan mati akan menghapus kode dan data yang tidak digunakan lagi.
Dead stripping berfungsi sangat baik untuk kode C (mis. Fungsi yang tidak digunakan, variabel dan konstanta dihapus seperti yang diharapkan) dan juga bekerja cukup baik untuk C ++ (mis. Kelas yang tidak digunakan dihapus). Itu tidak sempurna, dalam beberapa kasus beberapa simbol tidak dihapus meskipun akan baik-baik saja untuk menghapusnya, tetapi dalam kebanyakan kasus itu berfungsi dengan cukup baik untuk bahasa-bahasa ini.
Bagaimana dengan Obj-C? Lupakan saja! Tidak ada stripping mati untuk Obj-C. Karena Obj-C adalah bahasa fitur runtime, kompiler tidak dapat mengatakan pada waktu kompilasi apakah simbol benar-benar digunakan atau tidak. Misalnya kelas Obj-C tidak digunakan jika tidak ada kode yang langsung merujuknya, benar? Salah! Anda dapat secara dinamis membuat string yang berisi nama kelas, meminta pointer kelas untuk nama itu dan mengalokasikan kelas secara dinamis. Misalnya bukannya
MyCoolClass * mcc = [[MyCoolClass alloc] init];
Saya juga bisa menulis
NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];
Dalam kedua kasus mmc
adalah referensi ke objek dari kelas "MyCoolClass", tetapi tidak ada referensi langsung ke kelas ini dalam sampel kode kedua (bahkan tidak nama kelas sebagai string statis). Semuanya terjadi hanya saat runtime. Dan itu meskipun kelas sebenarnya adalah simbol nyata. Bahkan lebih buruk untuk kategori, karena mereka bahkan bukan simbol nyata.
Jadi jika Anda memiliki pustaka statis dengan ratusan objek, namun sebagian besar binari Anda hanya memerlukan beberapa di antaranya, Anda dapat memilih untuk tidak menggunakan solusi (1) hingga (4) di atas. Kalau tidak, Anda berakhir dengan binari yang sangat besar yang berisi semua kelas ini, meskipun sebagian besar dari mereka tidak pernah digunakan. Untuk kelas Anda biasanya tidak memerlukan solusi khusus sama sekali karena kelas memiliki simbol nyata dan selama Anda mereferensikannya secara langsung (tidak seperti dalam contoh kode kedua), tautan akan mengidentifikasi penggunaannya dengan cukup baik. Namun untuk kategori, pertimbangkan solusi (5), karena memungkinkan untuk hanya memasukkan kategori yang benar-benar Anda butuhkan.
Misalnya, jika Anda menginginkan kategori untuk NSData, misalnya menambahkan metode kompresi / dekompresi, Anda akan membuat file header:
// NSData+Compress.h
@interface NSData (Compression)
- (NSData *)compressedData;
- (NSData *)decompressedData;
@end
void import_NSData_Compression ( );
dan file implementasi
// NSData+Compress
@implementation NSData (Compression)
- (NSData *)compressedData
{
// ... magic ...
}
- (NSData *)decompressedData
{
// ... magic ...
}
@end
void import_NSData_Compression ( ) { }
Sekarang pastikan bahwa di mana saja dalam kode Anda import_NSData_Compression()
dipanggil. Tidak masalah di mana namanya atau seberapa sering disebut. Sebenarnya itu tidak benar-benar harus dipanggil sama sekali, itu sudah cukup jika linker berpikir demikian. Misalnya Anda dapat meletakkan kode berikut di mana saja di proyek Anda:
__attribute__((used)) static void importCategories ()
{
import_NSData_Compression();
// add more import calls here
}
Anda tidak perlu memanggil importCategories()
kode Anda, atribut akan membuat kompiler dan linker percaya bahwa itu disebut, bahkan jika tidak.
Dan tip terakhir:
Jika Anda menambah -whyload
panggilan tautan terakhir, penghubung akan mencetak dalam log build file objek mana dari perpustakaan mana ia memuat karena simbol yang digunakan. Ini hanya akan mencetak simbol pertama yang dianggap digunakan, tetapi itu tidak selalu merupakan satu-satunya simbol yang digunakan dalam file objek itu.