• 27.08.2026 21:10:10
  • Admin Admin

Bu swiftui eğitimi, büyük dosyaları uygulama arka plandayken yüklemek için URLSession background transfer, kalıcı iş kaydı, BGProcessingTask ve gözlemlenebilir hata yönetimini uçtan uca ele alır.

SwiftUI Eğitiminde Arka Plan Dosya Yükleme ve BGTask Tasarımı

SwiftUI eğitiminde URLSession background transfer yaşam döngüsü

Bir ios eğitimi veya ios kursu içeriğinde arka plan yüklemeyi yalnızca `Task {}` ile başlatmak yanıltıcıdır: uygulama askıya alındığında bu task çalıştırma zamanı kaybeder. Sistem tarafından ayrı bir süreçte yönetilen `URLSessionConfiguration.background` ise yükleme soketini uygulama belleğinden bağımsız sürdürür. Arka plan oturumunun tanımlayıcısı sabit ve bundle kimliğiyle isim alanına ayrılmış olmalıdır; her açılışta rastgele UUID üretmek, sistemin eski oturumu yeni delegate'e bağlamasını engeller.

import Foundation

final class UploadSessionDelegate: NSObject, URLSessionDelegate {
    var finishEvents: (() -> Void)?

    func urlSessionDidFinishEvents(
        forBackgroundURLSession session: URLSession
    ) {
        DispatchQueue.main.async { [weak self] in
            self?.finishEvents?()
            self?.finishEvents = nil
        }
    }
}

final class UploadService {
    static let shared = UploadService()

    let delegate = UploadSessionDelegate()
    lazy var session: URLSession = {
        let config = URLSessionConfiguration.background(
            withIdentifier: "com.acme.fieldapp.uploads"
        )
        config.waitsForConnectivity = true
        config.sessionSendsLaunchEvents = true
        return URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
    }()

    func enqueue(fileURL: URL, uploadID: UUID) {
        var request = URLRequest(
            url: URL(string: "https://api.acme.example/uploads/\(uploadID.uuidString)")!
        )
        request.httpMethod = "PUT"
        request.setValue(uploadID.uuidString, forHTTPHeaderField: "Idempotency-Key")

        let task = session.uploadTask(with: request, fromFile: fileURL)
        task.taskDescription = uploadID.uuidString
        task.resume()
    }
}

Buradaki kritik ayrıntı `uploadTask(with:fromFile:)` seçimidir. Background session, gövdeleri yeniden başlatma sonrası okuyabilmek için dosya URL'si ister; `from: Data` ile bellekte tuttuğunuz 800 MB video, uygulama sonlandırıldığında yeniden üretilemez. Dosyayı `Application Support/UploadStaging` altına atomik olarak taşıyın ve transfer tamamlanmadan silmeyin. SwiftUI giriş noktasında `handleEventsForBackgroundURLSession` completion handler'ını çağırmamak da yaygın bir hatadır: sistem bu completion çağrısını bekler, erken çağırmak ise delegate olayları bitmeden uygulamanın tekrar askıya alınmasına yol açabilir.

import SwiftUI

final class AppDelegate: NSObject, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        handleEventsForBackgroundURLSession identifier: String,
        completionHandler: @escaping () -> Void
    ) {
        guard identifier == "com.acme.fieldapp.uploads" else {
            completionHandler()
            return
        }
        UploadService.shared.delegate.finishEvents = completionHandler
    }
}

@main
struct FieldApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup { UploadListView() }
    }
}

iOS MVVM mimarisi içinde kalıcı yükleme kuyruğu ve idempotency

ios mvvm mimarisi içinde ViewModel'i transferin doğruluk kaynağı yapmayın. `@Observable` veya `@Published` durumları yeniden başlatmada kaybolur; ViewModel yalnızca kalıcı kuyruktaki işi ekrana projekte etmelidir. Üretimde GRDB ile `upload_jobs` tablosunda istemci üretimli UUID, dosya yolu, sunucu durumu, deneme sayısı ve son hata kodunu saklamak; uygulama yeniden açıldığında `URLSession.getAllTasks` sonucu ile bu kaydı uzlaştırmak için yeterli bir taban sağlar.

import GRDB

struct UploadJob: FetchableRecord, PersistableRecord {
    var id: String
    var filePath: String
    var state: String
    var attemptCount: Int
    var lastHTTPStatus: Int?
}

let migrator = DatabaseMigrator()
migrator.registerMigration("createUploadJobs") { db in
    try db.create(table: "upload_jobs") { table in
        table.column("id", .text).primaryKey()
        table.column("filePath", .text).notNull()
        table.column("state", .text).notNull().defaults(to: "queued")
        table.column("attemptCount", .integer).notNull().defaults(to: 0)
        table.column("lastHTTPStatus", .integer)
    }
}

