Membuat Target Benar-Benar “Khusus MAS”
Di Bagian 1, kita mendaftarkan Bundle ID khusus MAS dan menduplikasi build target FocusTimer MAS. Tetapi target tersebut masih hanya salinan dari target distribusi langsung.
Build MAS harus berbeda dari build distribusi langsung dalam tiga hal.
- Entitlement — hanya set minimum yang sesuai untuk App Store
- Info.plist — hilangkan kunci Sparkle, tambahkan metadata App Store
- Kode — percabangan agar tetap bisa dikompilasi meskipun tanpa Sparkle
Dalam artikel ini, kita akan memisahkan ketiganya.
Seperti di Bagian 1,
FocusTimer,com.example.FocusTimer.mas, dan sebagainya semuanya adalah nilai contoh.
Langkah 1 — File Entitlement Khusus MAS
Entitlement adalah file yang mencantumkan izin sistem mana yang diminta oleh aplikasi. Dalam build distribusi langsung, Sparkle memerlukan izin tambahan untuk menginstal pembaruan, tetapi build MAS tidak memiliki Sparkle, sehingga izin tersebut tidak diperlukan.
Buat file FocusTimer-MAS.entitlements baru di root proyek.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
Dibandingkan dengan FocusTimer.entitlements distribusi langsung, inilah perbedaannya.
- Tidak ada izin terkait Sparkle — Entri
temporary-exception.mach-lookup.*dari build distribusi langsung (pengecualian yang memungkinkan Sparkle berkomunikasi dengan helper installer) tidak ada alasan untuk ada di build MAS. - Tidak ada
network.client— Dengan Sparkle dihapus, aplikasi FocusTimer itu sendiri tidak membuat panggilan jaringan apa pun. Adalah jujur untuk menghapus izin yang tidak Anda gunakan, dan izin yang tidak perlu juga ditandai selama review. Semakin kecil permukaan izin yang diminta aplikasi, semakin baik.
Contoh di atas adalah bentuk paling sederhana yang mungkin, dengan hanya cukup izin untuk “membaca dan menulis file yang secara eksplisit dipilih pengguna.” Jika aplikasi Anda benar-benar menggunakan jaringan atau sumber daya lain, tambahkan izin yang sesuai, tetapi jangan pernah menyertakan izin yang tidak Anda gunakan.
Langkah 2 — File Info.plist Khusus MAS
Demikian pula, buat FocusTimer-MAS-Info.plist baru di root proyek.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
</dict>
</plist>
Ada dua perbedaan dari FocusTimer-Info.plist distribusi langsung.
① Semua kunci Sparkle SU* dihilangkan
Info.plist distribusi langsung berisi kunci konfigurasi Sparkle seperti SUFeedURL dan SUPublicEDKey. Info.plist MAS tidak boleh mengandung satu pun kunci ini. Review App Store melarang pembaruan otomatis bawaan, sehingga bahkan kunci yang hanya mengisyaratkan fitur semacam itu yang tersisa di plist bisa menjadi masalah. Paling aman untuk tidak menyertakan kuncinya sama sekali.
② Kunci metadata App Store ditambahkan
LSApplicationCategoryType— kategori tempat aplikasi berada di App Store.public.app-category.productivitydalam contoh di atas berarti kategori “Produktivitas.” Nilai ini harus cocok dengan kategori yang Anda tetapkan di App Store Connect di Bagian 3, sehingga peringatan ketidakcocokan tidak muncul selama review.ITSAppUsesNonExemptEncryption— apakah aplikasi menggunakan teknologi enkripsi yang tunduk pada peraturan ekspor. Jika Anda hanya menggunakan enkripsi standar (misalnya, HTTPS, API sistem standar) atau tidak menggunakan enkripsi sama sekali, Anda dapat menetapkan ini kefalse. Meng-hardcode kunci ini terlebih dahulu secara otomatis melewati kuesioner enkripsi yang muncul setiap kali Anda mengunggah build.
Sebelum menetapkan
ITSAppUsesNonExemptEncryptionkefalse, verifikasi bahwa aplikasi Anda benar-benar tidak menggunakan enkripsi non-standar. Jika Anda tidak yakin, lebih aman untuk merujuk dokumentasi Apple tentang enkripsi.
Langkah 3 — Pengaturan Build Target MAS
Sekarang untuk pembaruan pengaturan build yang kita tunda di Bagian 1, Bagian 2-3. Di TARGETS → FocusTimer MAS → Build Settings, selaraskan hal-hal berikut.
| Kunci pengaturan build | Nilai |
|---|---|
PRODUCT_BUNDLE_IDENTIFIER | com.example.FocusTimer.mas |
INFOPLIST_FILE | FocusTimer-MAS-Info.plist |
CODE_SIGN_ENTITLEMENTS | FocusTimer-MAS.entitlements |
ENABLE_APP_SANDBOX | YES |
ENABLE_HARDENED_RUNTIME | YES |
ENABLE_USER_SELECTED_FILES | readwrite |
CODE_SIGN_STYLE | Automatic |
MARKETING_VERSION, CURRENT_PROJECT_VERSION | Sama dengan target distribusi langsung |
Poin-poin utamanya adalah sebagai berikut.
INFOPLIST_FILEdanCODE_SIGN_ENTITLEMENTSharus mengarah ke file khusus MAS yang baru saja Anda buat. Kesalahan umum adalah membiarkan kedua baris ini mengarah ke file distribusi langsung.- Menetapkan
CODE_SIGN_STYLEkeAutomaticmembiarkan Xcode secara otomatis menerbitkan dan mencocokkan profil provisioning Mac App Store dan sertifikat Apple Distribution. Anda tidak perlu mengelola penandatanganan sendiri. - Jika aplikasi Anda tidak menggunakan jaringan, jangan atur
ENABLE_OUTGOING_NETWORK_CONNECTIONS. Ini sama dengan alasan menghapusnetwork.clientdari entitlement. - Pertahankan nomor versi (
MARKETING_VERSION, dll.) pada nilai yang sama dengan target distribusi langsung, agar versi dua saluran tidak menyimpang.
Langkah 4 — Percabangan Kode Sparkle (#if canImport(Sparkle))
Bahkan setelah memisahkan file konfigurasi, satu masalah tetap ada. Di suatu tempat dalam kode sumber terdapat import Sparkle dan kode yang menggunakan Sparkle, tetapi Sparkle tidak ditautkan ke target MAS, sehingga pernyataan import itu sendiri menghasilkan error kompilasi.
Solusinya adalah membungkus kode terkait Sparkle dengan direktif kompilasi kondisional Swift #if canImport(Sparkle).
Mengapa canImport — Mengapa Tidak Menggunakan Flag Terpisah
Anda juga bisa membercabangkan dengan flag kompilasi kustom seperti #if MAS_BUILD. Tetapi kemudian Anda harus secara manual menjaga sinkronisasi pengaturan “aktifkan flag untuk target MAS, matikan untuk target distribusi langsung.” Itu mudah tidak sinkron seiring bertambahnya target atau berubahnya pengaturan.
canImport(Sparkle) berbeda. Ini membuat kompilator langsung memeriksa apakah “Sparkle.framework ditautkan ke target ini.” Karena kita menghapus Sparkle dari dependensi paket target MAS di Bagian 1, canImport(Sparkle) secara otomatis false di target MAS. Tidak ada flag terpisah yang perlu dijaga sinkronisasinya. Itulah mengapa aplikasi macOS menggunakan pola ini sebagai standar de facto saat memisahkan saluran MAS.
Percabangan ① — Bungkus File Khusus Sparkle Seluruhnya
File yang hanya berisi logika pembaruan otomatis (misalnya, UpdaterCoordinator.swift) mendapat seluruh file dibungkus dalam #if.
#if canImport(Sparkle)
import Foundation
import Sparkle
import Combine
@Observable
@MainActor
final class UpdaterCoordinator {
// Kode yang membungkus Sparkle updater …
}
#endif
Dalam build MAS, file ini dikompilasi seolah kosong dan tidak berpengaruh sama sekali.
Percabangan ② — Bungkus Juga Pengguna Sparkle
Kode yang membuat UpdaterCoordinator dan meneruskannya ke UI juga perlu dibercabangkan. Jika tidak, build MAS akan mereferensikan “tipe yang tidak ada.”
@main
struct FocusTimerApp: App {
@State private var viewModel = CompositionRoot.makeContentViewModel()
#if canImport(Sparkle)
@State private var updater = UpdaterCoordinator()
#endif
var body: some Scene {
Settings {
#if canImport(Sparkle)
PreferenceView(viewModel: viewModel, updater: updater)
#else
PreferenceView(viewModel: viewModel)
#endif
}
MenuBarExtra {
#if canImport(Sparkle)
MenuBarContent(updater: updater)
#else
MenuBarContent()
#endif
} label: {
// Ikon menu bar …
}
}
}
private struct MenuBarContent: View {
#if canImport(Sparkle)
@Bindable var updater: UpdaterCoordinator
#endif
var body: some View {
Button("Tampilkan FocusTimer") { /* … */ }
#if canImport(Sparkle)
Button("Periksa Pembaruan…") { updater.checkForUpdates() }
.disabled(!updater.canCheckForUpdates)
#endif
// Item menu bersama lainnya …
}
}
Ada tiga poin utama.
- Deklarasi field
updateritu sendiri dibungkus dalam#if. - Setiap tampilan yang menerima
updater(PreferenceView,MenuBarContent) dibercabangkan menjadi dua bentuk — satu untuk saat Sparkle ada dan satu untuk saat tidak ada. - Elemen UI khusus Sparkle seperti “Periksa Pembaruan…” juga dibungkus dalam
#if. Dalam build MAS, item menu ini sama sekali tidak muncul — dan memang benar demikian, edisi App Store seharusnya tidak memiliki menu pembaruan sendiri.
Elemen seperti toggle “Aktifkan pembaruan otomatis” di layar pengaturan (PreferenceView) semuanya harus dibungkus dengan pola yang sama.
Rangkuman Bagian 2
Jika Anda telah mengikuti sejauh ini, Anda sekarang memiliki:
- ✅ File entitlement khusus MAS
FocusTimer-MAS.entitlements(izin minimum) - ✅
FocusTimer-MAS-Info.plistkhusus MAS (kunci Sparkle dihapus, metadata App Store ditambahkan) - ✅ Pengaturan build target MAS yang diselaraskan
- ✅ Kode pembaruan otomatis yang dibercabangkan dengan
#if canImport(Sparkle)
Sekarang target FocusTimer MAS benar-benar berbeda dari target distribusi langsung — ini dalam bentuk yang dapat dimasukkan ke App Store. Yang tersisa adalah menyiapkan jalur untuk benar-benar mengunggah build ini ke App Store Connect.
Di bagian berikutnya, kita akan menyelesaikan seri dengan membuat ExportOptions-MAS.plist untuk upload, mendaftarkan catatan aplikasi di App Store Connect, dan membahas cara memverifikasi agar dua saluran tetap tidak rusak ke depannya.