Tüm Yazılar
KategoriiOS
Okuma Süresi
20 dk okuma
Yayın Tarihi
...
Kelime Sayısı
1.642kelime

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

Background App Refresh, BGTaskScheduler, silent push notifications ve background URLSession. iOS arka plan işlemlerinin tam rehberi.

iOS Background Processing: BGTask, Push ve Silent Notifications

# 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


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 ekle
2// BGTaskSchedulerPermittedIdentifiers: [
3// "com.app.refresh",
4// "com.app.processing"
5// ]
6 
7// 2. AppDelegate'de kayıt
8class AppDelegate: UIResponder, UIApplicationDelegate {
9 func application(
10 _ application: UIApplication,
11 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
12 ) -> Bool {
13 
14 // Background App Refresh task
15 BGTaskScheduler.shared.register(
16 forTaskWithIdentifier: "com.app.refresh",
17 using: nil
18 ) { task in
19 self.handleAppRefresh(task: task as! BGAppRefreshTask)
20 }
21 
22 // Background Processing task
23 BGTaskScheduler.shared.register(
24 forTaskWithIdentifier: "com.app.processing",
25 using: nil
26 ) { task in
27 self.handleProcessing(task: task as! BGProcessingTask)
28 }
29 
30 return true
31 }
32}

3. Background App Refresh

swift
1// Kısa süreli görevler (30 saniye max)
2func handleAppRefresh(task: BGAppRefreshTask) {
3 // Sonraki refresh'i zamanla
4 scheduleAppRefresh()
5 
6 // Expiration handler
7 task.expirationHandler = {
8 // Temizlik: devam eden işleri iptal et
9 URLSession.shared.getAllTasks { tasks in
10 tasks.forEach { $0.cancel() }
11 }
12 }
13 
14 // Veri güncelleme
15 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 sonra
29 
30 do {
31 try BGTaskScheduler.shared.submit(request)
32 } catch {
33 print("Refresh schedule failed: \(error)")
34 }
35}
36 
37// Scene lifecycle'da çağır
38func 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 = 1
7 
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 = true
23 request.requiresExternalPower = false // true = sadece şarjda
24 request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 60) // 1 saat
25 
26 do {
27 try BGTaskScheduler.shared.submit(request)
28 } catch {
29 print("Processing schedule failed: \(error)")
30 }
31}
32 
33// DataSync Operation
34class DataSyncOperation: Operation {
35 override func main() {
36 guard !isCancelled else { return }
37 
38 // 1. Offline değişiklikleri al
39 let pendingChanges = CoreDataManager.shared.pendingChanges()
40 
41 for change in pendingChanges {
42 guard !isCancelled else { break }
43 
44 // 2. Sunucuya gönder
45 let semaphore = DispatchSemaphore(value: 0)
46 APIClient.shared.sync(change) { result in
47 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ükleme
2class 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çer
10 config.sessionSendsLaunchEvents = true // Bitince uygulamayı uyandır
11 config.allowsCellularAccess = false // Sadece Wi-Fi
12 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 boyutu
19 task.countOfBytesClientExpectsToReceive = 5 * 1024 * 1024 // ~5MB
20 task.resume()
21 return task
22 }
23}
24 
25extension BackgroundDownloadManager: URLSessionDownloadDelegate {
26 func urlSession(
27 _ session: URLSession,
28 downloadTask: URLSessionDownloadTask,
29 didFinishDownloadingTo location: URL
30 ) {
31 // Geçici dosyayı kalıcı konuma taşı
32 let documentsURL = FileManager.default.urls(
33 for: .documentDirectory, in: .userDomainMask
34 )[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: Int64
58 ) {
59 let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
60 print("Download progress: \(Int(progress * 100))%")
61 }
62}
63 
64// AppDelegate'de completion handler
65func application(
66 _ application: UIApplication,
67 handleEventsForBackgroundURLSession identifier: String,
68 completionHandler: @escaping() -> Void
69) {
70 // Session'a completion handler'ı aktar
71 BackgroundDownloadManager.shared.backgroundCompletionHandler = completionHandler
72}

6. Silent Push Notifications

swift
1// Server tarafı payload
2// {
3// "aps": {
4// "content-available": 1 // Silent push flag
5// },
6// "custom-data": "sync-needed"
7// }
8 
9// AppDelegate'de handle etme
10func application(
11 _ application: UIApplication,
12 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
13 fetchCompletionHandler completionHandler: @escaping(UIBackgroundFetchResult) -> Void
14) {
15 // 30 saniye süre var!
16 guard let action = userInfo["custom-data"] as? String else {
17 completionHandler(.noData)
18 return
19 }
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 lifecycle
2@main
3struct MyApp: App {
4 @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
5 
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 schedule
17 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 = true
12region.notifyOnExit = true
13locationManager.startMonitoring(for: region)

9. Audio & VoIP Background

swift
1// AVAudioSession background playback
2import AVFoundation
3 
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] = title
14 info[MPMediaItemPropertyArtist] = artist
15 info[MPMediaItemPropertyPlaybackDuration] = duration
16 info[MPNowPlayingInfoPropertyElapsedPlaybackTime] = player.currentTime
17 MPNowPlayingInfoCenter.default().nowPlayingInfo = info
18}

10. Best Practices

Background Execution Karar Ağacı

swift
1Arka plan görevi mı?
2├── Kısa(<30sn)?
3│ ├── Push ile tetiklenen → Silent Push
4│ └── Periyodik → BGAppRefreshTask
5├── Uzun(dakikalar)?
6│ └── BGProcessingTask(şarj + Wi-Fi)
7├── İndirme/Yükleme?
8│ └── Background URLSession
9├── Sürekli?
10│ ├── Konum → Significant Location Change
11│ ├── Ses → AVAudioSession .playback
12│ ├── VoIP → PushKit
13│ └── BLE → Core Bluetooth background
14└── 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:

Etiketler

#Background#BGTask#Push Notification#URLSession#iOS#Concurrency
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