func reconcile(session: URLSession, dbQueue: DatabaseQueue) async throws {
    let tasks = await session.allTasks
    let activeIDs = Set(tasks.compactMap { $0.taskDescription })

    try dbQueue.write { db in
        try db.execute(
            sql: "UPDATE upload_jobs SET state = 'queued' WHERE state = 'uploading' AND id NOT IN (\(activeIDs.map { _ in "?" }.joined(separator: ",")))",
            arguments: StatementArguments(activeIDs)
        )
    }
}

Örnekteki SQL, `activeIDs` boşken `NOT IN ()` üretir ve SQLite sözdizimi hatası verir. Bu edge case'i ayrı ele alın: aktif task yoksa doğrudan `UPDATE upload_jobs SET state = 'queued' WHERE state = 'uploading'` çalıştırın. Daha önemlisi, `taskDescription` tek başına sunucu tarafı doğruluk garantisi değildir. İstemcinin zaman aşımı yaşadığı ama sunucunun nesneyi kaydettiği durumda aynı dosya ikinci kez gidebilir. Bu nedenle `Idempotency-Key` değerini hem kalıcı tabloda hem HTTP başlığında saklayın; sunucu bu anahtarla ilk başarılı yanıtı tekrar döndürmeli veya aynı kaynak URI'sine `PUT` işlemini idempotent yapmalıdır.

Dosyanın staging alanından silinme koşulunu HTTP başarı koduna bağlayın. `didCompleteWithError` içinde `error == nil` olması tek başına yeterli değildir; örneğin sunucu 413 döndürdüğünde taşıma katmanı hatasız tamamlanır ama iş kalıcı olarak reddedilmiştir. Delegate tarafında `URLSessionTaskDelegate.urlSession(_:task:didCompleteWithError:)` ile hata nesnesini, `URLSessionDataDelegate.urlSession(_:dataTask:didReceive:)` ile HTTP durumunu job kaydına yazın; yalnızca API sözleşmenizde başarılı kabul edilen 200, 201 veya 204 sonrasında staging dosyasını silin.

Swift kursu için BGProcessingTask ile uzlaştırma tasarımı

BGProcessingTask, dosya baytlarını taşıyan mekanizma değildir; background URLSession bu işi zaten sistem daemon'u üzerinden yürütür. BGProcessingTask'i yarım kalmış işleri uzlaştırmak, süresi dolmuş imzalı URL'leri yenilemek ve bozulmuş staging kayıtlarını temizlemek için kullanın. Sistem bu görevi `earliestBeginDate` sonrasında uygun gördüğü bir anda çalıştırır, tam zamanlı cron gibi davranmaz. Bu nedenle kullanıcı 'Yükle' düğmesine basınca URLSession task'ını hemen oluşturun, BGProcessingTask'i yalnızca toparlama işi için planlayın.

import BackgroundTasks

final class BackgroundCoordinator {
    static let taskID = "com.acme.fieldapp.upload-reconcile"

    func register() {
        BGTaskScheduler.shared.register(
            forTaskWithIdentifier: Self.taskID,
            using: nil
        ) { task in
            self.handle(task: task as! BGProcessingTask)
        }
    }

    func scheduleReconcile() {
        BGTaskScheduler.shared.getPendingTaskRequests { requests in
            guard !requests.contains(where: { $0.identifier == Self.taskID }) else { return }

            let request = BGProcessingTaskRequest(identifier: Self.taskID)
            request.requiresNetworkConnectivity = true
            request.requiresExternalPower = false
            request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
            try? BGTaskScheduler.shared.submit(request)
        }
    }

    private func handle(task: BGProcessingTask) {
        let work = Task {
            let succeeded = await UploadReconciler.shared.run()
            task.setTaskCompleted(success: succeeded)
        }
        task.expirationHandler = {
            work.cancel()
        }
    }
}

Kaydı uygulama başlangıcında, özellikle `application(_:didFinishLaunchingWithOptions:)` içinde yapın; ekran açıldıktan sonra register etmek bazı sistem başlatmalarında geç kalabilir. `Info.plist` içine izinli kimliği eklemeden `submit` çağrısı hata üretir. Xcode eğitimi sırasında bu ayarı yalnızca Signing and Capabilities ekranında aramak yerine plist çıktısını doğrulamak daha güvenlidir.

<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
    <string>com.acme.fieldapp.upload-reconcile</string>
</array>
<key>UIBackgroundModes</key>
<array>
    <string>processing</string>
</array>

Expiration handler içinde sadece `Task.cancel()` çağırmak yeterli sayılmaz: `UploadReconciler.run()` döngüsü her veritabanı sayfası ve her token yenileme isteğinden önce `try Task.checkCancellation()` kontrolü yapmalıdır. Buna karşılık sistemin yürüttüğü background URLSession upload task'larını expiration anında körlemesine iptal etmeyin; uzlaştırma görevinin süresinin dolması, transferin geçersiz olduğu anlamına gelmez.

iPhone uygulama geliştirme, loglama ve app store yayınlama kontrolü

