Vladimir'in cevabı aslında oldukça iyi, ancak burada biraz daha arka plan bilgisi vermek istiyorum. Belki bir gün birileri cevabımı bulur ve yardımcı olabilir.
Derleyici kaynak dosyaları (.c, .cc, .cpp, .m) nesne dosyalarına (.o) dönüştürür. Kaynak dosya başına bir nesne dosyası vardır. Nesne dosyaları semboller, kodlar ve veriler içerir. Nesne dosyaları işletim sistemi tarafından doğrudan kullanılamaz.
Şimdi dinamik bir kütüphane (.dylib), bir çerçeve, yüklenebilir bir paket (.bundle) veya yürütülebilir bir ikili dosya oluştururken, bu nesne dosyaları, işletim sisteminin "kullanılabilir" olarak düşündüğü bir şey üretmek için bağlayıcı tarafından birbirine bağlanır, örn. doğrudan belirli bir bellek adresine yükleyin.
Ancak, statik bir kitaplık oluştururken, tüm bu nesne dosyaları büyük bir arşiv dosyasına eklenir, dolayısıyla statik kitaplıkların uzantısı (arşiv için .a). Bu nedenle .a dosyası, nesne (.o) dosyalarının arşivinden başka bir şey değildir. Bir TAR arşivini veya sıkıştırılmadan bir ZIP arşivini düşünün. Tek bir .a dosyasını tüm .o dosyaları grubundan kopyalamak daha kolaydır (.class dosyalarını kolay dağıtım için bir .jar arşivine paketlediğiniz Java'ya benzer).
Bir ikili dosyayı statik kitaplığa (= arşiv) bağlarken, bağlayıcı arşivdeki tüm sembollerin bir tablosunu alır ve bu sembollerden hangilerinin ikili dosyalar tarafından referanslandığını kontrol eder. Yalnızca başvurulan sembolleri içeren nesne dosyaları gerçekten bağlayıcı tarafından yüklenir ve bağlama işlemi tarafından dikkate alınır. Örneğin, arşivinizde 50 nesne dosyası varsa, ancak yalnızca 20'si ikili dosya tarafından kullanılan simgeler içeriyorsa, yalnızca 20 tanesi bağlayıcı tarafından yüklenirse, diğer 30'u bağlama sürecinde tamamen yok sayılır.
Bu diller, C ve C ++ kodu için oldukça iyi çalışır, çünkü bu diller derleme zamanında mümkün olduğunca yapmaya çalışır (C ++ ayrıca yalnızca çalışma zamanı özelliklerine de sahiptir). Ancak Obj-C farklı bir dildir. Obj-C büyük ölçüde çalışma zamanı özelliklerine bağlıdır ve birçok Obj-C özelliği aslında yalnızca çalışma zamanı özellikleridir. Obj-C sınıfları aslında C fonksiyonları veya global C değişkenleri ile karşılaştırılabilir sembollere sahiptir (en azından mevcut Obj-C çalışma zamanında). Bağlayıcı, bir sınıfa başvurulup başvurulmadığını görebilir, böylece kullanımda olan bir sınıfı belirleyebilir. Statik kitaplıktaki bir nesne dosyasından bir sınıf kullanırsanız, linker kullanımda olan bir sembolü gördüğünden, bu nesne dosyası bağlayıcı tarafından yüklenir. Kategoriler yalnızca çalışma zamanı özelliğidir, kategoriler sınıflar veya işlevler gibi simgeler değildir ve bu da bir bağlayıcının bir kategorinin kullanımda olup olmadığını belirleyemeyeceği anlamına gelir.
Bağlayıcı Obj-C kodu içeren bir nesne dosyası yüklerse, bunun tüm Obj-C parçaları her zaman bağlama aşamasının bir parçasıdır. Dolayısıyla, kategorileri içeren bir nesne dosyası yüklenir, çünkü ondan herhangi bir sembol "kullanımda" kabul edilir (bir sınıf olsun, bir fonksiyon olsun, global bir değişken olsun), kategoriler de yüklenir ve çalışma zamanında kullanılabilir olacaktır . Ancak nesne dosyasının kendisi yüklenmezse, içindeki kategoriler çalışma zamanında kullanılamaz. İçeren bir nesne dosyası sadece kategoriler edilir asla o içerdiğinden yüklenen hiçbir sembolleri bağlayıcı olacağını hiç "kullanımda" düşünün. Ve buradaki tüm sorun bu.
Birkaç çözüm önerildi ve şimdi tüm bunların birlikte nasıl oynandığını bildiğinize göre, önerilen çözüme bir göz atalım:
Bir çözüm, -all_load
bağlayıcı çağrısına eklemektir . Bu bağlayıcı bayrağı gerçekte ne yapacak? Aslında, bağlayıcıya aşağıdaki "Kullanılan herhangi bir sembol görüp görmediğinize bakılmaksızın tüm arşivlerin tüm nesne dosyalarını yükle " komutunu söyler . Tabii ki bu işe yarar ; ancak oldukça büyük ikili dosyalar da üretebilir.
Başka bir çözüm, -force_load
arşiv yolunu içeren bağlayıcı çağrısına eklemektir . Bu bayrak tam olarak aynı şekilde çalışır -all_load
, ancak yalnızca belirtilen arşiv için geçerlidir. Tabii ki bu da işe yarayacak.
En popüler çözüm -ObjC
linker çağrısına eklemektir . Bu bağlayıcı bayrağı gerçekte ne yapacak? Bu bayrak, bağlayıcıya " Herhangi bir Obj-C kodu içerdiklerini görürseniz, tüm arşivlerdeki tüm nesne dosyalarını yükle " komutunu bildirir . Ve "herhangi bir Obj-C kodu" kategorileri içerir. Bu da işe yarayacak ve Obj-C kodu içermeyen nesne dosyalarının yüklenmesini zorlamayacaktır (bunlar yalnızca istek üzerine yüklenir).
Başka bir çözüm, oldukça yeni Xcode derleme ayarıdır Perform Single-Object Prelink
. Bu ayar ne yapacak? Etkinleştirilirse, tüm nesne dosyaları (unutmayın, kaynak dosya başına bir tane vardır) tek bir nesne dosyasına (gerçek bağlantı olmayan, dolayısıyla PreLink adı ) birleştirilir ve bu tek nesne dosyası (bazen "ana nesne olarak da adlandırılır) dosya ") daha sonra arşive eklenir. Artık ana nesne dosyasının herhangi bir sembolü kullanımda olarak kabul edilirse, tüm ana nesne dosyası kullanımda kabul edilir ve bu nedenle tüm Objective-C bölümleri her zaman yüklenir. Sınıflar normal semboller olduğundan, tüm kategorileri almak için böyle bir statik kütüphaneden tek bir sınıf kullanmak yeterlidir.
Nihai çözüm, Vladimir'in cevabının sonuna eklediği hiledir. Yalnızca kategori bildiren herhangi bir kaynak dosyaya bir " sahte sembol " yerleştirin. Çalışma zamanında kategorilerden herhangi birini kullanmak istiyorsanız , derleme zamanında sahte sembole bir şekilde başvurduğunuzdan emin olun , çünkü bu, nesne dosyasının bağlayıcı tarafından yüklenmesine ve dolayısıyla içindeki tüm Obj-C koduna neden olur. Örneğin, boş bir işlev gövdesine sahip bir işlev olabilir (çağrıldığında hiçbir şey yapmaz) veya erişilen küresel bir değişken olabilir (örn.int
okunduktan veya yazıldıktan sonra bu yeterlidir). Yukarıdaki tüm diğer çözümlerden farklı olarak, bu çözüm çalışma zamanında hangi kategorilerin kullanılabilir olduğunu kontrol eder derlenmiş koda kaydırır (bağlantılarını ve kullanılabilir olmalarını istiyorsa, sembole erişir, aksi takdirde sembole erişmez ve bağlayıcı göz ardı eder o).
Hepsi bu kadar millet.
Oh, bekleyin, bir şey daha var:
Bağlayıcının adlı bir seçeneği var -dead_strip
. Bu seçenek ne işe yarar? Bağlayıcı bir nesne dosyası yüklemeye karar verdiyse, kullanılsın veya kullanılmasın, nesne dosyasının tüm sembolleri bağlı ikili dosyaya dahil olur. Örneğin, bir nesne dosyası 100 işlev içerir, ancak bunlardan yalnızca biri ikili dosya tarafından kullanılır, nesne işlevlerinin bir bütün olarak eklendiği veya hiç eklenmediği için, 100 işlevin tümü hala ikiliye eklenir. Kısmen bir nesne dosyası eklemek genellikle bağlayıcılar tarafından desteklenmez.
Ancak, bağlayıcıya "ölü şerit" demeniz halinde, bağlayıcı önce tüm nesne dosyalarını ikiliye ekler, tüm referansları çözer ve sonunda ikiliyi kullanılmayan semboller için tarar (veya yalnızca kullanılmayan diğer semboller tarafından kullanılır) kullanımı). Kullanılmadığı tespit edilen tüm semboller, optimizasyon aşamasının bir parçası olarak kaldırılır. Yukarıdaki örnekte, kullanılmayan 99 işlev tekrar kaldırılmıştır. Eğer gibi seçenekleri kullanabilirsiniz, bu çok yararlıdır -load_all
, -force_load
ya Perform Single-Object Prelink
bu seçenekler kolayca bazı durumlarda önemli ölçüde ikili boyutları havaya uçurmak çünkü ve ölü sıyırma tekrar kullanılmayan kod ve verileri kaldırılır.
Ölü sıyırma C kodu için çok iyi çalışır (örneğin, kullanılmayan fonksiyonlar, değişkenler ve sabitler beklendiği gibi kaldırılır) ve ayrıca C ++ için oldukça iyi çalışır (örneğin, kullanılmayan sınıflar kaldırılır). Mükemmel değildir, bazı durumlarda bazı semboller çıkarılsa bile kaldırılmaz, ancak çoğu durumda bu diller için oldukça iyi çalışır.
Obj-C ne olacak? Unut gitsin! Obj-C için ölü sıyırma yoktur. Obj-C, çalışma zamanı özellikli bir dil olduğundan, derleyici derleme sırasında bir sembolün gerçekten kullanımda olup olmadığını söyleyemez. Örneğin, doğrudan referans veren bir kod yoksa bir Obj-C sınıfı kullanılmaz, değil mi? Yanlış! Sınıf adı içeren bir dizeyi dinamik olarak oluşturabilir, bu ad için bir sınıf işaretçisi isteyebilir ve sınıfı dinamik olarak ayırabilirsiniz. Örneğin,
MyCoolClass * mcc = [[MyCoolClass alloc] init];
Ayrıca yazabilirim
NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];
Her iki durumda mmc
da "MyCoolClass" sınıfının bir nesnesine bir başvurudır, ancak ikinci kod örneğinde bu sınıfa doğrudan başvuru yoktur (hatta statik bir dize olarak sınıf adı bile yoktur). Her şey sadece çalışma zamanında olur. Ve bu sınıflar olsa var olan aslında gerçek semboller. Gerçek semboller bile olmadıkları için kategoriler için daha da kötüdür.
Dolayısıyla, yüzlerce nesneye sahip statik bir kütüphaneniz varsa, ancak ikili dosyalarınızın çoğunun bunlardan sadece birkaçına ihtiyacı varsa, yukarıdaki çözümleri (1) ila (4) kullanmamayı tercih edebilirsiniz. Aksi takdirde, çoğu kullanılmasa bile, tüm bu sınıfları içeren çok büyük ikili dosyalar ile sonuçlanırsınız. Sınıflar için özel sembollere gerek yoktur, çünkü sınıflar gerçek sembollere sahiptir ve doğrudan referansta bulunduğunuz sürece (ikinci kod örneğinde olduğu gibi), bağlayıcı kullanımlarını oldukça iyi tanımlayacaktır. Ancak kategoriler için, yalnızca gerçekten ihtiyacınız olan kategorileri dahil etmeyi mümkün kıldığı için çözümü (5) düşünün.
Örneğin, NSData için bir kategori istiyorsanız, örneğin bir sıkıştırma / açma yöntemi eklemek isterseniz, bir başlık dosyası oluşturursunuz:
// NSData+Compress.h
@interface NSData (Compression)
- (NSData *)compressedData;
- (NSData *)decompressedData;
@end
void import_NSData_Compression ( );
ve bir uygulama dosyası
// NSData+Compress
@implementation NSData (Compression)
- (NSData *)compressedData
{
// ... magic ...
}
- (NSData *)decompressedData
{
// ... magic ...
}
@end
void import_NSData_Compression ( ) { }
Şimdi kodunuzdaki herhangi bir yerin import_NSData_Compression()
çağrıldığından emin olun . Nerede çağrıldığı veya ne sıklıkta çağrıldığı önemli değil. Aslında gerçekten çağrılmak zorunda değil, linker düşünürse yeterlidir. Örneğin, aşağıdaki kodu projenizin herhangi bir yerine koyabilirsiniz:
__attribute__((used)) static void importCategories ()
{
import_NSData_Compression();
// add more import calls here
}
importCategories()
Kodunuzu hiç aramak zorunda değilsiniz , öznitelik derleyici ve bağlayıcıyı çağrılsa bile çağrıldığına inandırır.
Ve son bir ipucu: Son bağlantı çağrısına
eklerseniz -whyload
, bağlayıcı, kullanım günlüğünde hangi kitaplığın hangi nesne dosyasından yüklendiğini hangi sembolün kullanıldığından yazdırır. Yalnızca kullanımda dikkate alınan ilk sembolü yazdıracaktır, ancak bu nesne dosyasının kullanımındaki tek simge olmak zorunda değildir.