# iOS Background Processing: BGTask, Push ve Silent Notifications
iOS, kullanıcı deneyimi ve pil ömrünü korumak için arka plan işlemlerini sıkı bir şekilde kontrol eder. Ama doğru araçları kullanırsanız, uygulamanız arka planda da verimli çalışabilir. Bu rehberde BGTaskScheduler, silent push, background URLSession ve daha fazlasını production-grade örneklerle inceleyeceğiz.
İçindekiler
- Background Modes
- BGTaskScheduler
- Background App Refresh
- Background Processing Task
- Background URLSession
- Silent Push Notifications
- Background Fetch
- Location Updates
- Audio & VoIP
- Best Practices
1. Background Modes
Info.plist'te UIBackgroundModes ile tanımlanır:
Mode | Kullanım | Süre |
|---|---|---|
**fetch** | Periyodik veri güncelleme | ~30 saniye |
**processing** | Uzun arka plan işlemi | Birkaç dakika |
**remote-notification** | Silent push ile tetikleme | ~30 saniye |
**location** | Konum takibi | Sürekli (dikkatli!) |
**audio** | Ses çalma/kayıt | Sürekli |
**voip** | VoIP çağrıları | Sürekli |
**bluetooth-central** | BLE iletişimi | Sürekli |
**external-accessory** | MFi cihaz | Sürekli |
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
2. BGTaskScheduler Kurulumu
swift
1// 1. Info.plist'e ekle2// BGTaskSchedulerPermittedIdentifiers: [3// "com.app.refresh",4// "com.app.processing"5// ]6 7// 2. AppDelegate'de kayıt8class AppDelegate: UIResponder, UIApplicationDelegate {9 func application(10 _ application: UIApplication,11 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?12 ) -> Bool {13 14 // Background App Refresh task15 BGTaskScheduler.shared.register(16 forTaskWithIdentifier: "com.app.refresh",17 using: nil18 ) { task in19 self.handleAppRefresh(task: task as! BGAppRefreshTask)20 }21 22 // Background Processing task23 BGTaskScheduler.shared.register(24 forTaskWithIdentifier: "com.app.processing",25 using: nil26 ) { task in27 self.handleProcessing(task: task as! BGProcessingTask)28 }29 30 return true31 }32}3. Background App Refresh
swift
1// Kısa süreli görevler (30 saniye max)2func handleAppRefresh(task: BGAppRefreshTask) {3 // Sonraki refresh'i zamanla4 scheduleAppRefresh()5 6 // Expiration handler7 task.expirationHandler = {8 // Temizlik: devam eden işleri iptal et9 URLSession.shared.getAllTasks { tasks in10 tasks.forEach { $0.cancel() }11 }12 }13 14 // Veri güncelleme15 Task {16 do {17 let newData = try await fetchLatestContent()18 await updateLocalCache(with: newData)19 task.setTaskCompleted(success: true)20 } catch {21 task.setTaskCompleted(success: false)22 }23 }24}25 26func scheduleAppRefresh() {27 let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")28 request.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) // 15 dk sonra29 30 do {31 try BGTaskScheduler.shared.submit(request)32 } catch {33 print("Refresh schedule failed: \(error)")34 }35}36 37// Scene lifecycle'da çağır38func sceneDidEnterBackground(_ scene: UIScene) {39 scheduleAppRefresh()40}4. Background Processing Task
swift
1// Uzun süreli görevler (cihaz şarjda + Wi-Fi'da)2func handleProcessing(task: BGProcessingTask) {3 scheduleProcessing()4 5 let operationQueue = OperationQueue()6 operationQueue.maxConcurrentOperationCount = 17 8 task.expirationHandler = {9 operationQueue.cancelAllOperations()10 }11 12 let operation = DataSyncOperation()13 operation.completionBlock = {14 task.setTaskCompleted(success: !operation.isCancelled)15 }16 17 operationQueue.addOperation(operation)18}19 20func scheduleProcessing() {21 let request = BGProcessingTaskRequest(identifier: "com.app.processing")22 request.requiresNetworkConnectivity = true23 request.requiresExternalPower = false // true = sadece şarjda24 request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60) // 1 saat25 26 do {27 try BGTaskScheduler.shared.submit(request)28 } catch {29 print("Processing schedule failed: \(error)")30 }31}32 33// DataSync Operation34class DataSyncOperation: Operation {35 override func main() {36 guard !isCancelled else { return }37 38 // 1. Offline değişiklikleri al39 let pendingChanges = CoreDataManager.shared.pendingChanges()40 41 for change in pendingChanges {42 guard !isCancelled else { break }43 44 // 2. Sunucuya gönder45 let semaphore = DispatchSemaphore(value: 0)46 APIClient.shared.sync(change) { result in47 if case .success = result {48 CoreDataManager.shared.markSynced(change)49 }50 semaphore.signal()51 }52 semaphore.wait()53 }54 }55}5. Background URLSession
swift
1// Uygulama kapalıyken bile indirme/yükleme2class BackgroundDownloadManager: NSObject {3 static let shared = BackgroundDownloadManager()4 5 private lazy var session: URLSession = {6 let config = URLSessionConfiguration.background(7 withIdentifier: "com.app.background-download"8 )9 config.isDiscretionary = true // Sistem en uygun zamanı seçer10 config.sessionSendsLaunchEvents = true // Bitince uygulamayı uyandır11 config.allowsCellularAccess = false // Sadece Wi-Fi12 return URLSession(configuration: config, delegate: self, delegateQueue: nil)13 }()14 15 func downloadFile(from url: URL) -> URLSessionDownloadTask {16 let task = session.downloadTask(with: url)17 task.earliestBeginDate = Date(timeIntervalSinceNow: 60)18 task.countOfBytesClientExpectsToSend = 200 // Header boyutu19 task.countOfBytesClientExpectsToReceive = 5 * 1024 * 1024 // ~5MB20 task.resume()21 return task22 }23}24 25extension BackgroundDownloadManager: URLSessionDownloadDelegate {26 func urlSession(27 _ session: URLSession,28 downloadTask: URLSessionDownloadTask,29 didFinishDownloadingTo location: URL30 ) {31 // Geçici dosyayı kalıcı konuma taşı32 let documentsURL = FileManager.default.urls(33 for: .documentDirectory, in: .userDomainMask34 )[0]35 let destURL = documentsURL.appendingPathComponent(36 downloadTask.originalRequest?.url?.lastPathComponent ?? "download"37 )38 39 try? FileManager.default.moveItem(at: location, to: destURL)40 }41 42 func urlSession(43 _ session: URLSession,44 task: URLSessionTask,45 didCompleteWithError error: Error?46 ) {47 if let error {48 print("Download failed: \(error.localizedDescription)")49 }50 }51 52 func urlSession(53 _ session: URLSession,54 downloadTask: URLSessionDownloadTask,55 didWriteData bytesWritten: Int64,56 totalBytesWritten: Int64,57 totalBytesExpectedToWrite: Int6458 ) {59 let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)60 print("Download progress: \(Int(progress * 100))%")61 }62}63 64// AppDelegate'de completion handler65func application(66 _ application: UIApplication,67 handleEventsForBackgroundURLSession identifier: String,68 completionHandler: @escaping() -> Void69) {70 // Session'a completion handler'ı aktar71 BackgroundDownloadManager.shared.backgroundCompletionHandler = completionHandler72}6. Silent Push Notifications
swift
1// Server tarafı payload2// {3// "aps": {4// "content-available": 1 // Silent push flag5// },6// "custom-data": "sync-needed"7// }8 9// AppDelegate'de handle etme10func application(11 _ application: UIApplication,12 didReceiveRemoteNotification userInfo: [AnyHashable: Any],13 fetchCompletionHandler completionHandler: @escaping(UIBackgroundFetchResult) -> Void14) {15 // 30 saniye süre var!16 guard let action = userInfo["custom-data"] as? String else {17 completionHandler(.noData)18 return19 }20 21 Task {22 switch action {23 case "sync-needed":24 let hasNewData = await syncWithServer()25 completionHandler(hasNewData ? .newData : .noData)26 27 case "config-update":28 await refreshConfiguration()29 completionHandler(.newData)30 31 default:32 completionHandler(.noData)33 }34 }35}Silent Push Sınırlamaları
Kısıtlama | Detay |
|---|---|
**Rate limit** | Apple saatlik/günlük limit uygular |
**Low Power Mode** | Devre dışı bırakılabilir |
**Süre** | Max ~30 saniye |
**Garanti yok** | Sistem throttle edebilir |
**Background kill** | Kullanıcı uygulamayı kapatırsa çalışmaz |
7. Modern Background Fetch
swift
1// SwiftUI App lifecycle2@main3struct MyApp: App {4 @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate5 6 var body: some Scene {7 WindowGroup {8 ContentView()9 }10 .backgroundTask(.appRefresh("com.app.refresh")) {11 await handleRefresh()12 }13 }14 15 func handleRefresh() async {16 // Sonraki schedule17 let request = BGAppRefreshTaskRequest(identifier: "com.app.refresh")18 request.earliestBeginDate = .now.addingTimeInterval(30 * 60)19 try? BGTaskScheduler.shared.submit(request)20 21 // İş mantığı22 let hasUpdates = await DataSyncService.shared.checkForUpdates()23 if hasUpdates {24 await DataSyncService.shared.sync()25 }26 }27}8. Background Location
swift
1// Significant location change (pil dostu)2let locationManager = CLLocationManager()3locationManager.startMonitoringSignificantLocationChanges()4 5// Geofencing (passive, düşük pil)6let region = CLCircularRegion(7 center: CLLocationCoordinate2D(latitude: 41.0082, longitude: 28.9784),8 radius: 200,9 identifier: "office"10)11region.notifyOnEntry = true12region.notifyOnExit = true13locationManager.startMonitoring(for: region)9. Audio & VoIP Background
swift
1// AVAudioSession background playback2import AVFoundation3 4func configureAudioSession() {5 let session = AVAudioSession.sharedInstance()6 try? session.setCategory(.playback, mode: .default, options: [])7 try? session.setActive(true)8}9 10// Now Playing info (Lock Screen & Control Center)11func updateNowPlaying(title: String, artist: String, duration: TimeInterval) {12 var info = [String: Any]()13 info[MPMediaItemPropertyTitle] = title14 info[MPMediaItemPropertyArtist] = artist15 info[MPMediaItemPropertyPlaybackDuration] = duration16 info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime17 MPNowPlayingInfoCenter.default().nowPlayingInfo = info18}10. Best Practices
Background Execution Karar Ağacı
swift
1Arka plan görevi mı?2├── Kısa(<30sn)?3│ ├── Push ile tetiklenen → Silent Push4│ └── Periyodik → BGAppRefreshTask5├── Uzun(dakikalar)?6│ └── BGProcessingTask(şarj + Wi-Fi)7├── İndirme/Yükleme?8│ └── Background URLSession9├── Sürekli?10│ ├── Konum → Significant Location Change11│ ├── Ses → AVAudioSession .playback12│ ├── VoIP → PushKit13│ └── BLE → Core Bluetooth background14└── Hiçbiri → beginBackgroundTask(son çare, ~30sn)Enerji Optimizasyonu
- `isDiscretionary = true` kullan (sistem optimize eder)
- Network ihtiyacını `requiresNetworkConnectivity` ile belirt
- Minimum `earliestBeginDate` ile schedule et
- `expirationHandler`'da mutlaka temizlik yap
- Background task'ı test et: Debug → Simulate Background Fetch
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Okuyucu Ödülü
Tebrikler! Bu yazıyı sonuna kadar okuduğun için sana özel bir hediyem var:

