En son güncellendiği tarih: 30 May 2020
Ön Hazırlık
Coroutine Nedir ?
Coroutine'ler, thread'lerin bloklanmadan verimli bir şekilde kullanılmasını sağlayan alt görev parçalarıdır. Ayrıca thread'lerin yeniden yapılandırılması için az maliyetli ve kontrol edilebilir yapılar sağlarlar. Coroutine'leri daha iyi anlamak için biraz senkron ve asenkron programlamadan bahsedelim.
Senkron Programlama
Programlama dilleri, senkron programlamada her satırda bulunan komutu sırayla, ek bir thread kullanmadan çalıştırır. Özetle ilk sıradaki komutun çalışması bitmeden devam eden komutlar çalışmaz. Bu durum, çalışması uzun süren komutlar nedeniyle performans sıkıntısına ve gecikmeye neden olabilir.
Asenkron Programlama ve Neden Coroutine ?
Asenkron programlamada ise bir işin bitmesi beklenmeden, bu işe bağlı olmayan diğer işler çalıştırılabilir. Bunu yapmak için de temel yaklaşımla birden çok thread kullanılabilir ancak asenkron programlamada multi thread kullanımı da zorunlu değildir.
Ancak asenkron çalışması gereken, birbirine komplex şekilde bağımlı çok fazla işiniz var ise thread'leri performanslı ve doğru şekilde kullanmak ve yönetmek giderek zorlaşır.
İşte bu noktada coroutine'ler yardıma koşar:
Coroutine'ler thread tarafından yürütülen alt görevleri temsil ederler. Thread'lerin bloklanmadan askıya alınmasını sağlarlar.
Context switch yöntemiyle bu alt görevlerin senkronizasyonu sağlanır. Coroutine'ler arası geçiş, işletim sistemi kaynaklarını kullanılmadığı için thread'ler arası geçişe göre çok daha hızlıdır.
Yapısal olarak senkron fonksiyon kullanır gibi asenkron fonksiyon kullanma imkanı sağlar. Kodun okunabilirliği artar.
Özetle coroutine'ler concurrency için büyük fayda sağlarlar.
Swift'de Neden İhtiyaç Duyulur ?
Async/Await pattern'i, senkron fonksiyonlara benzer bir asenkron fonksiyon kullanım deneyimi sağlamaktadır. Böylece senkron fonksiyon çağırır gibi asenkron ve bloklanmayan bir fonksiyon çağırabiliriz. Kotlin, Go, JavaScript, Python, Rust, C #, C ++ ve diğer birçok dilde, async/await pattern'i mümkün kılan coroutines desteği mevcuttur. Ancak bu özellik Swift'te desteklenmemektedir.
Asenkron programlama genellikle callback'lerle ilişkilidir. Çok fazla asenkron iş ile çalışmıyorsanız bu yöntem swift'de kullanışlıdır. Ancak iş sayısı arttıkça callback'lerin yönetilmesi zorlaşır. Ayrıca swift'in doğal hata yakalama mekanizması asenkron programlamaya pek uygun değildir.
Ayrıca Swift'de Combine, RxSwift, PromiseKit gibi asenkron programlamayı kolaylaştıran farklı framework'ler de mevcuttur. Ancak bunlar da kullandıkları altyapılar nedeniyle asenkron programlamaya bazı noktalarda uyum sağlayamazlar. Örnek olarak sıralı asenkron işlem çağrılarında callback metodlarını, zincirleme bir şekilde tasarlamanız gerekir. Bunun sonucunda loop'ları ve hata yakalama mekanizmalarını normal şekilde kullanamayız.
2018 yılında LLVM konferansında John McCall'in coroutines hakkındaki konuşmasını aşağıdaki linkten izleyebilirsiniz.
SwiftCoroutine
Daha fazla esneklik için coroutine'leri future ve channel'lar ile tamamlıyor.
Tamamen lock-free ve senkronizasyon için sadece atomik primitive'leri kullanıyor.
Gereksiniler
iOS 10+ / macOS 10.12+ / Ubuntu
Xcode 10.2+
Swift 5+
Kurulum
Swift Package Manager'dan SwiftCoroutine 'e erişebilirsiniz.
Bu bölüm projenin github'ında bulunan readme dosyasından alıntıdır. Kütüphanenin açıklanan 4 bölümü incelenip, kullanımı türkçe'ye çevrilmiştir.
1 - Courutine API
API, coroutine'lerin nasıl schedule edileceğini açıklayan CoroutineScheduler protokolünden oluşmaktadır.
Aşağıdaki örnek, asenkron çağrıları sarmak için bir coroutine içinde await() kullanımını gösterir.
Aşağıdaki örnekte ise coroutine'lerin başlatılması adına NSManagedObjectContext 'in CoroutineScheduler ile nasıl uyumlu hale getirebildiğini gösteriyor.
2 - Futures & Promises
Future: Belli zaman sonra oluşacak bir cevabın read-only tutucusudur.
Promise: Beklenen bu sonucun sağlayıcısıdır. Asenkron bir işlemin başarılı veya hatalı sonucunu temsil eder.
Asenkron kodu senkronize etmek için uygun bir mekanizmadır. Coroutine'lerle birlikte kullanılması durumunda asenkron kodun kullanımını bir seviye ileri götürür ve kodun async/await pattern'e sahip olmasını sağlar. Coroutine'leri iskelet, Futures & Promises'i de iskeletin kasları olarak düşünebiliriz.
Hızlıdırlar, senkron şekilde sonucu beklemeyi sağlarlar, akışın iptal edilebilmesini ve yönetilmesini sağlarlar.
CoFuture ve CoPromise alt sınıflarıyla temsil edilirler.
Aşağıdaki gibi birden fazla thread'in, paralel olarak başlatılmasına ve daha sonra await() ile senkronize edilmesine izin verirler.
CoFuture'ları, yeni CoFuture'lara dönüştürmek de çok kolaydır.
3 - Channels
Future'lar ve Promise'ler coroutine'ler arasında tek bir değerin transferi için uygun bir yol sağlarlar. Channel'lar ise bir değerin akışı için yol sağlarlar. Channel, coroutine'nin boşsa kullanımı sırasında, doluysa gönderimi sırasında askıya alınmasına izin veren bir kuyruğa benzer. Bu non-blocking primitive'ler Kotlin ve GO gibi dillerde mevcuttur.
Kanal oluşturmak için aşağıdaki gibi CoChannel sınıfı kullanılır.
4 - Scope
CoFuture'ların ve CoChannel'ların her zaman referans edilmesi gerekmiyor.
Bu sınıflar genellikle execution'ları bitince yok edilirler. Ama çoğu zaman da ihtiyaç duyulmadıklarında yok edilmelidirler. Bunun için CoFuture ve CoChannel'in iptal yöntemleri mevcuttur.
CoScope bu nesnelerin yaşam döngüsünü yönetmeyi kolaylaştırıyor. Bu nesnelerin zayıf referanslara bağlanmasına olanak sağlıyor. Ayrıca bu nesneler devre dışı bırakıldıklarında iptal edilmelerine olanak sağlıyor.
CoScope'a ihtiyaç duyulmadığında veya CoScope devre dışı bırakıldığında iptali için coroutine'ler (CoFutures, CoChannels ve diğer CoCancellable) eklenebilir.
Referans ve Kaynak: