Mobil Mühendislik
Eski iOS Uygulamalarını Modernize Etmek: SwiftUI, Swift Concurrency ve Observation'a Kademeli Geçiş
Modernizing iOS Apps
iOS modernizasyonu bir yeniden yazım projesi değil; iOS 27 SDK takvimine karşı SwiftUI, Swift Concurrency, Observation ve UIScene'i ekran ekran, ölçerek ve geri dönülebilir adımlarla sokma işidir.
Eski iOS uygulaması yeniden yazılarak değil, SDK takvimine karşı ekran ekran, ölçülerek ve geri alınabilir adımlarla modernize edilir.
Strangler Fig ve kademeli Swift 6 geçişi Apple rehberlerinde ve toplulukta önerilen yol; rewrite yalnızca küçük, iyi belgelenmiş kod tabanlarında savunuluyor.
Zihinsel model
Boğan incir gibi: yeni ekranlar eski gövdenin etrafında büyür, eski gövde düştüğünde uygulama zaten yeni yapıdadır.
Neden
- Rewrite sürerken eski uygulama da değişir; iki ürünü paralel taşımak takvimi kaçırır.
- UIHostingController ve UIViewRepresentable köprüleri, SwiftUI'ı eski UIKit kabuğunu bozmadan ekran ekran sokmayı sağlar.
- Yeni SDK Liquid Glass'tan çıkışı ve UIScene'siz yaşam döngüsünü kapatıyor; ertelenen modernizasyon görsel regresyon ve açılmayan uygulama olarak döner.
Kanıt
Apple 27 SDK ile gönderimleri açtı ve bu SDK'yı Nisan 2027'den itibaren zorunlu kılacağını duyurdu.
MacTech, 2026Eski sistemi parça parça devreden çıkarmak, tek seferde değiştirmekten daha az risklidir; geçiş için dikiş yerleri aranır.
Martin Fowler, 2024Apple'ın Swift 6 geçiş örneği, hedef hedef complete checking açıp uyarıları temizleyerek ilerliyor.
Apple WWDC, 2024Karşı görüş
Küçük ve iyi belgelenmiş bir kod tabanında rewrite daha hızlı olabilir; köprülerle iki UI çerçevesini uzun süre birlikte taşımanın da bakım maliyeti vardır.
Kavramlar
Pratikte
- İlk iş UIScene yaşam döngüsüne geç ve Liquid Glass regresyonlarını snapshot testiyle yakala.
- Swift 6 modundan önce modül bazında complete checking aç, uyarıları sıfırla, yenisini CI'da engelle.
- Her yeni SwiftUI ekranını Feature Flag arkasına koy; sorun çıkarsa eski UIKit ekranına dön.
Yıllar içinde UIKit, delegate, completion handler ve Combine katmanlarıyla büyümüş bir iOS kod tabanını bugünün araçlarına kırmadan taşımak, Ekim 2026 itibarıyla ertelenebilecek bir iş değil. iOS 27, Xcode 27 ve Swift 6.4 Eylül 2026'da yayımlandı; Nisan 2027'den itibaren App Store yüklemeleri 27 SDK ile derlenmek zorunda. 27 SDK ile derlenen bir uygulamada Liquid Glass'tan çıkış anahtarı yok sayılıyor ve UIScene yaşam döngüsünü benimsemeyen uygulama açılmıyor. Öte yandan araçlar geçişi hiç olmadığı kadar kolaylaştırdı: Swift 6.2'nin Approachable Concurrency ayarları strict concurrency sürtünmesini azalttı, iOS 26 UIKit tarafına @Observable takibi getirdi, Swift 6.4 ise async temizlik kodunu sadeleştirdi. Doğru strateji büyük bir rewrite değil; uyarı bütçesiyle başlayan, modül modül ilerleyen ve her adımı ölçülen bir bakım disiplini.
Çıkarımlar
- Modernizasyon takvimle başlar: Nisan 2027'de 27 SDK zorunlu; 27 SDK ile Liquid Glass'tan çıkış yok ve UIScene'e geçmemiş uygulama açılmıyor.
- UIKit'ten SwiftUI'a geçişi ekran ekran, UIHostingController köprüsüyle ve sık değişen, mantığı az ekranlardan başlayarak yap.
- Swift 6 moduna geçmeden önce modül bazında complete checking ile uyarıları sıfırla; Approachable Concurrency ile UI modüllerinde MainActor'ı varsayılan yap.
- @Observable'a geçiş, okunmayan özellik değişikliklerinin view'ı yeniden çizdirmesini engeller; iOS 26'dan beri UIKit de bundan yararlanıyor.
- Her adım ölçülebilir ve geri alınabilir olmalı: referans metrikler, snapshot testleri ve Feature Flag.
Alt başlıklar
Neden şimdi: SDK takvimi, Liquid Glass ve UIScene zorunluluğu
Modernizasyonu tetikleyen şey çoğu zaman bir mimari heves değil, takvimdir. Apple, Nisan 2027'den itibaren App Store Connect'e yüklenen uygulamaların 27 SDK ile derlenmesini şart koşuyor. iOS 27 SDK ile derlendiğinde sistem UIDesignRequiresCompatibility anahtarını yok sayıyor; yani Liquid Glass'tan geri çekilme seçeneği kalmıyor. Bu, özel çizilmiş navigation bar ve tab bar'ları olan eski UIKit ekranlarının bir anda görsel regresyon üretmesi demek. İkinci kapı UIScene: Apple'ın TN3187 teknik notuna göre iOS 26'dan sonraki ilk büyük sürümde, en güncel SDK ile derlenen ve scene tabanlı yaşam döngüsüne geçmemiş uygulama açılmıyor. Hâlâ yalnızca UIApplicationDelegate ile yaşayan eski projeler için bu, takvimdeki en somut iş kalemi.
Sahada sık gördüğüm tablo şu: projede yüzlerce derleyici uyarısı birikmiş, kimse bakmıyor, Swift 6 moduna geçiş denemesi ilk gün Sendable hatalarıyla rafa kalkıyor. Pratik bir eşik koymak işe yarıyor: önce uyarı sayısını sıfıra indirmeyi hedefleyen bir sprint, sonra her yeni Pull Request için “yeni uyarı yok” kuralı. Modernizasyonun ilk ölçülebilir çıktısı yeni bir ekran değil, CI'da kırmızıya dönen bir uyarı bütçesidir.
Ekim 2026 itibarıyla durum
Son 12 ayda iOS modernizasyonu bir tercih olmaktan çıkıp takvime bağlandı. 28 Nisan 2026'dan beri App Store Connect'e yüklenen uygulamalar Xcode 26 ve iOS 26 SDK ile derlenmek zorunda; 9 Eylül 2026'dan beri minimum deployment target da iOS 13. iOS 27, Xcode 27 ve Swift 6.4, 14-15 Eylül 2026'da yayımlandı ve Apple 27 SDK ile gönderimleri açtı; Nisan 2027'de 27 SDK zorunlu hâle gelecek. Xcode 27 yalnızca Apple silicon Mac'lerde ve macOS Tahoe 26.6 üzerinde çalışıyor, cihazda hata ayıklama için en az iOS 17 istiyor. iOS 27 SDK ile derlenen uygulamalarda iki kaçış kapısı kapandı: Liquid Glass uyumluluk anahtarı yok sayılıyor ve UIScene'e geçmemiş uygulama açılmıyor. Dil tarafında Swift 6.3 (Mart 2026) Swift Testing'e test iptali ve görüntü ekleri getirdi; Swift 6.4 ise defer içinde await, withTaskCancellationShield ve SwiftPM'de varsayılan Swift Build ile geldi. WWDC26'da SwiftUI'da State bir makroya dönüştü. Xcode 27 hâlâ Swift 6, 5, 4.2 ve 4 dil modlarını destekliyor; yani Swift 6 moduna geçiş zorunlu değil, ama Approachable Concurrency ile UI modüllerinde varsayılan MainActor izolasyonu önerilen başlangıç noktası olmaya devam ediyor.
UIKit'ten SwiftUI'a: ekran ekran, köprüyle geçiş
Büyük patlama tarzı yeniden yazım (big-bang rewrite) iOS'ta neredeyse her zaman takvimi kaçırır. Daha güvenli yol strangler fig yaklaşımı: eski uygulamanın kabuğu UIKit olarak kalır, yeni ve değişen ekranlar SwiftUI ile yazılıp UIHostingController içinde gösterilir. Tersi de mümkün: SwiftUI ekranın içinde hâlâ gereken bir UIKit bileşenini UIViewRepresentable ile sarabilirsiniz.
Hangi ekrandan başlanacağı için basit bir puanlama kullanılabilir: değişim sıklığı yüksek, iş mantığı az, özel animasyonu olmayan ekranlar önce. Ayarlar, profil, onboarding gibi formlar ideal adaylar. Ödeme akışı, karmaşık koleksiyon görünümleri ve klavye etkileşimi ağır ekranlar sona kalmalı.
Anti-pattern: SwiftUI ekranını yazıp içine eski view controller'ın 1.500 satırlık mantığını olduğu gibi kopyalamak. Görünüm katmanı değişir ama test edilemezlik aynen taşınır. Geçişten önce mantığı ayrı bir model tipine çıkarmak, geçişin asıl kazancıdır.
Swift Concurrency: completion handler'dan async/await'e
Swift Concurrency'ye geçişte en ucuz kazanç, ağ ve veri katmanındaki completion handler'ları async fonksiyonlara çevirmek. Mevcut callback tabanlı API'leri bir anda silmek gerekmiyor; withCheckedThrowingContinuation ile async bir sarmalayıcı yazıp çağıranları kademeli taşıyabilirsiniz. Combine zincirlerinin büyük kısmı da AsyncSequence ve basit async döngülerle daha okunur hâle gelir.
Dikkat edilmesi gereken iki tuzak var. Birincisi, continuation'ı iki kez resume etmek ya da hiç etmemek; checked varyant bunu çalışma zamanında yakalar, bu yüzden geçiş döneminde unsafe varyanta inmeyin. İkincisi, Task { } ile her yere yapılandırılmamış görev saçmak. Bir view kapandığında iptal edilmeyen görevler, eski delegate sızıntılarının yeni hâlidir. SwiftUI'da .task değiştiricisi görevi view yaşam döngüsüne bağlar ve iptali otomatik yapar. Swift 6.4 burada iki pratik araç ekledi: defer bloğu içinde await kullanılabiliyor (SE-0493) ve withTaskCancellationShield, iptal edilmiş bir görevde bile temizlik ya da geri alma kodunun tamamlanmasını sağlıyor (SE-0504). Eskiden ayrı bir Task içine sarılan async temizlik kodu artık doğrudan yazılabiliyor.
Ölçü olarak, bir modülde kalan completion handler sayısını grep ile sayıp sprint başına düşüşünü izlemek yeterince somut bir metrik.
Swift 6 strict concurrency ve Approachable Concurrency
Swift 6 dil modu, Swift 5'te uyarı olan veri yarışı kontrollerini derleme hatasına çevirir. Eski projelerde bu, Sendable olmayan tiplerin aktörler arasında geçtiği yüzlerce satır demek. Swift 6.2 ile gelen Approachable Concurrency bu yükü azaltmak için birkaç ayarı bir araya getiriyor: SE-0466 ile modül genelinde varsayılan izolasyonu MainActor yapabiliyorsunuz, SE-0461 (NonisolatedNonsendingByDefault) ile nonisolated async fonksiyonlar çağıranın aktöründe çalışıyor, SE-0470 ile izole protokol uyumları çıkarsanıyor. Xcode 26 ile açılan yeni projelerde bunlar varsayılan geliyor; Swift paketlerinde ise Package.swift içinde .defaultIsolation(MainActor.self) ile açılıyor.
Uygulama hedefleri için mantıklı strateji: UI ağırlıklı modüllerde MainActor varsayılanını açmak, gerçekten paralel iş yapan kodu @concurrent ile açıkça işaretlemek. Geçişi modül modül yapın: önce uyarı düzeyinde complete checking, sıfır uyarı, sonra Swift 6 modu. @unchecked Sendable ve nonisolated(unsafe) birer kaçış kapısıdır; her kullanımı bir yorum ve bir takip kaydıyla gelmeli.
Observation: ObservableObject'ten @Observable'a
iOS 17 ile gelen Observation çerçevesi, @Observable makrosuyla ObservableObject ve @Published ikilisinin yerini alıyor. Fark yalnızca sözdizimsel değil: eski modelde bir @Published özelliği değiştiğinde ona abone olan tüm view'lar yeniden değerlendirilir; @Observable ise view'ın gövdede gerçekten okuduğu özellikleri takip eder. Büyük bir ayarlar modeli olan ekranlarda gereksiz yeniden çizimlerin ciddi kısmı bu şekilde kaybolur.
Geçişte eşleme basit: @StateObject yerine @State, @ObservedObject yerine düz özellik ya da @Bindable, @EnvironmentObject yerine tip tabanlı @Environment. iOS 26 ile UIKit de bu işe katıldı: updateProperties() ve viewWillLayoutSubviews() içinde okunan @Observable özellikleri otomatik takip ediliyor, değiştiklerinde view güncelleniyor. iOS 18'de aynı davranış Info.plist'e UIObservationTrackingEnabled anahtarı eklenerek açılabiliyor.
WWDC26 ile SwiftUI'da State bir makroya dönüştü: @State içinde tutulan sınıflar artık view ömrü başına bir kez ve tembel olarak başlatılıyor. Bunun bir bedeli de var; Xcode 27 ile @State özelliğine bildirim yerinde değer atayan bazı initializer'lar artık derlenmiyor, bu yüzden SwiftUI ekranlarını yeni SDK'ya taşırken ilk derleme hatası listesine hazırlıklı olun.
Anti-pattern: @Observable sınıfı içinde hesaplanan, pahalı bir özelliği her body çağrısında okumak. Takip doğru çalışır ama maliyet view'a taşınır.
Güvenli geçiş için ölçüm ve test ağı
Modernizasyonun en büyük riski, kullanıcıya görünmeyen davranış değişiklikleridir: bir async çevirisinden sonra iki istek artık paralel gidiyor, MainActor varsayılanından sonra bir ağır iş ana iş parçacığına kayıyor. Bunu yakalamanın yolu geçişten önce bir referans çizgisi almak. Üç metrik genelde yeterli: soğuk açılış süresi, kilit ekranların ilk çizim süresi ve crash-free session oranı. Hedef koyarken somut olun; örneğin “geçiş boyunca crash-free session oranı yüzde 99,8'in altına inmeyecek”.
Test tarafında Swift Testing, paralel ve parametreli testleri kolaylaştırıyor; async kodu test etmek için XCTest'teki expectation dansına gerek kalmıyor. Swift 6.3 ile uyarı düzeyinde sorun bildirme, test iptali ve görüntü ekleri geldi; başarısız bir UI testine ekran görüntüsü iliştirmek artık çerçevenin kendi işi. SwiftUI ekranları için snapshot testleri, Liquid Glass gibi otomatik görsel değişikliklerde regresyonu yakalamanın en ucuz yolu.
Her geçiş adımı bir feature flag arkasında, sunucudan kapatılabilir olmalı. Yeni SwiftUI ekranında sorun çıkarsa eski UIKit ekranına dönmek bir yapılandırma değişikliği kadar ucuz olmalı.
Son gelişmeler
Ekim 2026 itibarıyla
Önce kendi cevabını düşün, sonra aç.
Uygulamamız hâlâ iOS 15'i destekliyor. Bu modernizasyon bize uygun mu?
Kısmen. Swift Concurrency iOS 13'e kadar geri dağıtılabiliyor, yani async/await ve aktörler bugünden kullanılabilir; App Store da Eylül 2026'dan beri zaten en az iOS 13 hedefi istiyor. @Observable ise iOS 17 istiyor ve Xcode 27 ile cihazda hata ayıklama da iOS 17 ve üstü cihazlarla sınırlı. Bu yüzden önce kullanıcı dağılımına bakıp minimum sürümü iOS 17'ye çekme kararını vermek, ardından Observation geçişini planlamak gerekir.
Neden doğrudan sıfırdan SwiftUI ile yeniden yazmıyoruz?
Çünkü yeniden yazım sürerken eski uygulama da değişmeye devam eder ve iki ürünü paralel taşırsınız. Ekran ekran geçişte her sprint sonunda üretimde çalışan bir uygulama olur ve risk küçük parçalara bölünür. Yeniden yazım ancak kod tabanı küçük ve iş mantığı iyi belgelenmişse makul.
Swift 6 moduna geçince yüzlerce hata çıkıyor, @unchecked Sendable ile geçiştirsek olmaz mı?
Kısa vadede derler ama derleyicinin size vermeye çalıştığı güvenceyi kapatmış olursunuz. Daha iyi yol, UI modüllerinde varsayılan MainActor izolasyonunu açmak; hataların büyük kısmı bu sayede kaybolur. Kalanlarda @unchecked kullanımını gerekçeli ve sayılı tutun.
Combine'ı tamamen bırakmalı mıyız?
Zorunlu değil. Yeni kodda async/await ve AsyncSequence daha okunur ve Observation ile daha iyi uyuşur. Çalışan, test edilmiş Combine zincirlerini yalnızca dokunmanız gerektiğinde çevirmek, toplu bir dönüşümden daha az risklidir.
UIScene geçişi gerçekten acil mi, yoksa bir uyarı olarak mı kalacak?
Acil. Apple'ın TN3187 notu, iOS 26'dan sonraki ilk büyük sürümde en güncel SDK ile derlenen ve scene yaşam döngüsüne geçmemiş uygulamanın açılmayacağını söylüyor. Nisan 2027'de 27 SDK zorunlu olduğuna göre, en basit hâliyle Info.plist'e bir scene manifest eklemek ve yaşam döngüsü kodunu SceneDelegate'e taşımak bu takvimin ilk işi olmalı.
Notun kaynakları
- Approachable Concurrency in Swift Packages (Use Your Loaf)
- Automatic property observation in UIKit with @Observable (Nil Coalescing)
- Apple Newsroom: Apple accelerates app development with new intelligence frameworks and advanced tools (Haziran 2026)
- MacTech: App Store submissions now open for the latest OS releases (Eylül 2026)
- Swift.org: Swift 6.4 Released (15 Eylül 2026)
- Swift.org: Swift 6.3 Released (24 Mart 2026)
- Apple Developer: UIDesignRequiresCompatibility
- Apple Developer: TN3187 Migrating to the UIKit scene-based life cycle
- Apple Developer: Upcoming Requirements
- Xcode 27 Is Out: Requirements, Targets, and What Broke (Blake Crosley, Eylül 2026)
- Apple Developer: WWDC26 SwiftUI guide
Sunum stili
12 slayt. Tıklarsan sunum o slayttan başlar.
Slayt notları
Konuyu büyük bir proje olarak değil, her sprint'e sığan bir bakım disiplini olarak çerçevele. Soru: büyümüş bir kod tabanını kırmadan bugüne nasıl taşırız?
Ekipte kaç kişinin yarım kalmış bir SwiftUI rewrite'ı yaşadığını sor. Sorun niyette değil, risk tek seferde biriktiği için.
27 SDK ile Liquid Glass uyumluluk anahtarı yok sayılıyor ve UIScene'e geçmemiş uygulama açılmıyor. Takvim, ne zaman başlayacağınız sorusunu çoktan cevapladı.
UIHostingController ile SwiftUI ekranı UIKit içinde, UIViewRepresentable ile tersini gösterebilirsiniz. Köprü iki yönde de çalışıyor.
Sık değişen ve mantığı az ekranlar en ucuz kazancı verir. Asıl kazanç view'ı değil, 1.500 satırlık mantığı test edilebilir bir modele çıkarmak.
Checked continuation'dan unsafe varyanta inmeyin; çift resume'u çalışma zamanında yakalar. Her yere saçılan Task { } blokları, eski delegate sızıntılarının yeni hâlidir.
400-800 uyarılık projeler Swift 6 denemesini ilk gün rafa kaldırır. Önce uyarı bütçesi, sonra dil modu.
UI ağırlıklı modüllerde MainActor varsayılanı hata listesinin büyük kısmını eritir. Swift paketlerinde .defaultIsolation(MainActor.self) ile açılır.
Büyük modelli ekranlarda gereksiz yeniden çizimlerin ciddi kısmı gider. Tuzak: pahalı hesaplanan özelliği her body çağrısında okumak.
Taşımadan önce referans çizgisi alın. Yeni SwiftUI ekranında sorun çıkarsa eskiye dönmek bir config değişikliği kadar ucuz olmalı.
Kısa vadede derler ama derleyicinin vermeye çalıştığı güvenceyi kapatırsınız. Bu kısayolu kullanan ekiplere sayılı ve gerekçeli tutmalarını söyle.
Uyarı sayısını sıfıra indirdiğiniz gün gerisi plan meselesi olur. Ekipten bu sayıyı bir sonraki retroya getirmesini iste.