Müşteriye kapalı kaynak bir SDK vermem gerekiyordu. Eskiden bunu Cocoapods vendored framework olarak yapardım; SPM’e geçtikten sonra binaryTarget ile daha temiz bir akış kuruyorum. Kurulum adımlarını ve karşılaştığım pitfall’ları paylaşayım.
XCFramework üretimi
Binary target .xcframework bekliyor. Framework’ü iki adımda üretiyorum:
xcodebuild archive
-scheme MySDK
-destination "generic/platform=iOS"
-archivePath build/MySDK-iOS
SKIP_INSTALL=NO
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild archive
-scheme MySDK
-destination "generic/platform=iOS Simulator"
-archivePath build/MySDK-iOS-Simulator
SKIP_INSTALL=NO
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
xcodebuild -create-xcframework
-framework build/MySDK-iOS.xcarchive/Products/Library/Frameworks/MySDK.framework
-framework build/MySDK-iOS-Simulator.xcarchive/Products/Library/Frameworks/MySDK.framework
-output build/MySDK.xcframeworkBUILD_LIBRARY_FOR_DISTRIBUTION=YES kritik. Olmadan module stability kaybediyorsun ve farklı Swift sürümleriyle derlenmiş consumer’larda crash oluyor.
Package.swift: remote binary target
let package = Package(
name: "MySDK",
platforms: [.iOS(.v16)],
products: [
.library(name: "MySDK", targets: ["MySDK"])
],
targets: [
.binaryTarget(
name: "MySDK",
url: "https://sdk.example.com/releases/1.2.0/MySDK.xcframework.zip",
checksum: "a3b2c1d4..."
)
]
)Checksum’u swift package compute-checksum MySDK.xcframework.zip ile alıyorsun. Yanlış checksum verirsen resolve adımında hata alıyorsun.
Zip formatı önemli
İlk seferimde tar.gz yollamaya çalıştım; SPM reddetti. Sadece .zip kabul ediyor. Ayrıca zip içinde xcframework doğrudan root’ta olmalı; bir klasörün içinde paketlenmiş şekilde olmamalı:
zip -r MySDK.xcframework.zip MySDK.xcframework-r flag’i unutulduğunda sadece klasör boş olarak paketleniyor ve hata mesajı yanıltıcı oluyor.
Local test için path-based binary target
Release öncesi consumer’dan SDK’yı test ettirmek için yerel dosyadan yüklüyorum:
.binaryTarget(
name: "MySDK",
path: "./artifacts/MySDK.xcframework"
)Bu şekilde SDK tüketici projesinin yanına kopyalayıp Package.swift dependency’sini local path’e çeviriyorum. Checksum gerektirmiyor.
Sürümleme: git tag şart
SDK consumer’ı .package(url:, from:) ile tüketiyor. Bu semver tag gerektiriyor. Her release’te:
git tag 1.2.0
git push origin 1.2.0Ve Package.swift içindeki URL + checksum’ı o release’e göre güncelliyorum. Tag atmayı unutursan consumer eski kodu alır, binary yeniyi; inconsistency yaşanır.
Bitcode ve strip sorunu
iOS 17’den sonra bitcode deprecated. Yine de eski Xcode ile build edilmiş SDK’lar bitcode ile geliyorsa App Store upload’ta hata veriyor. Çözüm: archive esnasında ENABLE_BITCODE=NO eklemek. Ayrıca symbol strip için STRIP_INSTALLED_PRODUCT=NO aksi halde crash symbolication zorlaşıyor.
Consumer tarafında debug
Binary target’tan gelen framework’te breakpoint koyamıyorsun. Source-level debug için consumer’a ayrıca .dSYM göndermelisin; release’te bunları zip içinde birlikte paketleyip iç bir S3’te tutuyorum:
MySDK.xcframework.zip
MySDK.xcframework.dSYM.zipPrivacy Manifest
iOS 17 Privacy Manifest gerektiği için SDK içine PrivacyInfo.xcprivacy koymam lazım. Binary target içinde bundle olarak paketlendiğinde consumer app privacy manifest aggregation’da bu dosyayı okuyabiliyor; koymayı unutursan App Store submit’te uyarı alıyorsun.
Hangi durumda binary target’a gitmem
Open-source kütüphane ise kaynak paylaşmak daha iyi; consumer farklı Swift sürümünde derleyebilsin. Binary target sadece kapalı kaynak veya lisans kısıtı olan senaryolarda değer. SDK boyutu çok büyükse Carthage veya manual vendored framework akışı da hâlâ geçerli; ama CI tarafı SPM’te daha az acı.
Son tavsiye
SDK’yı yayınlamadan önce ayrı bir test project açıp SPM üzerinden tüketen bir örnek app yaz. Çoğu integration sorununu consumer perspektifinden görmeden fark etmezsin.