Tüm Yazılar
KategoriTools
Okuma Süresi
10 dk
Yayın Tarihi
2026-10-21
Kelime Sayısı
1.514kelime

Kahveni hazırla - bu içerikli bir makale!

sizeledger: bir iOS uygulamasının her megabaytı nereden geliyor

Özet

Bir .ipa, .app ya da .xcarchive'i okuyup boyutunu kategori, framework, dil ve mimariye göre ayıran Swift CLI ve kütüphanesi.

  • sizeledger bir .ipa, .app ya da .xcarchive okur ve byte'larını kategori, framework, dil ve CPU mimarisine göre gösterir.
  • Yalnızca dosya okur; uygulamayı derlemez, çalıştırmaz, yüklemez ya da değiştirmez ve macOS ile Linux'ta aynı raporu verir.
  • Üç boyutu ayrı tutar: dosyaların toplamı, 4 KiB bloklarla kurulum tahmini ve .ipa içindeki sıkıştırılmış boyut.
  • ZIP okuyucu, DEFLATE çözücü ve Mach-O ayrıştırıcı Swift ile yazıldı; bu yüzden SizeLedgerKit kütüphanesinin bağımlılığı yok.
  • App Store indirme boyutunu vermez; diff ve budget komutları 0.1.10 itibarıyla planlanmış durumda, henüz yayında değil.
sizeledger: bir iOS uygulamasının her megabaytı nereden geliyor

İçindekiler


Sorun

Bir iOS uygulaması her sürümde biraz büyür ve "onu ne büyüttü?" sorusu genellikle .ipa'yı açıp dosyaları boyuta göre sıralayarak cevaplanır. Bu, sorunun yalnız bir kısmını cevaplar. Bir framework birçok dosyadan oluşan bir klasördür; bir dil, uygulamaya ve framework'lerine dağılmış .lproj klasörlerinin toplamıdır; universal bir binary her mimari için ayrı bir slice taşır; asset catalog ise tek ve içi görünmeyen bir Assets.car dosyasıdır. Apple'ın kendi sayıları, yani Xcode'un App Thinning Size Report'u ve App Store Connect'teki boyutlar, Apple'ın derleme hattını ve imzalı bir build'i gerektirir. Bu yüzden bir pull request kontrolünün Linux runner'da üretebileceği şeyler değildir.

sizeledger bu boşluk için yazılmış bir komut satırı aracı ve Swift kütüphanesidir. Bir .ipa, .app ya da .xcarchive verdiğinizde byte'ların nerede olduğunu gösterir: kategoriye göre (ana çalıştırılabilir dosya, framework'ler, app extension'lar, App Clip'ler, watch uygulaması, yerelleştirmeler, asset catalog'lar, kod imzası, diğer kaynaklar), framework ve kaynağa göre, .lproj diline göre ve CPU mimarisi slice'ına göre. Yalnızca dosyaları okur. Hiçbir şeyi derlemez, çalıştırmaz, yüklemez ya da değiştirmez; macOS'ta ve Linux'ta aynı raporu verir.

Repo 15 Ekim 2026'da açıldı ve 0.1.0 aynı gün yayınlandı. 15–21 Ekim arasında on yama sürümü daha çıktı; güncel sürüm 0.1.10. Bunların neredeyse hepsi okuyucuları özel hazırlanmış (crafted) girdilere karşı sağlamlaştırıyor; bu yazıdaki derslerin çoğu da oradan geliyor.

Tasarım kararları ve alternatifler

Üç boyut, birbirinden ayrı

En önemli karar neyin iddia edilmeyeceğiydi. "Uygulama boyutu" en az üç farklı anlama gelir ve sizeledger her birini kendi adıyla raporlar:

  • Toplam (bytes): dosyaların boyutlarının toplamı, bir dosya yöneticisinin gösterdiği değer.
  • Kurulum boyutu, tahmin (installBytes): her dosya, APFS'nin sakladığı gibi tam 4 KiB bloklara yukarı yuvarlanır. Kaynakta bu InstallSize.estimate'tir ve blockSize 4.096'dır.
  • .ipa içinde sıkıştırılmış (compressedBytes): bundle dosyalarının arşiv içinde kapladığı yer; yalnız .ipa girdisinde.

