# Xcode Cloud Pipeline Optimization: Build Times, Caching ve Cost 2026
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
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 < 23{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.
swift
1extension AscClient {2 // Build artifact'larını sil (storage tasarrufu)3 func deleteBuildArtifacts(buildRunId: String) async throws {4 let token = try await tokenCache.validToken(generator: generator)5 let url = URL(string: "https://api.appstoreconnect.apple.com/v1/ciBuildRuns/(buildRunId)/artifacts")!6 var request = URLRequest(url: url)7 request.httpMethod = "DELETE"8 request.setValue("Bearer (token)", forHTTPHeaderField: "Authorization")9 10 let (_, response) = try await URLSession.shared.data(for: request)11 guard let http = response as? HTTPURLResponse, http.statusCode == 204 else {12 throw AscError.httpError((response as? HTTPURLResponse)?.statusCode ?? 0)13 }14 }15 16 // Eski build'leri temizle (30 günden eski)17 func cleanOldBuildArtifacts(productId: String, daysOld: Int = 30) async throws {18 let token = try await tokenCache.validToken(generator: generator)19 let cutoffDate = Calendar.current.date(byAdding: .day, value: -daysOld, to: Date())!20 21 var components = URLComponents(22 url: URL(string: "https://api.appstoreconnect.apple.com/v1/ciBuildRuns")!,23 resolvingAgainstBaseURL: false24 )!25 components.queryItems = [26 URLQueryItem(name: "filter[product]", value: productId),27 URLQueryItem(name: "limit", value: "200"),28 URLQueryItem(name: "sort", value: "-startedDate")29 ]30 31 var request = URLRequest(url: components.url!)32 request.setValue("Bearer (token)", forHTTPHeaderField: "Authorization")33 34 let (data, _) = try await URLSession.shared.data(for: request)35 let runs = try JSONDecoder().decode(CiBuildRunsResponse.self, from: data)36 37 let oldRuns = runs.data.filter { run in38 guard let startDate = ISO8601DateFormatter().date(from: run.attributes.startedDate ?? "") else { return false }39 return startDate < cutoffDate40 }41 42 for run in oldRuns {43 try? await deleteBuildArtifacts(buildRunId: run.id)44 }45 print("(oldRuns.count) eski build artifact temizlendi")46 }47}Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
bash
1#!/bin/bash2# local_ci_test.sh — Xcode Cloud environment'ını taklit et3 4export CI_BUILD_NUMBER="999"5export CI_BRANCH=$(git rev-parse --abbrev-ref HEAD)6export CI_COMMIT=$(git rev-parse HEAD)7export CI_WORKSPACE_PATH=$(pwd)8export CI_APP_SOURCE_ROOT=$(pwd)9export CI_DERIVED_DATA_PATH="/tmp/local_derived_data"10export CI_RESULT="succeeded"11export CI_WORKFLOW="local-test"12 13mkdir -p "$CI_DERIVED_DATA_PATH"14 15echo "=== ci_post_clone.sh ==="16sh ./ci_scripts/ci_post_clone.sh17 18echo "=== Build (dry-run) ==="19xcodebuild build -workspace MyApp.xcworkspace -scheme MyApp -destination "platform=iOS Simulator,name=iPhone 16 Pro" -configuration Debug DERIVED_DATA_PATH="$CI_DERIVED_DATA_PATH" | xcpretty20 21echo "=== ci_post_build.sh ==="22sh ./ci_scripts/ci_post_build.sh23 24echo "=== ci_post_workflow.sh ==="25sh ./ci_scripts/ci_post_workflow.sh26 27echo "Local CI test tamamlandi"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:
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