iphone uygulama geliştirme sürecinde bu akışı yalnızca Simulator'da doğrulamak yetersizdir. Fiziksel cihazda Network Link Conditioner ile `%100 Loss`, yüksek gecikme ve değişken bant genişliği profilleri uygulayın; ardından uygulamayı arka plana alın, cihazı kilitleyin ve Xcode'dan Debug Memory Graph yerine cihaz loglarını inceleyin. Kullanıcının uygulamayı uygulama değiştiriciden zorla kapatması ayrı bir senaryodur: sistemin arka plan başlatmasına güvenmeyin, sonraki foreground açılışında `getAllTasks` ve veritabanı uzlaştırmasını mutlaka çalıştırın.

Transfer tanısını `os.Logger` ve `URLSessionTaskMetrics` ile iş kimliğine bağlayın. `URLSessionTaskMetrics` içindeki `countOfRequestBodyBytesSent`, TLS el sıkışması ve yeniden yönlendirme sayısı, istemcinin gerçekten kaç bayt gönderdiğini gösterir; yalnızca UI ilerleme yüzdesi bu bilgiyi vermez. Sunucudaki `Idempotency-Key` logu ile aynı UUID'yi eşleştirerek 401 token yenileme döngüsünü, 413 kalıcı reddini ve bağlantı kopmasını birbirinden ayırabilirsiniz.

import OSLog

final class MetricsDelegate: NSObject, URLSessionTaskDelegate {
    private let logger = Logger(subsystem: "com.acme.fieldapp", category: "upload")

    func urlSession(
        _ session: URLSession,
        task: URLSessionTask,
        didFinishCollecting metrics: URLSessionTaskMetrics
    ) {
        let protocolName = metrics.transactionMetrics.last?.networkProtocolName ?? "unknown"
        logger.info(
            "upload=\(task.taskDescription ?? "missing", privacy: .public) bytes=\(metrics.countOfRequestBodyBytesSent) redirects=\(metrics.redirectCount) protocol=\(protocolName, privacy: .public)"
        )
    }
}

app store yayınlama öncesinde Background Modes beyanını gerçek kullanımınızla sınırlayın: BGProcessingTask kullanıyorsanız `processing` gerekçesini gönderim notlarında somutlaştırın, fakat sadece arka plan URLSession transferi için gereksiz mod eklemeyin. swift eğitimi ve swift kursu çalışmalarında sık görülen hata, kullanıcı fotoğraf seçtikten sonra geçici dosya URL'sini kuyruğa yazmaktır. `PHPicker` veya `UIDocumentPickerViewController` ile gelen güvenlik kapsamlı URL'yi staging klasörüne kopyalamadan erişim izni sona erebilir; önce `startAccessingSecurityScopedResource()`, sonra koordineli kopyalama, en son `stopAccessingSecurityScopedResource()` uygulayın.

Sık Sorulan Sorular

SwiftUI eğitimi içinde büyük video yüklemek için URLSession background mı kullanmalıyım?

Evet, kullanıcı uygulamayı arka plana aldığında da sürmesi gereken transfer için `URLSessionConfiguration.background(withIdentifier:)` ve `uploadTask(with:fromFile:)` kullanın. Dosyayı Application Support altında saklayın; bellekten verilen `Data` gövdesi yeniden başlatma sonrası güvenilir bir kaynak değildir.

iOS MVVM mimarisi arka plan yükleme durumunu ViewModel'de tutmak doğru mu?

Hayır. ViewModel ekran ömrüne bağlıdır. İş kaydını GRDB, Core Data veya eşdeğer kalıcı depoda tutun; uygulama açıldığında `URLSession.getAllTasks` ile aktif görevleri ve kalıcı `upload_jobs` kayıtlarını uzlaştırın. Sunucu tekrarlarını önlemek için her kayda idempotency anahtarı ekleyin.

Xcode eğitimi sırasında BGProcessingTask neden cihazda hiç çalışmıyor?

Önce `BGTaskSchedulerPermittedIdentifiers` içinde identifier'ın bulunduğunu, register işleminin uygulama başında yapıldığını ve `UIBackgroundModes` altında `processing` değerinin tanımlı olduğunu kontrol edin. Ardından Xcode scheme'inde Debug > Simulate Background Processing Task ile handler akışını tetikleyin; gerçek çalıştırma zamanının sistem tarafından seçildiğini varsayarak testinizi fiziksel cihazda da yapın.

app store yayınlama için arka plan dosya yüklemesinde hangi kanıtları hazırlamalıyım?

İnceleme notlarında transferin kullanıcı tarafından başlatıldığını, dosyanın ne zaman gönderildiğini ve `processing` modunun yalnızca yarım kalan yüklemeleri uzlaştırmak için kullanıldığını yazın. Test hesabı gerekiyorsa yükleme uç noktasına erişen hesap, örnek dosya ve beklenen başarılı HTTP yanıtını ekleyin.

AI / LLM Discovery

Bu makale Opendart Akademi iOS / Swift eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt