RxSwift’e Küçük Bir Başlangıç

Ayşe Nur Bakırcı
Delivery Hero Tech Hub
10 min readNov 25, 2021

Merhaba. Burada sizlere RxSwift çalışırken aldığım notlardan herkesin yararlanabileceği ufak bir yol haritası çıkarmaya çalışacağım.

Öncelikle RxSwift en kısa tanımıyla gözlemlenebilir dizileri (observable) kullanarak asenkron kod yazmamızı sağlayan bir yapıdır. Peki bu yapıyı neden kullanmalıyız? Öncelikle senkron bir kod akışını ele aldığımızda her işlevin sırayla yapıldığını görüyoruz. Elimizde farklı işlemleri, farklı iş parçaları üzerinde aynı anda yürütebileceğimiz bir yapı varken yapacağımız işleri sırayla yapmak istemeyebiliriz. İşte burada RxSwift kullanmaya başlayabiliriz. :)

Böyle kullanışlı bir yapının elbette ki zorlukları olacaktır. RxSwift kullanırken birden çok eşzamanlı ilerleyen bileşeni takip edebilmeli, kodların doğru sırayla çağırdığımızdan da emin olmalıyız.

RxSwift

RxSwift içersinde, kullandığımız üç temel yapı vardır.

1. Observable

Değişiklikleri yayan yapılardır. Biz bir gözlemlenebilir dizi (observable) oluşturduğumuzda, aslında değişikliklerini takip edebileceğimiz asenkron bir dizi oluşturmuş oluruz. Takip ettiğimiz bu diziye her yeni öge geldiğinde bazı işlemleri gerçekleştirebiliriz. Bir observable 4 farklı olay yayabilir.

  • onNext: Bir sonraki olayı temsil eder. Diziye eklenen her yeni ögeyi onNext ile takip edebiliriz.
  • onCompleted: Observable başarılı bir şekilde tamamlandığında bu olay gösterilir. Tamamlanmış bir observable yeni olaylar yaymaz.
  • onError: Observable bir hata ile karşılaştığında bu olay gösterilir. Hata alan bir observable yeni olaylar yaymaz.
  • onDisposed: Observable tamamlanıp hafızadan atılacağı an oluşan olaydır.

Olayları takip ederken sonlu veya sonsuz olacak şekilde iki farklı observable kullanılır.

Sonlu observable bir süre yeni olay yaydıktan sonra onError veya onCompleted ile sonlanan yapıdır. Burada örnek olarak internetten dosya indiren bir observable düşünebiliriz. Dosya indirilirken her öge onNext ile alınır. Eğer herhangi bir hata ile karşılaşırsak onError durumu, indirme hata almadan devam eder ve dosyanın indirmesi başarılı bir şekilde biterse onComplete durumu ile observable sonlanır.

Sonsuz observable ise uygulamanın çalıştığı süre boyunca olay almaya devam eden yapıdır. Burada cihaz yönü değişikliğini takip eden bir observable örnek olabilir. Cihaz yönü değişiklikleri uygulama çalıştığı süre boyunca devam edebileceği için bu observable uygulama kapanana kadar sonlanmamalıdır.

Observable Oluşturmak;

Bir observable oluşturmak için üç farklı operatör kullanılabilir.

  • Just operatörü ile tek bir öge içeren observable oluşturulabilir.
  • Of operatörünü kullanarak birden fazla öge içeren observable oluşturabiliriz. Burada eklenen ögelerin hepsi aynı tipte olmalıdır.
  • From operatörünü kullanarak dizi içeren observable oluşturabiliriz. Oluşturulan observable dizinin içindeki her elemanın farklı ögeler olarak tutacaktır.

Observable’a Subscribe Olmak;

Oluşturulan bir observable olayları yayabilmek için bir subscriber’a ihtiyaç duyar (Kim takip edilmeyecek ögeleri yaymak ister ki :) ). Subscribe kullanarak observable’ın yeni olaylar yaymasını sağlayabilir, observable’ın yaydığı durumları ve ögeleri takip edebiliriz.

Yukarıdaki örneklerdeki gibi farklı yöntemlerle olaylara ve olaylar içerisindeki ögelere ulaşabiliriz.

Observable’lar yalnızca ögeleri yaymak zorunda değildir. Ögeler olmadan boş veya hiçbir şey yaymayan observable oluşturabiliriz.

Empty ile oluşturulan observable bize yalnızca onCompleted durumunu döndürecektir. Never ile oluşturulan observable ise hiçbir durum yaymayan ve sonlandırılmayan bir observable’dır. Sonsuz süreyi temsil etmek için kullanılabilir.

NOT: Yukarıdaki kod örneklerinde belki dikkatinizi çekmiştir. Subscribe sonrasında kullandığımız dispose yapısı mevcut. Bu yapı sayesinde artık kullanmayacağımız observable’ları hafızadan atabiliriz. Observable’lar ile çalışırken bunu kullanmayı unutmamalısınız, hafızadan atılmayan her observable uygulamanın performansını kötü etkileyecektir.

— Subject

Subject’ler hem observable hem de observer olarak kullanabileceğimiz yapılardır. Farklı durumlara göre kullanabileceğimiz farklı türleri mevcuttur.

  • Publish Subject: Boş bir şekilde başlar. Her yeni subscribe yalnızca abone olduktan sonra yayılan ögeleri alabilir.
  • Behavior Subject: Bir başlangıç değeriyle başlar. Her yeni subscribe abone olmadan önce yayılan son ögeyi (yayılan öge yoksa başlangıç değerini) ve devamında gelen ögeleri alır.

2. Operatör

Observable’ı kullanırken bazı olayları kısıtlamak veya yalnızda işimize yarayacak ögeleri filtrelemek isteyebiliriz. Bunun gibi durumlarda observable ile birlikle kullanabileceğimiz birçok operatör vardır.

  • ignoreElements(): Tüm ögelerin filtrelenmesi için kullanılır. Yalnızca onError veya onComplete durumlarını takip etmemiz gerektiğinde bu operatörü kullanabiliriz.
  • elementAt(): ElementAt operatörü parametre olarak bir sayı alır ve takip ettiğimiz gözlemlenebilir dizide yalnızca verilen indexte bulunan ögeyi almamızı sağlar.
  • filter(): Gelen dizideki ögeleri filter operatörü içerisine yazığımız koşula göre filtreleyebilmemizi sağlar. Takip ettiğimiz observable’dan yalnızca koşula uyan ögeleri alabiliriz.
  • skip(): Parametre olarak sayı alır. Observable’dan gelen ögelerden verilen sayı kadarını atlayarak ögeleri almamızı sağlar.
  • skipWhile(): Takip ettiğimiz observable’da, operatör içerisine yazılan koşula uyan ilk öge atlanır ve diğer ögeler koşula uysa da alınmaya devam eder. Yani bu yapı filter gibi uzun ömürlü değildir, yalnızca koşula uyan ilk öge için geçerlidir.
  • skipUntil(): Parametresi bir observable’dır. Operatöre verilen observable’ı trigger olarak adlandırdığımızı düşünelim. Takip edeceğimiz gözlemlenebilir dizinin ögelerini yalnızca trigger yeni bir durum aldığına takip etmeye başlayabiliriz.
  • take(): Parametre olarak sayı alır. Takip ettiğimiz observable’dan verilen sayı kadar öge almamızı sağlar.
  • takeUntil(): Parametresi bir observable’dır. Operatöre verilen observable’ı trigger olarak adlandırdığımızı düşünelim. Takip edeceğimiz gözlemlenebilir dizinin ögelerini yalnızca trigger’a yeni bir öge gelene kadar takip edebiliriz.
  • distinctUntilChange(): Üst üste gelen aynı ögeleri filtrelemek için kullanabiliriz.
  • toArray(): Gözlemlenebilir diziden gelen tüm ögeleri bir dizi içerisine toplayarak ögeleri dizi olarak almamızı sağlar.
  • map(): Dizi ögeleri üzerinde yapmak istediğimiz tüm değişiklikleri uygulamamızı sağlayan yapıdır.
  • mapWithIndex(): Map operatörü ile aynı işleve sahiptir. Tek fark ögeyle birlikte ögenin indexini de alarak işlem yapabilmemizdir.
  • flatMap(): Observable’ın her ögesinin değişimlerini ayrı ayrı takip edip sonrasında bu ögeleri birleştirerek tek bir observable oluşturan operatördür.
  • compactMap(): Diğer map işlemleri gibi observable’dan gelen ögeler için işlemler uygular. Bu yapının farkı gelen ögeler optional ise içerisini kontrol eder, eğer bir değer yoksa boş ögeyi diziden atar, varsa ögeyi optional olmaktan çıkartır ve bu ögelerle bir observable oluşturur.
  • startWith(): Gelen dizinin ilk indeksine eleman eklemek için kullanılır.
  • concat(): İki gözlemlenebilir diziyi birleştirmek için kullanılır. İlk dizinin ögeleri tamamlandıktan sonra ikinci dizinin ögeleri alınmaya başlanır.
  • merge(): İki gözlemlenebilir diziyi birleştirmek için kullanılır. İki diziden de gelen ögeleri aynı anda takip ederek tek bir gözlemlenebilir dizi olarak kullanabilmemizi sağlar.
  • combineLatest(): İki diziden gelen ögeleri aynı anda takip ederek yeni bir observable oluşturur. Bu yapıyı birden çok observable’ı birlikte izlememiz gerektiğinde kullanabiliriz.
  • zip(): combineLatest ile aynı görevde kullanılır ancak zip iki observable da öge yaymadan yeni bir öge yaymaz. Aşağıdaki örnekte sunny ögesinin kullanılabilmesi için right dizisine de yeni bir öge gelmelidir.
  • withLatestFrom(): Operatöre verilen observable öge yaymaya devam ederken, kaynak observable’a herhangi bir öge geldiğinde, iki observable’ın da son değeri alınarak bir observable oluşturulur. Burada yalnızca bir observable’ı takip edebildiğimiz gibi ikisini beraber de takip edebiliriz.
  • sample(): Burada kaynak observable değer yaymaya devam eder. Sample operatörüne parametre olarak verilen observable her yeni öge yaydığında, kaynak observable’ın yaydığı son öge alınır ve bir observable oluşturulur.
  • amb(): Amb operatörüne verilen observable’lardan hangisi önce olay yaymaya başlarsa o observable’a subscribe olunur. Diğer observable’ların yaydığı olaylar artık takip edilmez.
  • switchLatest(): Bu operatör karmaşık olduğu için bir örnek vererek açıklayacağım. Öncelikle elimizde 2 adet Int tipinde observable olduğunu düşünelim. Burada trigger olarak kullanacağımız observable’ın tipinin de Int tipinde bir observable olarak belirlenmesi gerekiyor. Trigger olarak belirlediğimiz observable’a switchLatest operatörünü ekleyip ögelerimizi almak için neler yapacağımıza bakabiliriz. Burada başta belirlediğimiz observable’lardan hangisinden öge almak istiyorsak, trigger olarak belirlediğimiz observable’a bu observable’ı yeni bir öge olarak göndermemiz gerekiyor. Yani kısaca biz trigger’a hangi observable’ı onNext olarak verirsek, o observable’ın değerlerini almaya devam ediyoruz.
  • reduce(): Operatör içersinde verilen işlemi ilk ögeden başlayıp, işlem sonucunu bir sonraki ögeye dahil ederek tüm ögelere uygular. Observable tamamlandığında toplam sonuç yayınlanır.
  • scan(): Operatör içersinde verilen işlemi ilk ögeden başlayıp, işlem sonucunu bir sonraki ögeye dahil ederek tüm ögelere uygular. Her işlemin sonucu yayınlanır.
  • replay(): Parametre olarak sayı alır. Observable’ın yaydığı son ögelerin kaydedilmesini ve yeni subscriber’lara yayılmasını sağlar. Örneğin parametre olarak 3 verdiğimizi düşünelim. Bu observable’ın yaydığı son 3 öge kaydedilir ve observable tarafından her yeni subscriber’a ilk olarak bu 3 öge yayılır.
  • replayAll(): Replay ile aynı işlevde kullanılır, observable’ın yaydığı tüm ögeleri kaydetmek istersek replayAll kullanabiliriz.
  • delaySubscription(): Bir observer’ın ögeleri almaya başladığı zaman geciktirilir. Burada observable beklemez ve öge yaymaya devam eder. Observer observable’ın yaydığı ögeleri nerede yakalarsa oradan almaya başlar ve devam eder.
  • delay(): Bir observable’ın öge yayma süresi geciktirilir. Burada verilen süre geçtikten sonra observable ögelerini yaymaya başlar.
  • timeOut(): Belirtilen zaman dilimi içerisinde observable yeni öge yaymazsa observable onError durumu ile sonlandırılır.
  • share(): Bir observable’a birden fazla abonelik oluşturmak istediğimizde, aboneliklerimiz observable’ın farklı zamanlarında oluşabilir ve her abonelikte aynı sonucu alamayabiliriz. Bu sorunun oluşmaması için share operatörü kullanılır. Share operatörü ile paylaşılan observable’a yapılan her abonelik aynı sonucu almamızı sağlar.
  • retry(): Observable herhangi bir hata ile karşılaşırsa işlemin tekrar yapılmasını sağlar. Parametre olarak verilen sayı kadar veya sonsuz sayıda deneme yapılabilir.
  • catch(): Observable herhangi bir hata ile karşılaşırsa varsayılan bir değer verilerek observable’ın hata ile sonlanması engellenir.
  • do(): Observable’ın yaşam döngüsünü kontrol etmemizi sağlar. Subscribe gibi onNext, onComplete ve onError durumlarını içerir ve herangi bir durumda bazı işlemleri gerçekleştirebiliriz. Ancak subscribe gibi observable’ın yeni olaylar yaymasını sağlamaz.
  • subscribe(): Observable’ın yeni olaylar yaymasını ve bu olayların sonucunda işlem yapmamızı sağlar. Bir observable subscribe olmadan yeni olaylar yaymaz.
  • dispose(): Tamamlanan observable’ın hafızadan atılmasını sağlar.

3. Scheduler

Yazının başında da belirttiğim gibi, RxSwift kullanırken zamanlayıcıları doğru kullanmak da çok önemlidir. Zamanlayıcılar yazdığımız asenkron kodun çalıştığı iş parçacığını temsil eder. Eğer bir observable’ın hangi zamanlayıcı üzerinde çalışacağını belirtmezsek, aboneliğin oluşturulduğu zamanlayıcının üzerinde çalışmaya devam edecektir. Bu her zaman bizim için iyi bir durum olmayabilir, bu yüzden özellikle belli zamanlayıcılarda çalışması gereken kodların hangi zamanlayıcıda çalışacağını kendimiz belirlemeliyiz. Burada en başta dikkat etmemiz gereken nokta UI işlemleri ve UI’ı etkileyen işlemler main thread’de, JSON işlemleri gibi uzun süren işlemlerin de arka planda bir thread’de çalıştırılmasıdır.

RxSwift’te kullanabileceğimiz 4 farklı scheduler(zamanlayıcı) vardır:

  • MainScheduler: UI işlemleri veya yüksek öncelikli işlemler için bu zamanlayıcıyı kullanabiliriz. Uzun süreli işlemleri bu zamanlayıcı üzerinde gerçekleştirmemeliyiz. Örneğin main scheduler üzerinde yaklaşık 5 saniye süren bir olay gerçekleştirdiğimizde bu kullanıcı arayüzünün 5 saniye boyunca kullanılamamasına sebep olacaktır.
  • SerialDispatchQueueScheduler: Arka planda gerçekleşen eşzamanlı işlemlerin seri olarak ilerlemesini istediğimizde bu zamanlayıcıyı kullanabiliriz.
  • ConcurrentDispatchQueueScheduler: Arkaplanda birden çok ve uzun süreli işlemleri aynı anda yapmamız gerektiğinde bu zamanlayıcıyı kullanabiliriz.
  • OperationQueueScheduler: Arka planda ilerleyen işlemler üzerinde daha fazla kontrole sahip olmamızı sağlar.

NOT: MainScheduler ve SerialDispatchQueueScheduler seri ilerleyen, ConcurrentDispatchQueueScheduler ve OperationQueueScheduler eşzamanlı ilerleyen zamanlayıcılardır.

Bu zamanlayıcıları oluşturduğumuz observable ile kullanmak için 2 farklı operatör mevcuttur. Bu operatörlerin nasıl kullanılacağını anlamak için öncelikle bir observable’ın yapısını incelemeliyiz.

Oluşturduğumuz yapıda aboneliğin oluşturulduğu ve ögelerin yayıldığı kısım subscription, yayılan olayları takip edip işlemler gerçekleştirdiğimiz kısım ise observing kısmıdır. Yukarıdaki kodların main scheduler üzerinde ilerlediğini ele alalım.

  • subscribe(on:_): Subscribe on oluşturduğumuz observable’ın subscription ve observing kısımlarının çalıştığı zamanlayıcıyı değiştirmemizi sağlar. Main scheduler üzerinde ilerlemeye başlayan kodlarımızı subscribe on ile arkaplanda çalışan bir scheduler üzerinde ilerlemesini sağlayabiliriz.
  • observe(on:_): Subscribe on yapısı tüm yapının ilerlediği zamanlayıcıyı değiştirirken observe on yalnızca observing kısmını etkiler. Yukarıdaki örnekte main kısmı da background bir zamanlayıcıya geçmişti. Ancak observing kısmında herhangi bir UI işlemi varsa bunu main scheduler üzerinde geri alabilmeliyiz. Bu durumda observe on kullanarak bunu gerçekleştirebiliriz.

Bunların sonucunda umarım RxSwift konusunda fikir sahibi olmanızı sağlamışımdır. Bir sonraki yazımda da RxCocoa’dan bahsettim. Okumak isterseniz buradan ulaşabilirsiniz.

Okuduğunuz için teşekkür ederim, herhangi bir fikriniz veya yorumunuz olursa benimle paylaşabilirsiniz. 🙂

--

--