Xcode Cloud, Apple'ın 2021'de tanıttığı, Xcode ve App Store Connect'e entegre CI/CD servisi. GitHub Actions, Bitrise veya CircleCI gibi harici araçlara göre temel avantajı: Apple ekosistemiyle native entegrasyon — provisioning profiles, code signing, TestFlight deployment, App Store submission hepsi otomatik. Dezavantajı: Apple Developer Program ücreti içinde gelen sınırlı compute hour'ların üstüne geçince pahalıya patlıyor. Bu rehber, Xcode Cloud'u gerçek production pipeline'ında verimli kullanmak için pratik optimizasyon tekniklerini kapsar.
💡 Pro Tip: Xcode Cloud"un free compute hour"larini (ayda 25 saat) asmamak icin en guclü silah: branch trigger'ları iyi tanımla ve paralel workflow'ları akıllıca kullan. Her push"ta tam test suite koşturmak yerine PR merge"de koştur, main"e pushta da sadece release workflow tetikle.
İçindekiler
- Workflow Mimarisi ve ci_workflows Yapısı
- Branch Triggers ve Smart Tetikleyiciler
- Derived Data Cache Optimizasyonu
- CocoaPods ve SPM Cache Stratejisi
- Paralel Test Destinations
- UI Test Sharding
- Post-Build Actions: Slack ve TestFlight
- Environment Secrets Yönetimi
- Fastlane Lane Migration
- Cost Optimization Stratejileri
- Apple Developer Account ve Concurrent Build Limits
- Sonuç
Workflow Mimarisi ve ci_workflows Yapısı
Xcode Cloud workflow'ları projenin root dizinine yerleştirilen ci_workflows/ klasöründeki YAML dosyaları ile tanımlanıyor. Her YAML = bir workflow.
Temel Dosya Yapısı
swift
1MyApp.xcodeproj/2ci_workflows/3 ci.yml # PR kontrolü — hızlı, minimal4 nightly.yml # Nightly — tam test + analiz5 release.yml # TestFlight deploy6 app-store.yml # App Store submissionMinimal CI Workflow
yaml
1# ci_workflows/ci.yml2name: CI — Pull Request Check3 4on:5 - pullRequestOpen6 - pullRequestReopened7 8environment:9 xcode: latest-release10 macOS: latest-release11 12start_condition:13 source_branch:14 exclude:15 - main16 - release/**17 18jobs:19 build:20 name: Build & Unit Tests21 steps:22 - build:23 platform: iOS Simulator24 destination: iPhone 16 Pro25 configuration: Debug26 scheme: MyApp27 - test:28 platform: iOS Simulator29 destination: iPhone 16 Pro30 test_plan: UnitTests31 maximum_test_repetitions: 232 retry_on_failure: trueRelease Workflow
yaml
1# ci_workflows/release.yml2name: TestFlight Release3 4on:5 - push6 7environment:8 xcode: 16.39 macOS: 15.410 11start_condition:12 source_branch:13 any_of:14 - main15 - release/**16 17jobs:18 archive_and_deploy:19 name: Archive & TestFlight20 steps:21 - build:22 platform: iOS23 destination: Any iOS Device24 configuration: Release25 scheme: MyApp26 - test:27 platform: iOS Simulator28 destination: iPhone 16 Pro29 test_plan: SmokeTests30 - archive:31 platform: iOS32 configuration: Release33 scheme: MyApp34 export_options:35 method: app-store36 upload_symbols: true37 compile_bitcode: false38 - testflight:39 groups:40 - Internal Testers41 - Beta Testers42 notify_testers: trueBranch Triggers ve Smart Tetikleyiciler
Gereksiz build'lerin önüne geçmek için trigger stratejisi kritik.
Branch-Based Trigger Matrisi
yaml
1# Sadece belirli path'ler değiştiğinde tetikle2start_condition:3 source_branch:4 any_of:5 - feature/**6 - bugfix/**7 source_changes:8 any_of:9 - "**/*.swift"10 - "**/*.storyboard"11 - "**/*.xib"12 - "project.pbxproj"13 # .md, .txt, .gitignore değişince build TETIKLEME14 none_of:15 - "**/*.md"16 - "**/*.txt"17 - ".gitignore"18 - "docs/**"Scheduled Trigger (Nightly Build)
yaml
1# ci_workflows/nightly.yml2name: Nightly Full Test Suite3 4on:5 - schedule6 7schedule:8 cron: "0 22 * * 1-5" # UTC 22:00, pazartesi-cuma9 branch: main10 11jobs:12 full_test_suite:13 name: Full Test + Static Analysis14 steps:15 - analyze:16 platform: iOS Simulator17 destination: iPhone 16 Pro18 scheme: MyApp19 - test:20 platform: iOS Simulator21 destination: iPhone 16 Pro22 test_plan: AllTests23 collect_result_bundle: trueTag-Based Release Trigger
yaml
1start_condition:2 tag:3 any_of:4 - "v*.*.*" # v1.2.35 - "rc-*" # rc-16 none_of:7 - "nightly-*"Derived Data Cache Optimizasyonu
Xcode Cloud her build'de temiz bir container başlatıyor — derived data cache'i persist etmek build sürelerini %30-50 kısaltabilir.
Cache Neden Kritik?
- Olmadan: Her build ~8-15 dk (framework compile dahil)
- Cache ile: ~3-6 dk (sadece değişen dosyalar compile)
- SPM resolve olmadan: +2-4 dk ekstra geliyor
Custom Cache Script
Xcode Cloud, derived data'yı built-in cache etmiyor ama ci_post_clone.sh ve ci_pre_build.sh hook'ları ile cache mekanizması kurabilirsin.
bash
1#!/bin/sh2# ci_scripts/ci_pre_build.sh3# Derived data cache restore4 5set -e6 7CACHE_DIR="${CI_DERIVED_DATA_PATH}/../../.cache"8DERIVED_DATA_ARCHIVE="${CACHE_DIR}/derived_data.tar.gz"9CACHE_KEY_FILE="${CI_DERIVED_DATA_PATH}/../../.cache_key"10 11# Cache key: Package.resolved + proje hash12CURRENT_KEY=$(cat Package.resolved Project.xcodeproj/project.pbxproj | md5)13 14if [ -f "$CACHE_KEY_FILE" ] && [ "$(cat $CACHE_KEY_FILE)" = "$CURRENT_KEY" ]; then15 if [ -f "$DERIVED_DATA_ARCHIVE" ]; then16 echo "Cache hit — derived data restore ediliyor..."17 tar -xzf "$DERIVED_DATA_ARCHIVE" -C "${CI_DERIVED_DATA_PATH}"18 echo "Cache restore tamamlandi"19 fi20else21 echo "Cache miss — fresh build"22fibash
1#!/bin/sh2# ci_scripts/ci_post_build.sh3# Derived data cache save4 5set -e6 7CACHE_DIR="${CI_DERIVED_DATA_PATH}/../../.cache"8DERIVED_DATA_ARCHIVE="${CACHE_DIR}/derived_data.tar.gz"9CACHE_KEY_FILE="${CACHE_DIR}/.cache_key"10 11mkdir -p "$CACHE_DIR"12 13# Cache key kaydet14CURRENT_KEY=$(cat Package.resolved Project.xcodeproj/project.pbxproj | md5)15echo "$CURRENT_KEY" > "$CACHE_KEY_FILE"16 17# Derived data arşivle (Build/ klasörü yeterli, Index kaydet)18tar -czf "$DERIVED_DATA_ARCHIVE" -C "${CI_DERIVED_DATA_PATH}" Build/Products Build/Intermediates.noindex/MyApp.build19 20echo "Cache kaydedildi: $(du -sh $DERIVED_DATA_ARCHIVE | cut -f1)"Gerçekçi Cache Boyutları
Proje Büyüklüğü | Derived Data | SPM Cache | Toplam |
|---|---|---|---|
Küçük (50k LOC) | ~800 MB | ~200 MB | ~1 GB |
Orta (150k LOC) | ~2.5 GB | ~500 MB | ~3 GB |
Büyük (300k+ LOC) | ~6 GB | ~1 GB | ~7 GB |
Büyük projelerde tam derived data arşivlemek yerine sadece Build/Products yeterli — %60 daha küçük, %80 zaman kazanımı.
CocoaPods ve SPM Cache Stratejisi
SPM Package Resolution Cache
bash
1#!/bin/sh2# ci_scripts/ci_post_clone.sh3# SPM package cache4 5set -e6 7# CI_WORKSPACE_PATH: checkout root8# SPM cache dizini (Xcode Cloud container'da persist)9SPM_CACHE="${HOME}/Library/Caches/org.swift.swiftpm"10RESOLVED_HASH=$(md5 < Package.resolved)11CACHE_ARCHIVE="${HOME}/.spm_cache_${RESOLVED_HASH}.tar.gz"12 13if [ -f "$CACHE_ARCHIVE" ]; then14 echo "SPM cache hit"15 mkdir -p "$SPM_CACHE"16 tar -xzf "$CACHE_ARCHIVE" -C "$SPM_CACHE"17else18 echo "SPM cache miss — fresh resolve"19 # Çözüm sonrası cache'e al20 xcodebuild -resolvePackageDependencies -workspace MyApp.xcworkspace -scheme MyApp21 if [ -d "$SPM_CACHE" ]; then22 tar -czf "$CACHE_ARCHIVE" -C "$SPM_CACHE" .23 fi24fiCocoaPods Cache (Pod install Hızlandırma)
bash
1#!/bin/sh2# CocoaPods kullanıyorsan3# ci_scripts/ci_post_clone.sh4 5PODS_LOCK_HASH=$(md5 < Podfile.lock)6PODS_CACHE="${HOME}/.pods_cache_${PODS_LOCK_HASH}.tar.gz"7 8if [ -f "$PODS_CACHE" ]; then9 echo "Pods cache hit — extract ediliyor"10 tar -xzf "$PODS_CACHE" -C . Pods11 echo "Pods cache restore tamamlandi"12else13 echo "Pod install basliyor..."14 bundle exec pod install --repo-update15 tar -czf "$PODS_CACHE" Pods16fiSPM vs CocoaPods — Build Time Karşılaştırması
Metrik | SPM | CocoaPods |
|---|---|---|
Resolve süresi (cache yok) | 2-4 dk | 3-7 dk |
Resolve süresi (cache var) | 20-40 sn | 10-30 sn |
Build entegrasyonu | Native (Xcode) | xcconfig inject |
Cache etkinliği | Yüksek | Orta |
Xcode Cloud desteği | Tam | Kısmi |
Paralel Test Destinations
Aynı test suite'ini birden fazla device'da paralel koşturmak toplam test süresini dramatik düşürür.
Paralel Destination Konfigürasyonu
yaml
1# ci_workflows/parallel_tests.yml2name: Parallel Device Tests3 4jobs:5 unit_tests:6 name: Unit Tests — Multi Device7 steps:8 - test:9 platform: iOS Simulator10 destinations:11 - iPhone 16 Pro12 - iPhone 1613 - iPad Pro (M4)14 test_plan: UnitTests15 maximum_test_repetitions: 316 retry_on_failure: true17 collect_result_bundle: true18 19 # Ayrı job olarak macOS Catalyst test20 catalyst_tests:21 name: macOS Catalyst Tests22 steps:23 - test:24 platform: macOS25 destination: My Mac26 scheme: MyAppCatalyst27 test_plan: CatalystTestsTest Plan Yapısı (.xctestplan)
Test plan'ı Xcode'dan export edip source control'e ekle:
json
1{2 "configurations": [3 {4 "id": "unit-config",5 "name": "Unit Tests Configuration",6 "options": {7 "codeCoverageEnabled": true,8 "codeCoverageTargets": ["MyApp"],9 "testTimeoutsEnabled": true,10 "defaultTestExecutionTimeAllowance": 60,11 "maximumTestRepetitions": 3,12 "testRepetitionMode": "retryOnFailure"13 }14 }15 ],16 "testTargets": [17 {18 "target": {19 "containerPath": "container:MyApp.xcodeproj",20 "identifier": "MyAppTests",21 "name": "MyAppTests"22 },23 "enabled": true,24 "selectedTests": []25 }26 ],27 "version": 128}Test Süresi Benchmark
Paralel destination etkisi (50 unit test, 20 UI test):
Konfigürasyon | Süre |
|---|---|
Seri, 1 device | 18 dk |
Paralel, 2 device | 10 dk |
Paralel, 3 device | 7 dk |
Sharded UI tests, 3 device | 4 dk |
UI Test Sharding
UI testleri en yavaş test türü. Sharding ile bölüştür.
Xcode Cloud'da UI Test Sharding
yaml
1# ci_workflows/ui_tests.yml2name: UI Tests — Sharded3 4jobs:5 ui_tests_shard_1:6 name: UI Tests Shard 1/37 environment_variables:8 TEST_SHARD: "1"9 TOTAL_SHARDS: "3"10 steps:11 - test:12 platform: iOS Simulator13 destination: iPhone 16 Pro14 test_plan: UITests15 test_filter:16 only_test_identifiers:17 - "LoginTests"18 - "OnboardingTests"19 - "HomeTests"20 21 ui_tests_shard_2:22 name: UI Tests Shard 2/323 environment_variables:24 TEST_SHARD: "2"25 TOTAL_SHARDS: "3"26 steps:27 - test:28 platform: iOS Simulator29 destination: iPhone 16 Pro30 test_plan: UITests31 test_filter:32 only_test_identifiers:33 - "ProfileTests"34 - "SettingsTests"35 - "PaymentTests"36 37 ui_tests_shard_3:38 name: UI Tests Shard 3/339 steps:40 - test:41 platform: iOS Simulator42 destination: iPhone 1643 test_plan: UITests44 test_filter:45 only_test_identifiers:46 - "SearchTests"47 - "FilterTests"48 - "NotificationTests"Dynamic Sharding Script
swift
1// Tests/UITestShardHelper.swift2// XCTestCase'e extension — otomatik shard atlama3 4import XCTest5 6extension XCTestCase {7 func skipIfNotInShard() {8 guard let shardIndex = ProcessInfo.processInfo.environment["TEST_SHARD"].flatMap(Int.init),9 let totalShards = ProcessInfo.processInfo.environment["TOTAL_SHARDS"].flatMap(Int.init) else {10 return // Shard env yok, hepsini koş11 }12 13 // Test class adından hash al, modulo ile shard belirle14 let className = String(describing: type(of: self))15 let classHash = abs(className.hashValue)16 let assignedShard = (classHash % totalShards) + 117 18 if assignedShard != shardIndex {19 throw XCTSkip("Bu test (assignedShard). shard'a ait, şu an (shardIndex). shard koşuyor")20 }21 }22}23 24// Kullanım25class PaymentTests: XCTestCase {26 override func setUp() {27 super.setUp()28 skipIfNotInShard()29 }30 // ...31}Post-Build Actions: Slack ve TestFlight
Custom Script ile Slack Notification
bash
1#!/bin/sh2# ci_scripts/ci_post_workflow.sh3# Build sonucu Slack'e bildir4 5set -e6 7WORKFLOW_NAME="${CI_WORKFLOW}"8BUILD_NUMBER="${CI_BUILD_NUMBER}"9BRANCH="${CI_BRANCH}"10COMMIT="${CI_COMMIT:0:7}"11 12if [ "$CI_RESULT" = "succeeded" ]; then13 COLOR="good"14 STATUS="Basarili"15 EMOJI=":white_check_mark:"16else17 COLOR="danger"18 STATUS="Basarisiz"19 EMOJI=":x:"20fi21 22PAYLOAD=$(cat <<EOF23{24 "attachments": [25 {26 "color": "${COLOR}",27 "title": "${EMOJI} Xcode Cloud: ${WORKFLOW_NAME} — ${STATUS}",28 "fields": [29 {"title": "Branch", "value": "${BRANCH}", "short": true},30 {"title": "Build", "value": "#${BUILD_NUMBER}", "short": true},31 {"title": "Commit", "value": "${COMMIT}", "short": true}32 ],33 "footer": "Xcode Cloud",34 "ts": $(date +%s)35 }36 ]37}38EOF39)40 41curl -s -X POST -H "Content-Type: application/json" -d "$PAYLOAD" "$SLACK_WEBHOOK_URL"TestFlight External Group Automation
yaml
1# release workflow'unda2jobs:3 release:4 steps:5 - archive:6 platform: iOS7 scheme: MyApp8 configuration: Release9 - testflight:10 groups:11 - "Internal QA" # Hemen dağıt12 - "External Beta" # Apple review sonrası13 submit_for_review: false # Manuel review isteği14 notify_testers: true15 whats_new:16 - locale: "tr"17 text: "Bu sürümde performans iyileştirmeleri ve hata düzeltmeleri yapıldı."18 - locale: "en-US"19 text: "Performance improvements and bug fixes."Environment Secrets Yönetimi
Xcode Cloud'da Secret Tanımlama
App Store Connect → Xcode Cloud → Environment Variables.
Secret Türü | Nasıl Sakla | Erişim |
|---|---|---|
API Keys (.p8) | File secret | CI_ASC_KEY_PATH env var |
Webhook URL'leri | Text secret | Env var olarak |
Crash reporting token | Text secret | Build settings inject |
Signing certificates | Xcode Cloud otomatik | Otomatik inject |
Custom entitlements | Provisioning profile | Xcode Cloud otomatik |
Script'te Secret Kullanımı
bash
1#!/bin/sh2# Xcode Cloud environment variables otomatik inject ediyor3# Custom secret'lar da aynı şekilde geliyor4 5echo "Crash reporting setup..."6# Örn. Firebase Crashlytics'e build number inject7/usr/libexec/PlistBuddy -c "Add :FirebaseCrashlyticsConfig dict" "${CI_APP_SOURCE_ROOT}/MyApp/Info.plist" 2>/dev/null || true8/usr/libexec/PlistBuddy -c "Set :FirebaseCrashlyticsConfig:BUILD_NUMBER ${CI_BUILD_NUMBER}" "${CI_APP_SOURCE_ROOT}/MyApp/Info.plist"9 10# Slack webhook (secret olarak tanımlı)11echo "SLACK_WEBHOOK_URL mevcut: $([ -n "$SLACK_WEBHOOK_URL" ] && echo 'Evet' || echo 'Hayir')"Sensitive File Secret (örn. GoogleService-Info.plist)
bash
1#!/bin/sh2# ci_scripts/ci_post_clone.sh3# File secret inject4 5# Xcode Cloud file secret olarak yüklenen dosya6# CI_DERIVED_DATA_PATH, CI_WORKSPACE_PATH gibi path'ler mevcut7if [ -n "$GOOGLE_SERVICE_INFO_PLIST" ]; then8 echo "$GOOGLE_SERVICE_INFO_PLIST" > "${CI_APP_SOURCE_ROOT}/MyApp/GoogleService-Info.plist"9 echo "GoogleService-Info.plist inject edildi"10fiFastlane Lane Migration
Mevcut Fastlane lane'lerini Xcode Cloud'a migrate ederken dikkat edilmesi gerekenler.
Yaygın Fastlane Action'ları ve Xcode Cloud Karşılığı
Fastlane Action | Xcode Cloud Karşılığı |
|---|---|
build_app | build: adım |
run_tests | test: adım |
gym | archive: adım |
pilot (TestFlight) | testflight: adım |
match (signing) | Xcode Cloud otomatik signing |
deliver | App Store Connect entegrasyon |
increment_build_number | CI_BUILD_NUMBER env var |
slack | custom ci_post_workflow.sh |
sonar | ci_post_build.sh custom |
Karma Yaklaşım (Fastlane + Xcode Cloud)
Xcode Cloud'un build/test/deploy adımlarını kullan, Fastlane'i sadece özel logic için çağır:
ruby
1# Fastfile — sadece Xcode Cloud'un yapamadığı şeyler2lane :post_build_actions do3 # Screenshot generation (Xcode Cloud built-in değil)4 snapshot(5 devices: ["iPhone 16 Pro", "iPad Pro (M4)"],6 languages: ["tr", "en-US"],7 output_directory: "./fastlane/screenshots"8 )9 10 # App Store metadata güncellemesi (localized)11 deliver(12 skip_binary_upload: true,13 skip_screenshots: false,14 metadata_path: "./fastlane/metadata"15 )16endbash
1#!/bin/sh2# ci_scripts/ci_post_build.sh3# Fastlane lane çağır4bundle exec fastlane post_build_actionsCost Optimization Stratejileri
Xcode Cloud pricing 2026:
Plan | Compute Hours/ay | Fiyat |
|---|---|---|
Apple Developer Program dahil | 25 saat | $0 |
Tier 1 | 100 saat | $14.99/ay |
Tier 2 | 250 saat | $24.99/ay |
Tier 3 | 1000 saat | $99.99/ay |
Compute Hour Tüketimi Azaltma
yaml
1# Cost optimization: PR workflow — sadece unit tests2# ci_workflows/ci_pr.yml3name: PR Check (Minimal)4 5jobs:6 quick_check:7 name: Quick Build + Unit Test8 steps:9 - build:10 platform: iOS Simulator11 destination: iPhone 16 Pro12 configuration: Debug13 # Kısmi build: sadece main target14 scheme: MyApp15 - test:16 platform: iOS Simulator17 destination: iPhone 16 Pro18 # Sadece unit tests — UI test yok19 test_plan: UnitTestsOnly20 # Maximum 2 tekrar (flaky test döngüsü önle)21 maximum_test_repetitions: 2Compute Hour Monitoring Script
swift
1// ASC API ile compute hour kullanımını izle2extension AscClient {3 func fetchXcodeCloudUsage() async throws -> ComputeUsage {4 let token = try await tokenCache.validToken(generator: generator)5 6 var components = URLComponents(7 url: URL(string: "https://api.appstoreconnect.apple.com/v1/ciProducts")!,8 resolvingAgainstBaseURL: false9 )!10 components.queryItems = [11 URLQueryItem(name: "include", value: "buildRuns"),12 URLQueryItem(name: "limit[buildRuns]", value: "100")13 ]14 15 var request = URLRequest(url: components.url!)16 request.setValue("Bearer (token)", forHTTPHeaderField: "Authorization")17 18 let (data, _) = try await URLSession.shared.data(for: request)19 let products = try JSONDecoder().decode(CiProductsResponse.self, from: data)20 21 let totalMinutes = products.data.flatMap { product in22 product.relationships?.buildRuns?.data ?? []23 }.count * 8 // Ortalama 8 dk per build varsayımı24 25 return ComputeUsage(26 estimatedMinutes: totalMinutes,27 estimatedHours: Double(totalMinutes) / 60.028 )29 }30}31 32struct ComputeUsage {33 let estimatedMinutes: Int34 let estimatedHours: Double35}Maliyet Düşürme Kontrol Listesi
Trigger optimizasyonu:
- PR branch'lerde UI test koşturma — sadece unit test
docs/**,*.mddeğişikliklerinde build tetikleme- Feature branch'lerde nightly build yok — sadece main
Build optimizasyonu:
- Debug configuration kullan (Release build daha uzun sürer)
- Sadece değişen module'ları build et (modular architecture ile)
- SPM ve derived data cache aktif
Test optimizasyonu:
- Flaky test'leri belirle ve düzelt (maximum_test_repetitions'ı düşür)
- UI test'leri sharding ile paralel koştur
- Smoke test plan oluştur — PR için full suite değil
Apple Developer Account ve Concurrent Build Limits
Concurrent Build Limitleri
Plan | Max Concurrent Build |
|---|---|
Free (25h) | 1 |
Tier 1 | 2 |
Tier 2 | 5 |
Tier 3 | 10 |
Multi-Account Organization Yönetimi
Birden fazla app aynı org'da olduğunda:
yaml
1# ci_workflows/app_a.yml — priority: high2# ci_workflows/app_b.yml — daha az kritik3 4# Aynı anda 2 concurrent limit varsa:5# app_a PR workflow + app_b release workflow = slot doldu6# Çözüm: app_b'nin PR workflow'u sadece main'de tetiklensin7start_condition:8 source_branch:9 any_of:10 - main # Sadece main'de, feature branch'lerde değilALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Xcode Cloud workflow'larını deploy etmeden önce local'de test etmek zor — ama ci_scripts/ hook script'lerini local olarak koşturabilirsin:
bash
#!/bin/bash# local_ci_test.sh — Xcode Cloud environment'ını taklit et export CI_BUILD_NUMBER="999"export CI_BRANCH=$(git rev-parse --abbrev-ref HEAD)export CI_COMMIT=$(git rev-parse HEAD)export CI_WORKSPACE_PATH=$(pwd)export CI_APP_SOURCE_ROOT=$(pwd)export CI_DERIVED_DATA_PATH="/tmp/local_derived_data"export CI_RESULT="succeeded"export CI_WORKFLOW="local-test" mkdir -p "$CI_DERIVED_DATA_PATH" echo "=== ci_post_clone.sh ==="sh ./ci_scripts/ci_post_clone.sh echo "=== Build (dry-run) ==="xcodebuild build -workspace MyApp.xcworkspace -scheme MyApp -destination "platform=iOS Simulator,name=iPhone 16 Pro" -configuration Debug DERIVED_DATA_PATH="$CI_DERIVED_DATA_PATH" | xcpretty echo "=== ci_post_build.sh ==="sh ./ci_scripts/ci_post_build.sh echo "=== ci_post_workflow.sh ==="sh ./ci_scripts/ci_post_workflow.sh echo "Local CI test tamamlandi"Sonuç
Xcode Cloud, Apple ekosisteminde CI/CD için en düşük friction seçenek — signing otomasyonu, TestFlight entegrasyonu ve App Store Connect bütünleşmesi rakipsiz. Ama compute hour sınırları ve özelleştirme eksikliği için akıllı strateji şart: branch trigger'ları optimize et, cache'i kur, test'leri paralelize et ve post-build script'lerle eksikleri kapat. Doğru kurulumla 25 saatlik free tier çoğu indie proje için yeterli, büyük ekiplerde de Tier 1-2 makul maliyet.
Daha fazla okuma için: iOS CI/CD Pipeline Rehberi, App Store Connect API Otomasyonu, iOS 19 Beta Yenilikler, Senior iOS Developer 2026.
Kaynaklar:
- Xcode Cloud Documentation — Apple resmi dokümantasyon
- Configuring Your First Xcode Cloud Workflow — Getting started
- ci_scripts Reference — Custom script hook'ları
- App Store Connect API — Xcode Cloud — Programatik yönetim
- WWDC 2023: Simplify distribution in Xcode and Xcode Cloud — Apple session

