iOS ve Concurrency (Eşzamanlılık)-Operation

Ayşe Nur Bakırcı
Delivery Hero Tech Hub
6 min readApr 11, 2022

Herkese merhaba, önceki yazımda Grand Centra Dispatch yapısını ve nasıl kullanıldığını anlatmaya çalıştım. Burada ise GCD’nin üzerine inşa edilmiş ve asenkron görevler üzerinde daha fazla etkileşime sahip olmamızı sağlayan Operation yapısını anlatacağım.

Operation

Operasyonlar, tek bir görevle ilişkili soyut sınıflardır. Soyut sınıf oldukları için doğrudan kullanılamazlar. Operasyonları, özel alt sınıflar oluşturarak veya BlockOperation gibi sistemde tanımlı olan alt sınıflar yardımıyla kullanabiliriz.

Operasyonlar, görevlerin güvenli bir şekilde yürütülmesini ve koordine edilmesini sağlar. Bu yapının özellikleri, bağımlılık oluşturulabilmesi, durumlarının takip edilebilmesi ve iptal edilebilmesidir.

Operasyonlar kullanılırken dikkat edilmesi gereken nokta, oluşturulan bir operasyon nesnesinin yalnızca bir kez çalıştırılabilmesidir. Bu yüzden projelerde, her görev için bir operasyon nesnesi oluşturularak bu nesnelerin OperationQueue’ya eklenmesi tercih edilir.

Operasyonları yürütmenin bir diğer yolu da start metodudur. OperationQueue kullanmak istemediğimiz durumlarda, operasyonları start metoduyla manuel olarak yürütebiliriz. Ancak start metodu, işlemi o an bulunulan thread’de seri çalıştırdığı için OperationQueue’ya göre sisteme daha fazla yük getirecektir.

Bir operasyonu iptal etmek istediğimizde ise cancel metodunu kullanabiliriz.

Operation Durumları

Her operasyonun bir yaşam döngüsü vardır. Yaşam döngüsünün çeşitli bölümlerinde oluşan durumları takip ederek operasyonun ilerleyişi hakkında bilgi sahibi olabiliriz. Bu durumlar;

  • isReady: Operasyonun çalışmaya hazır olduğunu belirten durumdur.
  • isExecuting: isReady durumundaki bir operasyonun durumu, start methodu çağrıldığında veya OperationQueue’ya eklendiğinde isExecuting olur. Bu operasyonun yürütüldüğü anlamına gelir.
  • isCancelled: Operasyon devam ederken, cancel methodu çağrılırsa bu operasyonun isCancelled özelliği true olur. Ancak başlatılan işlem sonlandırılmaz. Tamamlanana kadar devam eder.
  • isFinished: Operasyonun tamamlandığını belirten durumdur. Operasyon iptal edilse de isFinished durumuna geçer.

Operation Oluşturmak

— Block Operation

BlockOperation, sistemde tanımlı bir operasyon sınıfıdır. Bu yapı karmaşık işlemler içermeyen basit operasyonların oluşturulması için kullanılabilir.

Yukarıdaki örneği incelersek;

  1. Öncelikle BlockOperation kullanılarak yeni bir operasyon oluşturulmuştur.
  2. Oluşturulan operasyona yeni bir işlem daha eklemek için BlockOperation’ın addExecutionBlock metodu kullanılmıştır. Bu adımdan sonra, elimizde bir operasyon nesnesi ve bu operasyon nesnesinin yürüteceği iki görev vardır.
  3. Operasyonlar tamamlandıktan sonra, tamamlandığını belirten bir işlem yapmak için completionBlock özelliği kullanılmıştır. CompletionBlock yalnızca operasyonun içerisindeki tüm görevler tamamlandığında çalışır.
  4. Son olarak bu operasyonun işlenebilmesi için başlatılması gerekir. Burada start metodu yardımıyla operasyon başlatılmıştır.

Yukarıda oluşturduğumuz yapı çalıştırıldığında aşağıdaki çıktıyı alırız.

— Subclassing Operation

Operasyonları BlockOperation ile oluşturup kolay bir şekilde kullanabilsek de, projelerde operasyonları kullanmamızın sebebi karmaşık ve yeniden kullanılabilir işlemleri gerçekleştirmektir. Bu yüzden projelerde operasyonlar genel olarak alt sınıflar oluşturularak kullanılırlar.

Yukarıdaki örneği incelersek;

  1. StringOperation adında yeni bir alt sınıf oluşturulmuştur. Bu yapı terminale yazı yazdıran basit bir operasyon örneğidir.
  2. Operasyon nesnesi yürütüldüğünde yapılması gereken işlemler, Operation sınıfından override edilen main metodu içerisine yazılır.
  3. Main metodu içerisinde yapılacak işlemler oluşturulurken, operasyonun iptal edilip edilmediğini de kontrol edilebilir. Bu kontroller operasyon iptal edildiğinde fazladan işlem yapmaktan kaçınmamızı sağlar.
  4. Oluşturduğumuz StringOperation sınıfından yeni bir operasyon nesnesi oluşturabilir, bu operasyona da completionBlock ekleyebiliriz. Buradaki yapı, BlockOperation yapısıyla aynıdır.
  5. Oluşturduğumuz operasyon nesnelerini, OperationQueue’ya ekleyerek operasyonun başlamasını sağlarız. Yukarıda da belirttiğim gibi operasyonlar start metodu çağrılarak veya OperationQueue’ya eklenerek başlatılır. Burada da operasyon, OperationQueue’ya eklenerek başlatılmıştır.

Yukarıda oluşturduğumuz yapı çalıştırıldığında aşağıdaki çıktıyı alırız.

Operation Queue (Operation Kuyrukları)

Bir operasyonun performanslı bir şekilde ilerlemesini sağlayan yapı OperationQueue’dur. GCD’de bulunan DispatchQueue’lar gibi, OperationQueue’lar da operasyonları yürütmek, zamanlanmasını sağlamak ve aynı anda çalışabilecek operasyon sayısını kontrol etmek için kullanılır.

Operasyon kuyruklarının özelliklerini kısaca listelersek;

  • Operasyon kuyrukları varsayılan olarak background QoS değerine sahiptir. Bu sayede kuyruğa eklenen görevler kullanıcı deneyimini etkilemeden çalışır.
  • Operasyon kuyruklarına eklenen görevler hazır olma durumlarına göre çağrılır ve işlem bitene kadar kuyrukta kalır. Bu görevler kuyrukta kaldığı için operasyon kuyruğu hafızada korunur. Bu yüzden görevleri tamamlanmamış bir operasyon kuyruğunu askıya almak, memory leak oluşmasına yol açabilir.
  • Operasyon kuyruklarıyla çalışırken, aynı anda yapılması gereken görev sayısını maxConcurrentOperationCount özelliği ile belirtebiliriz. Bu özelliğe herhangi bir değer vermezsek operasyon kuyruğu aynı anda sürdürebildiği kadar operasyonu sürdürecektir. Eğer bu özelliğin değerini 1'e eşitlersek operasyon kuyruğu seri bir kuyrukmuş gibi çalışacaktır.

OperationQueue’ya hiçbir özellik eklenmediği durumda aldığımız çıktı:

OperationQueue’nun maxConcurrentOperationCount özelliğinin 1 olduğu durumda aldığımız çıktı:

Operation Bağımlılıkları

Operation yapısını GCD’den ayıran önemli kısımlardan birisi de operasyonların bağımlılıklarıdır. Birbiriyle,

  • Ön koşul işlemi tamamlanmadan bağımlı işlemin başlamaması,
  • İlk operasyondan ikincisine veri geçirmek için önce ilk operasyonun tamamlanması

şeklinde ilişkileri olan operasyonlar arasında bağımlılıklar oluşturabiliriz.

