Tüm Yazılar
KategoriDevOps
Okuma Süresi
15 dk okuma
Yayın Tarihi
...
Kelime Sayısı
2.996kelime

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

Xcode Cloud workflow tasarımı, build time reduction, caching strategies, test parallelization, TestFlight automation ve cost optimization — production pipeline rehberi.

Xcode Cloud Pipeline Optimization: Build Times, Caching ve Cost 2026

# 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ı

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ı, minimal
4 nightly.yml # Nightly — tam test + analiz
5 release.yml # TestFlight deploy
6 app-store.yml # App Store submission

Minimal CI Workflow

yaml
1# ci_workflows/ci.yml
2name: CI — Pull Request Check
3 
4on:
5 - pullRequestOpen
6 - pullRequestReopened
7 
8environment:
9 xcode: latest-release
10 macOS: latest-release
11 
12start_condition:
13 source_branch:
14 exclude:
15 - main
16 - release/**
17 
18jobs:
19 build:
20 name: Build & Unit Tests
21 steps:
22 - build:
23 platform: iOS Simulator
24 destination: iPhone 16 Pro
25 configuration: Debug
26 scheme: MyApp
27 - test:
28 platform: iOS Simulator
29 destination: iPhone 16 Pro
30 test_plan: UnitTests
31 maximum_test_repetitions: 2
32 retry_on_failure: true

Release Workflow

yaml
1# ci_workflows/release.yml
2name: TestFlight Release
3 
4on:
5 - push
6 
7environment:
8 xcode: 16.3
9 macOS: 15.4
10 
11start_condition:
12 source_branch:
13 any_of:
14 - main
15 - release/**
16 
17jobs:
18 archive_and_deploy:
19 name: Archive & TestFlight
20 steps:
21 - build:
22 platform: iOS
23 destination: Any iOS Device
24 configuration: Release
25 scheme: MyApp
26 - test:
27 platform: iOS Simulator
28 destination: iPhone 16 Pro
29 test_plan: SmokeTests
30 - archive:
31 platform: iOS
32 configuration: Release
33 scheme: MyApp
34 export_options:
35 method: app-store
36 upload_symbols: true
37 compile_bitcode: false
38 - testflight:
39 groups:
40 - Internal Testers
41 - Beta Testers
42 notify_testers: true

Branch 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 tetikle
2start_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 TETIKLEME
14 none_of:
15 - "**/*.md"
16 - "**/*.txt"
17 - ".gitignore"
18 - "docs/**"

Scheduled Trigger (Nightly Build)

yaml
1# ci_workflows/nightly.yml
2name: Nightly Full Test Suite
3 
4on:
5 - schedule
6 
7schedule:
8 cron: "0 22 * * 1-5" # UTC 22:00, pazartesi-cuma
9 branch: main
10 
11jobs:
12 full_test_suite:
13 name: Full Test + Static Analysis
14 steps:
15 - analyze:
16 platform: iOS Simulator
17 destination: iPhone 16 Pro
18 scheme: MyApp
19 - test:
20 platform: iOS Simulator
21 destination: iPhone 16 Pro
22 test_plan: AllTests
23 collect_result_bundle: true

Tag-Based Release Trigger

yaml
1start_condition:
2 tag:
3 any_of:
4 - "v*.*.*" # v1.2.3
5 - "rc-*" # rc-1
6 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/sh
2# ci_scripts/ci_pre_build.sh
3# Derived data cache restore
4 
5set -e
6 
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 hash
12CURRENT_KEY=$(cat Package.resolved Project.xcodeproj/project.pbxproj | md5)
13 
14if [ -f "$CACHE_KEY_FILE" ] && [ "$(cat $CACHE_KEY_FILE)" = "$CURRENT_KEY" ]; then
15 if [ -f "$DERIVED_DATA_ARCHIVE" ]; then
16 echo "Cache hit — derived data restore ediliyor..."
17 tar -xzf "$DERIVED_DATA_ARCHIVE" -C "${CI_DERIVED_DATA_PATH}"
18 echo "Cache restore tamamlandi"
19 fi
20else
21 echo "Cache miss — fresh build"
22fi
bash
1#!/bin/sh
2# ci_scripts/ci_post_build.sh
3# Derived data cache save
4 
5set -e
6 
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 kaydet
14CURRENT_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.build
19 
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/sh
2# ci_scripts/ci_post_clone.sh
3# SPM package cache
4 
5set -e
6 
7# CI_WORKSPACE_PATH: checkout root
8# 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" ]; then
14 echo "SPM cache hit"
15 mkdir -p "$SPM_CACHE"
16 tar -xzf "$CACHE_ARCHIVE" -C "$SPM_CACHE"
17else
18 echo "SPM cache miss — fresh resolve"
19 # Çözüm sonrası cache'e al
20 xcodebuild -resolvePackageDependencies -workspace MyApp.xcworkspace -scheme MyApp
21 if [ -d "$SPM_CACHE" ]; then
22 tar -czf "$CACHE_ARCHIVE" -C "$SPM_CACHE" .
23 fi
24fi

CocoaPods Cache (Pod install Hızlandırma)

bash
1#!/bin/sh
2# CocoaPods kullanıyorsan
3# ci_scripts/ci_post_clone.sh
4 
5PODS_LOCK_HASH=$(md5 < Podfile.lock)
6PODS_CACHE="${HOME}/.pods_cache_${PODS_LOCK_HASH}.tar.gz"
7 
8if [ -f "$PODS_CACHE" ]; then
9 echo "Pods cache hit — extract ediliyor"
10 tar -xzf "$PODS_CACHE" -C . Pods
11 echo "Pods cache restore tamamlandi"
12else
13 echo "Pod install basliyor..."
14 bundle exec pod install --repo-update
15 tar -czf "$PODS_CACHE" Pods
16fi

SPM 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.yml
2name: Parallel Device Tests
3 
4jobs:
5 unit_tests:
6 name: Unit Tests — Multi Device
7 steps:
8 - test:
9 platform: iOS Simulator
10 destinations:
11 - iPhone 16 Pro
12 - iPhone 16
13 - iPad Pro (M4)
14 test_plan: UnitTests
15 maximum_test_repetitions: 3
16 retry_on_failure: true
17 collect_result_bundle: true
18 
19 # Ayrı job olarak macOS Catalyst test
20 catalyst_tests:
21 name: macOS Catalyst Tests
22 steps:
23 - test:
24 platform: macOS
25 destination: My Mac
26 scheme: MyAppCatalyst
27 test_plan: CatalystTests

Test 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": 1
28}

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.yml
2name: UI Tests — Sharded
3 
4jobs:
5 ui_tests_shard_1:
6 name: UI Tests Shard 1/3
7 environment_variables:
8 TEST_SHARD: "1"
9 TOTAL_SHARDS: "3"
10 steps:
11 - test:
12 platform: iOS Simulator
13 destination: iPhone 16 Pro
14 test_plan: UITests
15 test_filter:
16 only_test_identifiers:
17 - "LoginTests"
18 - "OnboardingTests"
19 - "HomeTests"
20 
21 ui_tests_shard_2:
22 name: UI Tests Shard 2/3
23 environment_variables:
24 TEST_SHARD: "2"
25 TOTAL_SHARDS: "3"
26 steps:
27 - test:
28 platform: iOS Simulator
29 destination: iPhone 16 Pro
30 test_plan: UITests
31 test_filter:
32 only_test_identifiers:
33 - "ProfileTests"
34 - "SettingsTests"
35 - "PaymentTests"
36 
37 ui_tests_shard_3:
38 name: UI Tests Shard 3/3
39 steps:
40 - test:
41 platform: iOS Simulator
42 destination: iPhone 16
43 test_plan: UITests
44 test_filter:
45 only_test_identifiers:
46 - "SearchTests"
47 - "FilterTests"
48 - "NotificationTests"

Dynamic Sharding Script

swift
1// Tests/UITestShardHelper.swift
2// XCTestCase'e extension — otomatik shard atlama
3 
4import XCTest
5 
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 belirle
14 let className = String(describing: type(of: self))
15 let classHash = abs(className.hashValue)
16 let assignedShard = (classHash % totalShards) + 1
17 
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ım
25class 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/sh
2# ci_scripts/ci_post_workflow.sh
3# Build sonucu Slack'e bildir
4 
5set -e
6 
7WORKFLOW_NAME="${CI_WORKFLOW}"
8BUILD_NUMBER="${CI_BUILD_NUMBER}"
9BRANCH="${CI_BRANCH}"
10COMMIT="${CI_COMMIT:0:7}"
11 
12if [ "$CI_RESULT" = "succeeded" ]; then
13 COLOR="good"
14 STATUS="Basarili"
15 EMOJI=":white_check_mark:"
16else
17 COLOR="danger"
18 STATUS="Basarisiz"
19 EMOJI=":x:"
20fi
21 
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}
38EOF
39)
40 
41curl -s -X POST -H "Content-Type: application/json" -d "$PAYLOAD" "$SLACK_WEBHOOK_URL"

