iOS | Threading 2

cemal tüysüz
4 min readNov 6, 2022
https://www.independent.ie/

Herkese selamlar, bir önceki yazıda Senkron/Asenkron Programlama , Thread, Thread Safe ve Main Thread gibi temel konulara değinmiştik. Bu yazıda ise biraz daha ileri seviyeye geçerek GCD(Grand Central Dispatch) ve Operations gibi apiler ile sync/async programlama tekniklerini göreceğiz.

Bir önceki yazıyı henüz okumadıysanız yazıya bu link üzerinden ulaşabilirsiniz.

GCD — Grand Central Dispatch

GCD Apple ın desteklediği sync ve async çalışmamızı kolaylaştıran bir API dir. Bu api aracılığı ile asenkron çalışma mantıklarının tamamını ve çok daha fazlasını basitçe yapabiliyor oluyorsunuz. Ayrıca GCD thread safe tir yani threading işlemlerinde yaşayabileceğiniz bazı sorunlardan uzak olacaksınız.

DispatchQueue

DispatchQueue kuyruk mantığı ile (First In First Out) çalışan Main veya farklı bir thread de async veya sync çalışmamıza olanak sağlayan bir yapıdır.

Kuyruk yapısının tam olarak çalışma mantığını bilmiyorsanız kuyruk veri yapısı ile ilgili yazdığım yazımı okumanızı öneririm.

Aşağıdaki kod bloğunu inceleyelim :

Satır 1 de main thread üzerinde sync bir şekilde çalışabilmemize olanak sağlayacak bir kapsam görüyorsunuz. Öncelikle bu kodu bu şekilde Main thread üzerinde çalıştırırsanız hata alırsınız. Zaten main thread üzerinde sync olarak devam eden bir akışta tekrardan sync bir kapsam oluşturamazsınız.

Satır 5 de main thread içinde ayrı bir akış oluşturarak async bir şekilde çalışabilmek için açılan kapsamı görüyor olacaksınız.

Satır 9 da async ve 13 de sync çalışan kapsamları görüyorsunuz. Bu kapsamlar farklı bir thread de çalıştırılması için global() yöntemi ile oluşturuldular.

DispatchQueue.Attributes

Tek bir DispatchQueue nesnesinin async veya sync kod bloklarını nasıl çalıştırmasını gerektiğini belirttiğimiz parametredir.

Serial Attribute ile async biçimde çalıştırmak istediğiniz blok kuyruğa eklenir ve sıra ona gelince çalışır. DispatchQueue default olarak bu şekilde çalışır yani serial şekilde çalışmak istiyorsanız attribute vermenize gerek yoktur.

Aşağıdaki serial örneğini inceleyelim :

Concurrent attribute, yeni gelen kapsamın kuyrukta beklemeden direkt çalıştırılmasını sağlar. DispatchQueue sınıfının bu şekilde çalışmasını istiyorsak bunu belirtmemiz gerekir

Aşağıdaki kod bloğunu inceleyelim:

QoS (Quality-Of-Service)

QoS yapılacak olan işleri bir nevi kategorilere ayırmamızı sağlar. Belirttiğiniz kategoriler belirli bir servis kalitesi düzeyine sahiptir. Sistem bu kalite düzeylerini dikkate alarak yapılacak işi ele alabilir.

Örnek olarak aşağıdaki kod bloğunu inceleyelim.

.userInteractive : UI da güncelleme yapma gibi kullanıcının sonucunu hemen bekleyebileceği öncelikli işler için kullanılabilir. (TableView / CollectionView Reload, Animations vb.)

.userInitiated UI da işlerin devam edebilmesi için yapılması gerekilen hafif background sayılabilecek görevlerin performanslı ve hızlı bir şekilde yapılıp bitirilebilmesi için kullanılabilir. (örneğin kullanıcı telefonunu kayıt ettiği bir pdf raporunu uygulama içerisinde görüntülemek istediğinde vs kullanılabilir.)

.background : Kullanıcıya hissetirmek istemediğiniz işler için kullanılan servis kalitesi. (anlık event gönderme gibi çok fazla uzun sürmeyecek işler)

.utility: Background gibi çalışır aynı şekilde kullanıcıya hissettirilmeyecek işler için kullanılır. Background dan farkı ise çok daha uzun sürebilecek işler için kullanılır. Süre konusunda bir garanti yoktur.

.default : Varsayılan servis kalitesi çeşididir. Yani siz QoS belirtmediğinizde aslında default olarak bu parametre çalışır.

.unspecified : Herhangi bir QoS bilgisi verilmek istenmediğinde kullanılır. Default bile değildir. Kullanılması için spesifik bir sebep gereklidir.

DispatchWorkItem

Bu yapı ile beraber yürütmek istediğiniz kod bloğunu kapsülleyerek bir fonksiyon gibi defalarca çağırabilir ve kod bloğunu yürütebilirsiniz. DispatchQueue ile bu yapıyı beraber kullanabilirsiniz. Bunun avantajı ise bu yapı iptal edilebilme olanağını bizlere sunuyor ve kuyruğa eklediğiniz bir work item henüz çalışmadıysa iptal edilip kuyruktan çıkartılabiliyor. DispatchWorkItem ile zamanlı bir çalışma gerçekleştirebilir. Örneğin 5 saniye sonra async olarak çalışmasını istediğiniz bir kodu workItem ile kapsülleyip DispatchQueue ile yürütebilirsiniz.

Bu yapı ile bir örnek oluşturalım ve özelliklerine değinelim.

2. satırda non-optional bir biçimde oluşturduğum Dispatch work item i satır 6 da perform() ile çalıştırıyorum.

10. satırda nullable bir biçimde oluşturduğum workItem2 nesnesini 16 da çalıştırıyorum. 17 de nesne referansını bellekten uçurduğum için 19 da çalışmasını istesem de çalışmıyor olacak.

DispatchWorkItem + DispatchQueue + Timing

Bu kısımda bir work item oluşturup bunu DispatchQueue kuyruğuna ekleyip çalışmasını sağlayacağız. Bunun yanı sıra zamanlı iş oluşturup bu kod bloklarını run edeceğiz.

Satırda 1 de oluşturduğum kuyruğa 15 ve 16. satırlarda çalıştırması için work item nesnelerini veriyorum.

Satır 15 de asyncAfter ile beraber, deadline parametresi ile süre belirtip 2 saniye sonra workItem 1 nesnesini çalıştırıyorum.

DispatchWorkItem nesneleri cancel ile iptal edilebilirler. Buradaki önemli nokta work item in run edilmemiş olması gerektiğidir.

DispatchWorkItem + Notify

Execute edilmiş bir workItem nesnesi çalışmayı bitirdiğinde haberdar olmak istersek notify yöntemini kullanabiliriz. Bu yöntemi kullanırken sadece work item nesnesinin hangi kuyrukta çalışacağını belirtmemiz sonrasında da bir kapsam oluşturmamız yeterli olacaktır.

Satır 1 ve 6 da work item nesnelerimi oluşturuyorum. 12 ve 17. satırlarda notify yöntemi ile çalışma son bulduğunda ne olacağını belirttiğim kapsamlarımı oluşturuyorum. Queue parametresine direkt olarak main verdim çünkü bu sefer kendi kuyruğumu oluşturmayıp main kuyruk üzerinden work nesnelerini execute edeceğim.

22 ve 23. satırlarda da nesnelerimi run ediyorum.

DispatchGroup

DispatchGroup yapısı ile birden fazla sync ve async work item veya queue yapısını yönetebilir veya run edebilirsiniz. Bu işlemler bittiğinde de bu durumdan sync veya async bir biçimde haberdar olabilirsiniz.

Örneğin elinizde bir kullanıcı profili ekranı var ve bu ekranın tamamının oluşabilmesi için senaryo gereği birden fazla servise istek atmanız gerekiyor. Herhangi bir istek gerçekleşirken indicator gösterilir ve indicator gösterilirken UI da işlem yapılamaz.

Bu durumda istekleri atabilir ve her bir isteğin sonucuna göre ekranda olması gereken bir component i çizebilirsiniz. Böylelikle indicator sürekli açılır ve kapanır ve her şey tek tek yüklenir. DispatchGroup kullanarak tek bir indicator gösterirsiniz ve tüm bu isteklerin bittiği noktayı anlayarak erkanı tek seferde çizebilirsiniz.

Aşağıdaki örneği inceleyelim :

Öncelikle isteklerimi run edecek bir kuyruk oluşturuyorum ve bu isteklerin farklı bir thread üzerinde gitmesi için target kısmını global() olarak ayarlıyorum.

Her bir istek kuyrukta yer alan bir önceki isteğin tamamlanmasını beklemeden gitmesi için attributes kısmını .concurrent (Senaryo gereği verildi siz duruma göre buna karar veriyor olacaksınız.) veriyorum.

Sonrasında bir isteğe başlamadan evvel enter() ile bir adet process başlatıyorum. İsteğim tamamlandığında ise .leave() ile bir adet process kapatıyorum. Tüm process ler kapandığında notify kısmı tetikleniyor ve UI da çizme işlemlerimizi gerçekleştiriyoruz.

Okuduğunuz için teşekkür ederim. Umarım yazıyı faydalı bulmuşsunuzdur. bir başka yazıda görüşmek üzere, sağlıcakla kalın.

Kaynaklar

--

--