Swift’te Completion Handler Yerine async/await Kullanımı
Swift 5.5 ile tanıtılan async/await yapısı, asenkron işlemleri daha okunabilir ve yönetilebilir bir hale getirdi. Bu yazıda, Swift'teki eski completion handler yönteminden async/await kullanımına geçişi ele alacağız. Adım adım örnekler üzerinden bu geçişin nasıl yapılacağına dair açıklamalar yapacağım.
Asenkron Programlamaya Giriş 🚀
Asenkron programlama, özellikle zaman alıcı işlemler (ağ istekleri, veri tabanı sorguları, dosya okuma/yazma işlemleri) yaparken önemlidir. Bu tür işlemleri beklerken uygulamanızın kilitlenmemesi, kullanıcı deneyimi açısından kritik bir rol oynar. Swift’teki eski yöntem, bu tür işlemleri completion handler olarak bilinen geri çağırma fonksiyonları ile yönetmekti.
Örnek olarak, eski yöntemle bir ağ isteği gönderen fonksiyonu şu şekilde yazalım:
func fetchData(completion: @escaping (Data?, Error?) -> Void) {
let url = URL(string: "https://api.example.com/data")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(nil, error)
} else {
completion(data, nil)
}
}
task.resume()
}Burada, fetchData fonksiyonu bir ağ isteği yapıyor ve bitince completion bloğu çağrılıyor. Ancak bu yöntem bazen karmaşık ve hata yönetimi zordur.
async/await Nedir? 🤔
Swift’te async/await yapısı, asenkron işlemleri daha temiz ve anlaşılır bir şekilde yönetmeyi sağlar. Bu yapıyı kullanarak kodu, senkron kod gibi yazabilirsiniz. async işlevler asenkron olarak çalışır ve await ile beklenir.
async/await ile fetchData Fonksiyonunun Yeniden Yazılması
Swift 5.5 ve sonrasında, async/await sayesinde yukarıdaki örneği çok daha temiz ve basit bir şekilde yazabiliriz:
func fetchData() async throws -> Data {
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}asyncAnahtar Kelimesi: Bu fonksiyonun asenkron olduğunu belirtir.awaitAnahtar Kelimesi: Asenkron işlemin sonucunu bekleriz.throws: Hata yönetimi için kullanılanthrows, işlemi yaparken hata oluşursa onu yakalamamıza olanak tanır.
fetchData Kullanımı
async/await yapısını kullanırken, bu fonksiyonu çağıran kodun da asenkron olması gerekir. Aşağıda, bu fonksiyonu bir başka asenkron fonksiyondan nasıl çağıracağımızı gösteriyoruz:
func loadData() async {
do {
let data = try await fetchData()
print("Veri başarıyla yüklendi: \(data)")
} catch {
print("Hata oluştu: \(error)")
}
}Bu örnekte, loadData fonksiyonu da asenkron olarak tanımlandı. fetchData fonksiyonunun sonucu await ile bekleniyor ve hata yönetimi do-catch bloğu ile yapılıyor.
Avantajlar 🎯
async/await kullanmanın birçok avantajı vardır:
- Daha Temiz ve Anlaşılır Kod:
completion handlerile gelen geri çağırmalar yerine, kodunuzu adım adım yazabilirsiniz. Bu, kodun okunabilirliğini artırır. - Daha Az Hata Yapma İhtimali:
completion handlerile yapılan hata yönetimi bazen karmaşık olabilir.async/awaitile hatalar daha merkezi bir şekilde yönetilir. - Zincirleme Asenkron İşlemler: Asenkron işlemleri birbirine bağlamak (
chaining) çok daha kolaydır.
async/await ile Zincirleme Asenkron İşlemler 🔗
Örneğin, bir API’den önce veri alıp, ardından bu veriyi başka bir API’ye gönderme işlemi yapalım:
func fetchUserData() async throws -> UserData {
let url = URL(string: "https://api.example.com/user")!
let (data, _) = try await URLSession.shared.data(from: url)
let user = try JSONDecoder().decode(UserData.self, from: data)
return user
}
func fetchUserPosts(for userId: String) async throws -> [Post] {
let url = URL(string: "https://api.example.com/posts?userId=\(userId)")!
let (data, _) = try await URLSession.shared.data(from: url)
let posts = try JSONDecoder().decode([Post].self, from: data)
return posts
}
func loadUserPosts() async {
do {
let user = try await fetchUserData()
let posts = try await fetchUserPosts(for: user.id)
print("Kullanıcı gönderileri: \(posts)")
} catch {
print("Hata oluştu: \(error)")
}
}Bu örnekte, fetchUserData ve fetchUserPosts fonksiyonlarını sırasıyla bekliyoruz. Kodun her adımını daha kolay takip edebiliyoruz.
Performans ve Başlatma Maliyetleri ⚙️
Async/await gerçekten kullanımı kolaylaştırıyor, ancak arka planda bazı performans etkileri olabilir. Özellikle, birden fazla asenkron işlemi zincirleme yaparken, her bir async işlevin bağımsız olarak çalışabilmesi ve birbirinden bağımsız thread’lerde çalışabilmesi gibi avantajları da vardır. Fakat, gereksiz yere fazla async/await kullanmak bazı durumlarda performans sorunlarına yol açabilir.
Get Mine Rala’s stories in your inbox
Join Medium for free to get updates from this writer.
Buna örnek olarak, çok sayıda asenkron işlevin birbirine sırayla await edilmesi durumunda, işlemlerin sıralı olarak yapılması gerekebilir. Bunun yerine, birden fazla işlemi paralel olarak yaparak daha verimli hale getirebilirsiniz.
Örnek Paralel İşlem:
func fetchDataFromMultipleAPIs() async throws -> [Data] {
async let userData = fetchUserData()
async let postsData = fetchUserPosts(for: "userID123")
let data = try await [userData, postsData]
return data
}Burada, her iki asenkron işlem paralel olarak başlatılmakta ve en son beklenmektedir. Bu tür optimizasyonlar büyük projelerde oldukça faydalı olabilir.
Async/await ve UI Güncellemeleri 🎨
Async/await kullanırken, özellikle iOS uygulamalarında kullanıcı arayüzünü (UI) güncellemek önemlidir. Birçok zaman, UI güncellemeleri ana iş parçacığı üzerinde yapılmalıdır. Swift, async/await ile ana iş parçacığında UI güncellemelerini yapmayı kolaylaştırır. Bu sebeple, asenkron işlemler bittikten sonra UI’nin düzgün bir şekilde güncellenmesi önemlidir.
Örnek UI Güncelleme:
func loadData() async {
do {
let data = try await fetchData()
DispatchQueue.main.async {
// Ana iş parçacığında UI güncelleniyor
self.updateUI(with: data)
}
} catch {
print("Hata oluştu: \(error)")
}
}Burada, asenkron işlem tamamlandığında UI üzerinde işlem yapabilmek için DispatchQueue.main.async kullanarak ana iş parçacığında UI güncellemesi yapılır.
Hata Yönetimi ve async/await’te try, catch, throws Kullanımı ⚠️
Hata yönetimi, async/await ile çalışırken önemli bir konudur. async/await ile kullanılan throws ve try/catch yapıları oldukça temiz ve anlaşılır olsa da, doğru hata yönetimi stratejilerini kullanmak gerekir.
Özellikle, farklı seviyelerde hataları nasıl yöneteceğiniz ve hataları hangi seviyede yakalayacağınız konusunda belirli kurallar koymak, daha sağlam ve hatasız bir uygulama yapmanıza yardımcı olur.
Örnek Hata Yönetimi:
func loadData() async {
do {
let data = try await fetchData()
// Veriyi işleme
} catch let error as URLError {
print("Ağ hatası oluştu: \(error.localizedDescription)")
} catch {
print("Bilinmeyen bir hata oluştu: \(error)")
}
}Burada, catch bloklarıyla belirli hata türlerine özgü yönetim yapılır. Bu, hata yönetimini daha sağlam hale getirebilir.
Async/Await ve Geriye Dönük Uyumluluk (Backwards Compatibility) 🔄
Async/await, Swift 5.5 ile tanıtılmıştır, ancak eski iOS sürümlerinde veya eski Swift sürümlerinde kullanılmaz. Bu durumda, eski sürümleri desteklemek isteyen bir projede ne yapılabilir?
Birçok geliştirici, yeni özelliklerin geriye dönük uyumluluğunu nasıl sağlayacaklarına dair kafa karıştırıcı bir durumla karşılaşabilir. Eğer minimum iOS sürümünüz eskiyse veya eski Swift sürümleri kullanıyorsanız, async/await’i doğrudan kullanamazsınız. Bunun için, Combine framework’ü gibi alternatif teknolojiler veya completion handler’ları kullanmak gerekebilir.
Örnek Geriye Dönük Uyumluluk:
#if swift(>=5.5)
func fetchData() async throws -> Data {
let url = URL(string: "https://api.example.com/data")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
#else
func fetchData(completion: @escaping (Data?, Error?) -> Void) {
let url = URL(string: "https://api.example.com/data")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(nil, error)
} else {
completion(data, nil)
}
}
task.resume()
}
#endifBu şekilde, belirli bir Swift sürümüne göre uygun yöntemi seçebilirsiniz.
Sonuç 🎬
Swift’te async/await, asenkron programlamayı devrim niteliğinde bir şekilde dönüştürerek kod yazmayı daha temiz, anlaşılır ve bakımı kolay hale getiriyor. Eski completion handler yapılarının karmaşık yapısından ve hata yönetimindeki zorluklardan uzaklaşarak, async/await ile yazdığınız kod çok daha düzenli ve yönetilebilir oluyor. Bu yenilik, hata yönetimini basitleştirirken, zincirleme asenkron işlemleri ve paralel çalışmayı kolaylaştırarak performans optimizasyonlarına olanak tanır.
Yeni projelerinizde veya mevcut projelerinizi güncellerken async/await’i tercih ederek, modern Swift dil özelliklerinden tam anlamıyla faydalanabilirsiniz. Bu sayede, daha temiz, daha okunabilir ve sürdürülebilir bir yazılım geliştirme süreci deneyimleyebilirsiniz. Swift ile yazılım geliştirirken her zaman en yeni araçları kullanmak, hem üretkenliği artırır hem de projelerinizi geleceğe daha sağlam bir temelle taşır.