TestFlight External Group Automation

yaml
1# release workflow'unda
2jobs:
3 release:
4 steps:
5 - archive:
6 platform: iOS
7 scheme: MyApp
8 configuration: Release
9 - testflight:
10 groups:
11 - "Internal QA" # Hemen dağıt
12 - "External Beta" # Apple review sonrası
13 submit_for_review: false # Manuel review isteği
14 notify_testers: true
15 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/sh
2# Xcode Cloud environment variables otomatik inject ediyor
3# Custom secret'lar da aynı şekilde geliyor
4 
5echo "Crash reporting setup..."
6# Örn. Firebase Crashlytics'e build number inject
7/usr/libexec/PlistBuddy -c "Add :FirebaseCrashlyticsConfig dict" "${CI_APP_SOURCE_ROOT}/MyApp/Info.plist" 2>/dev/null || true
8/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/sh
2# ci_scripts/ci_post_clone.sh
3# File secret inject
4 
5# Xcode Cloud file secret olarak yüklenen dosya
6# CI_DERIVED_DATA_PATH, CI_WORKSPACE_PATH gibi path'ler mevcut
7if [ -n "$GOOGLE_SERVICE_INFO_PLIST" ]; then
8 echo "$GOOGLE_SERVICE_INFO_PLIST" > "${CI_APP_SOURCE_ROOT}/MyApp/GoogleService-Info.plist"
9 echo "GoogleService-Info.plist inject edildi"
10fi

Fastlane 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ığı şeyler
2lane :post_build_actions do
3 # 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 )
16end
bash
1#!/bin/sh
2# ci_scripts/ci_post_build.sh
3# Fastlane lane çağır
4bundle exec fastlane post_build_actions

Cost 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 tests
2# ci_workflows/ci_pr.yml
3name: PR Check (Minimal)
4 
5jobs:
6 quick_check:
7 name: Quick Build + Unit Test
8 steps:
9 - build:
10 platform: iOS Simulator
11 destination: iPhone 16 Pro
12 configuration: Debug
13 # Kısmi build: sadece main target
14 scheme: MyApp
15 - test:
16 platform: iOS Simulator
17 destination: iPhone 16 Pro
18 # Sadece unit tests — UI test yok
19 test_plan: UnitTestsOnly
20 # Maximum 2 tekrar (flaky test döngüsü önle)
21 maximum_test_repetitions: 2

Compute Hour Monitoring Script

swift
1// ASC API ile compute hour kullanımını izle
2extension 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: false
9 )!
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 in
22 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.0
28 )
29 }
30}
31 
32struct ComputeUsage {
33 let estimatedMinutes: Int
34 let estimatedHours: Double
35}

Maliyet Düşürme Kontrol Listesi

Trigger optimizasyonu:

  • PR branch'lerde UI test koşturma — sadece unit test
  • docs/**, *.md değ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: high
2# ci_workflows/app_b.yml — daha az kritik
3 
4# Aynı anda 2 concurrent limit varsa:
5# app_a PR workflow + app_b release workflow = slot doldu
6# Çözüm: app_b'nin PR workflow'u sadece main'de tetiklensin
7start_condition:
8 source_branch:
9 any_of:
10 - main # Sadece main'de, feature branch'lerde değil

ALTIN İ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: false
24 )!
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 in
38 guard let startDate = ISO8601DateFormatter().date(from: run.attributes.startedDate ?? "") else { return false }
39 return startDate < cutoffDate
40 }
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/bash
2# local_ci_test.sh — Xcode Cloud environment'ını taklit et
3 
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.sh
17 
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" | xcpretty
20 
21echo "=== ci_post_build.sh ==="
22sh ./ci_scripts/ci_post_build.sh
23 
24echo "=== ci_post_workflow.sh ==="
25sh ./ci_scripts/ci_post_workflow.sh
26 
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:

Etiketler

#Xcode Cloud#CI/CD#Pipeline#Build Optimization#TestFlight#Caching#2026
Muhittin Çamdalı

Muhittin Çamdalı

Senior iOS Developer

12+ yıllık deneyime sahip iOS Developer. Swift, SwiftUI ve modern iOS mimarileri konusunda uzman. Apple platformlarında performanslı ve kullanıcı dostu uygulamalar geliştiriyorum.

iOS Geliştirme Haberleri

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

Gizliliğinize saygı duyuyoruz. İstediğiniz zaman abonelikten çıkabilirsiniz.

Paylaş

Bunu da begenebilirsiniz