Operasyon bağımlılıklarını yönetmek için kullanılan iki farklı metod vardır. addDependency metodu operasyona bağımlılık eklemek için kullanılırken, removeDependency metodu eklenen bağımlılığın kaldırılması için kullanılır.

Birbirine bağımlı operasyonlar oluşturduğumuzda, bir operasyonun bağımlı olduğu tüm işlemler tamamlanmadan operasyon isReady durumuna geçemez. Bu durum işlemler tamamlanmadan operasyonun başlamamasını sağlar. Bağımlılıklar için operasyonun başarılı veya başarısız tamamlanması önemli değildir. Operasyon başlatılmadan önce bağımlı olduğu herhangi bir operasyon başarısız olarak tamamlansa da operasyon yürütülecektir. Eğer bağımlı operasyonların herhangi birinde hata olduğunda diğer operasyonların devam etmemesini istersek, bunu manuel olarak kontrol etmemiz gerekir.

Yukarıdaki örnekte öncelikle DownloadModelOperation, modeli indirir. Model indirildikten sonra DownloadImageOperation indirilen model içerisindeki resmi indirir. Son olarak eğer resimleri filtrelemek istersek ImageFilterOperation ile indirilen resimler filtrelenir. Göründüğü gibi buradaki tüm işlemler birbiriyle bağlantılıdır. Bu görevler arasında bağımlılık oluşturmak, görevlerin daha sağlıklı ilerlemesini sağlar.

Proje içerisinde 1. adımı incelediğimizde, downloadImage ile downloadModel arasında bağımlılık oluşturulmuştur. Bu durum, downloadModel tamamlandıktan sonra downloadImage’ın hazır olacağı anlamına gelir. Aynı bağımlılık imageFilter ile downloadImage arasında da oluşturulmuştur.

2. adımda ise imageFilter ile downloadImage’a arasındaki bağımlılık kaldırılmıştır.

Üç operasyonun da birbirine bağlı olduğu durumda aldığımız çıktı;

imageFilter’ın bağımlılığı kaldırıldığında, herhangi bir operasyonun tamamlanmasını beklemeyeceği için diğerlerinden önce tamamlanacaktır. Bu durumda alacağımız çıktı;

Son olarak, operasyon yapısını daha iyi örnekleyebilmek için daha önce oluşturduğum TVSeries uygulamasından birkaç örnek göstereceğim. 🙃TVSeries, tmdb’den dizileri alarak görüntülenmesini ve bu diziler hakkında ufak bilgilere ulaşmamızı sağlayan basit bir uygulamadır. Bu uygulama içersinde operasyonlar, internetten aldığımız dizilerin afişlerinin indirilmesini sağlamaktadır.

İlk olarak işlemleri yaparken kullanacağımız ImageRecord sınıfı oluşturulmuştur. Bu sınıf, internetten aldığımız modeli, kullanıcıya göstereceğimiz UIImage’ı ve bu UIImage’ın yüklenme aşamalarını gösteren bir durum değişkeni içermektedir.

Bu model ile internetten aldığımız dizi modeli içerisinde bulunan imagePath’i, bir UIImage’a dönüştürüp kullanıcının görüntüleyebilmesini sağlayacağız.

Yukarıdaki örnekte ise, eğer seçilen index’teki model’in durumu ready ise yani model var ve yüklenmeye hazırsa, operasyon nesnesi oluşturup bu operasyon nesnesini OperationQueue yardımıyla yürütürüz. Bu yürütmenin sonucunda, ImageRecord içerisindeki model’den imagePath alınır ve bu path ile yüklenen afiş yine ImageRecord içerisindeki UIImage’a eşitlenir. Bu sayede kullandığımız ImageRecord modelinden direkt kullanacağımız UIImage’a erişebiliriz.

Projeyi ayrıntılı incelemek isterseniz aşağıdaki linkten ulaşabilirsiniz. 🙂

--

--