Hiçbiri App Store'un indirme ya da kurulum boyutu değildir. App Store uygulamayı cihaz başına inceltir (tek mimari slice, yalnız o cihazın asset görselleri), binary'leri şifreler ve indirmeyi sıkıştırır. Alternatif, yüklenen dosyadan mağaza sayısını tahmin eden bir formül olurdu. Ne kadar yanıldığı bilinmeyen bir tahmin, ne olduğunu dürüstçe söyleyen yerel bir sayıdan kötüdür. Bu yüzden rapor kurulum boyutuna "tahmin" der ve girdi bir simülatör build'iyse ya da universal binary içeriyorsa not ekler; bunlar bir cihazın daha küçük bir şey görmesinin en yaygın iki nedenidir.

Tahmin varsayılmıyor, ölçülüyor. Testler iki SampleShop fixture uygulamasını açıp du ile karşılaştırır: bir Mac'te (Apple silicon, macOS 27.0.1, APFS) kurulum tahmini du -sk ile birebir aynıdır ve toplam du -A'nın %0,16 yakınındadır; Linux konteynerinde (swift:6.4.1-noble, aarch64) toplam du -b --apparent-size ile aynıdır, kurulum tahmini ise orada klasör başına da 4 KiB sayan du -sk'nın %0,65 yakınındadır. Testler iki ortamda da %1'in üstünde kırılır.

Kütüphanede bağımlılık yok

SizeLedgerKit'in hiçbir paket bağımlılığı yok. ZIP okuyucu (ZIP64 dahil), DEFLATE çözücü ve Mach-O ayrıştırıcı (thin, fat ve fat64) kütüphanenin içinde Swift ile yazıldı. Akla gelen alternatif unzip, lipo ve otool çağırmaktır; ama lipo ve otool Xcode ile gelir. Aracın amacı ise ölçümün her yerde, macOS build işinden .ipa'yı artifact olarak alan bir Linux CI işinde bile çalışabilmesidir. Okuyucuları Swift ile yazmak, aracın okuduğu her byte'ın testlerin kapsadığı koddan geçmesi demek: DEFLATE çözücü stored, fixed ve dynamic bloklar için zlib çıktısıyla, Mach-O ayrıştırıcı ise thin, 32-bit, fat ve fat64 dosyalarla sınanıyor. Yalnızca komut satırı hedefi swift-argument-parser'a bağımlı.

İnsan ve CI için çıktı

Varsayılan çıktı bir terminal tablosudur. --format markdown bir pull request yorumu ya da job summary için GitHub Markdown'ı yazar; --format json ise anahtar sırası sabit bir rapor üretir, böylece commit'lenebilir, jq ile sorgulanabilir ve diff ile karşılaştırılabilir. 1.0'a kadar JSON düzeni schemaVersion 0 taşır ve bir minor sürümde değişebilir; değiştiğinde changelog bunu söyler.

Girdi güvenilmez

Bir .ipa başkasının size verdiği bir dosyadır; sizeledger onu düşmanca kabul eder. 0.1.4'ten beri 500.000'den fazla dosya içeren, 64 klasörden derin yol barındıran, 8 GiB'tan fazla sıkıştırılmamış byte bildiren ya da saklanan boyutunun 300 katından fazlasına açılan girdiyi reddeder. Sınırlar bayraklarla (--max-files, --max-depth, --max-bytes, --max-ratio), kütüphanede ise SizeLedger.analyze(_:limits:)'e verilen bir AnalysisLimits değeriyle ayarlanır. 0.1.1, Mach-O başlıklarının okunma biçimini değiştirdi: fat başlığı bir slice'ı DEFLATE girdisinin 1 GB içine yerleştiren 1 MB'lık özel bir .ipa, 0.1.0'ın bellekte 3,77 GB tutmasına yol açıyordu. Başlıklar artık en fazla 4 KiB'lık aralıklar olarak okunuyor ve sıkıştırma çözülürken yalnız DEFLATE geri referanslarının ihtiyaç duyduğu 32 KiB'lık pencere tutuluyor; aynı dosya bundan sonra bir Mac'te (Apple silicon) 11,5 MB kullandı (PR #27).

Gerçek bir hata: Swift string'leri dosya yolu değildir

En öğretici hata boyutlarla ilgili değildi. Swift'in String'i kanonik olarak eşdeğer metni eşit sayar ve Character'e, yani grapheme cluster'a göre böler. Bir unzip aracı ve bir dosya sistemi bunların hiçbirini yapmaz. 0.1.7'ye kadar sizeledger girdi yollarını String olarak işliyordu ve PR #52 bunun neyi bozduğunu sıralıyor:

  • Payload/G.app/../\u{301}evil kabul ediliyordu, oysa README bir .. bileşeninin reddedildiğini söylüyor. U+0301 birleştirici işareti önündeki / ile tek bir Character oluşturuyor, bu yüzden split(separator: "/") hiçbir zaman .. üretmiyordu.
  • 5 MB'lık Payload/G.app/\u{301}big.bin, hasPrefix("Payload/G.app/") kontrolünü geçemiyor, bundle dışındaki dosyalara düşüyor ve toplamlardan çıkıyordu.
  • NFC ve NFD yazılmış café.bin tek bir String sözlük anahtarıydı; ikinci dosya birincinin yerine geçiyor ve 5 MB not düşülmeden toplamlardan kayboluyordu.
  • Ardından birleştirici işaret gelen bir /, --max-depth sayımına girmiyordu.

Düzeltme (commit f77172f, "fix(ipa): split and compare entry paths on their bytes") hangi dosyaların var olduğu, hangi klasörde durduğu ve ne kadar derin olduğu kararlarının tümünü byte'lara taşıdı. İç BundlePath tipi utf8 görünümleri üzerinde böler, önek kontrol eder ve karşılaştırır; PathKey tipi ise sözlükleri ve kümeleri byte'larla anahtarlar. PathBytesTests içindeki sekiz yeni test, değişiklikten önce main üzerinde kırılıyordu.

İlk düzeltme hikâyenin tamamı değildi. 0.1.8'de (PR #55) adı geçerli UTF-8 olmayan bir .ipa girdisi hâlâ Latin-1 olarak çözülüp o metinle tanımlanıyordu; böylece caf\xE9.bin ve UTF-8 café.bin tek bir yol oluyordu: unzip -l'nin 4 dosya ve 5.000.179 B olarak listelediği bir arşiv 3 dosya ve 179 B olarak raporlanıyordu. Kimlik artık arşivin sakladığı byte'lardan geliyor (PathKey(bytes:)); gösterilen ad yine Latin-1 okuması, rapor da iki satırın aynı okunabileceğini not ediyor. İkisinden çıkardığım ders şu: bir yol, metin olarak gösterilen bir byte dizisidir ve String eşitliği, hasPrefix ya da String anahtarlı bir sözlük üzerinden yapılan her karşılaştırma, dosya sisteminin hiç vermediği bir kararı verir.

Kullanım

macOS ya da Linux'ta Homebrew ile kurun ya da macOS (universal) veya Linux (x86_64, aarch64) için bir release binary'si indirip SHA256SUMS ile doğrulayın:

sh
1brew install muhittincamdali/tap/sizeledger

Sonra dışa aktarılmış bir .ipa'yı, bir build'den çıkan .app'i ya da bir .xcarchive'i ölçün:

sh
1sizeledger analyze path/to/MyApp.ipa

SampleShop 2.0 fixture'ında bu komut 21 dosyada 8,75 MB, 8,80 MB kurulum tahmini ve 2,72 MB sıkıştırılmış boyut yazar; catalog.json uygulamanın %70,8'i, Assets.car %18,1'idir. CI için README yaygın durumları gösteriyor:

sh
1# Keep the report for later
2sizeledger analyze build/MyApp.ipa --out size/main.json
3 
4# Markdown in a GitHub Actions job summary
5sizeledger analyze build/MyApp.xcarchive --format markdown >> "$GITHUB_STEP_SUMMARY"

0.1.10'dan beri --out var olan bir dosyanın üzerine yazmaz: --force verilmedikçe çıkış kodu 2 ile biter. Çıkış kodu 0 raporun üretildiğini, 2 girdinin okunamadığını ya da raporun yazılamadığını, 64 seçeneklerin geçersiz olduğunu söyler.

Kütüphane aynı raporu kendi araçlarınıza verir. .package(url: "https://github.com/muhittincamdali/sizeledger", from: "0.1.10") ve SizeLedgerKit ürününü ekleyin. Linux workflow'unun her pull request'te ve main'e her push'ta derleyip çalıştırdığı Examples/LibraryUsage örneği şöyle başlar:

swift
1let report: SizeReport
2do {
3 report = try SizeLedger.analyze(URL(fileURLWithPath: path))
4} catch {
5 print("error: \(Sanitize.text(String(describing: error)))")
6 exit(2)
7}
8 
9let totals = report.totals
10print("\(Sanitize.text(report.app.name ?? report.input.bundle)): \(totals.files) files")
11print(" bytes: \(ByteFormat.string(totals.bytes))")
12print(" install size (estimate): \(ByteFormat.string(totals.installBytes))")

Adlar, yollar ve notlar uygulamadan gelir; bu yüzden terminale ulaşmadan önce Sanitize.text'ten geçerler. Bu fonksiyon kontrol karakterlerini, çift yönlü metin override'larını ve görünmeyen karakterleri değiştirir.

Sınırlar

  • Boyutlar yerel ölçümlerdir. App Store indirme ve kurulum boyutları Apple tarafındaki inceltme, şifreleme ve sıkıştırmaya bağlıdır; sizeledger bir tahmin verir ve bunu açıkça söyler.
  • Asset catalog'lar dosya olarak sayılır. Assets.car içindeki rendition'lar henüz listelenmiyor (0.8 için planlandı, macOS'ta assetutil ile).
  • Swift metadata, __swift5_* bölümlerinin boyutudur ve bir alt sınırdır, çünkü metadata'nın bir kısmı __const içinde durur.
  • iOS tarzı bundle'lar sınıflandırılır. Contents/ klasörlü bir macOS .app'i ölçülür ama dosyaları "Other resources" altına düşer.
  • Birden fazla uygulama, şifreli girdi, stored ve DEFLATE dışında sıkıştırma ya da .. yol bileşeni içeren bir .ipa tahmin yürütülmeden, bir mesajla reddedilir.
  • Varsayılan sınırlar içinde özel hazırlanmış bir girdi yine de zaman harcatabilir: bulunan en yavaşı, her biri 4,29 GB bildiren iki girdili 28,6 MB'lık bir dosya, başka işlerle meşgul bir Mac'te (Apple silicon) 15–24 sn sürdü. Daha düşük bir --max-bytes bu sınırı düşürür.
  • Bugün yalnız analyze var. İki raporu karşılaştırma (diff, 0.2 için planlandı), GitHub Action ile budget kuralları (0.3 için planlandı), HTML treemap (0.8 için planlandı), dondurulmuş JSON şeması (1.0 için planlandı) ve Android .apk/.aab desteği (1.1 için planlandı) yol haritasında, araçta değil. budget gelene kadar CI'da sınır kontrolü .totals.installBytes üzerinde tek satırlık bir jq komutudur.

Test paketinde 159 test var; CI bunları macOS'ta ve Linux'ta (x86_64 ve arm64) çalıştırıyor. Biri UTF-8 olmayan bir dosya adına ihtiyaç duyuyor, APFS bunu kabul etmediği için yalnız Linux'ta koşuyor.

Kaynak kod: github.com/muhittincamdali/sizeledger

Etiketler

#ios#app-size#ipa#mach-o#swift#cli#linux
Muhittin Çamdalı

Muhittin Çamdalı

Senior Mobile Engineer

12+ yıllık deneyime sahip Senior Mobile Engineer. iOS, Flutter ve React Native ile mobil ürünler geliştiriyorum; full-stack ve AI-native çalışıyorum.

iOS Geliştirme Haberleri

Haftalık Swift tips, SwiftUI tricks ve iOS best practices. Spam yok, sadece değerli içerik.

Onay e-postasındaki bağlantıyı açıp “Aboneliğimi onayla” düğmesine bastığında aboneliğin başlar. Bültende açılma/tıklama istatistikleri tutulur; dilediğin an tek tıkla ayrılabilirsin. Gizlilik

Paylaş

İlgili İçerik