Reactive Programming — RxSwift
Öncelikle Reactive programlama için Wikipedia tanıma bakacak olursak;
Reactive programlama, veri akışları (data streams) ve değişikliklerin sisteme yayılması (propagation of change) ile ilgilenen declarative bir programlama paradigmasıdır. Bu, kullanılan programlama dillerinde statik veya dinamik veri akışlarını kolaylıkla ifade etmenin mümkün olacağı ve temeldeki yürütme modelinin veri akışı üzerinden değişiklikleri otomatik olarak yayacağı anlamına gelir.
Reactive programlama, “değişikliklere tepki verme” temeline dayalı bir yöntemdir. Günlük yaşamımızdan ve insanlar arası iletişimimizden ilham alıyor diyebiliriz.
Günlük yaşantımızda, genellikle çoklu görevler yapmaya çalışırız ve bunun için bunları bölümlere ayırırız. Örnek verecek olursak, bir kahve almak için sipariş vermeniz, hazırlanmasını beklemeniz ve ardından içeceğinizi almanız gerekir. Büyük olasılıkla beklerken başka bir şey yapacaksınız örneğin bir telefon görüşmesi gerçekleştireceksiniz. Bu, bir görevi reactive olarak yürütmenin en basit şeklidir. Baristadan bir “tepki” (siparişiniz hazır olduğunda seslenmek gibi) beklerken başka bir şey yaparsınız. İşte buna asenkron çalışma denilmektedir.
Asenkron bir işlem yürüten kod satırı çalıştırıldıktan sonra, işlemin bitmesi beklenmeden diğer satırdaki işlemler yürütülebilmektedir.
Reactive programlamada bir işlemin yürütülebilmesi için başka bir işlemin sonucu beklenmek zorunda değildir. Olaylar zaman içinde bir sırada gerçekleştirilir. Bu olay dizisine “akış” (stream) denilmektedir.
Programlamada asenkron çalışma ihtiyacı, uygulamaları daha duyarlı hale getirmek amacıyla kullanılır. Asenkron programlama ile uzun süren işlemler gerçekleştirilirken aynı zamanda diğer görevlere devam edebiliriz. Böylece ana uygulama bir görevi yürütürken, kilitlenme ve donma gibi yavaşlamaları kullanıcıya yansıtmadan, sorunsuz bir kullanıcı deneyimi sunmayı sağlar. Uzun süren bir işlem farklı bir iş parçacığında yapılır ve tamamlandığında ana iş parçacığına bildirilir.
Imperative vs. Reactive Programming
Imperative programlamada bir program, açık olarak tanımlanmış talimatlar dizisinden oluşur. Örneğin bir robota komut verdiğimizi düşünelim:
- 2 metre yürü
- Kapının kolunu tut
- Kapının kolunu 20 derece sağa çevir
Burada her bir prosedür alt prosedürlerden, o prosedüre ait detaylardan oluşmaktadır. Örneğin robotun nasıl bir açıyla vücudunu tutacağı, kolun hangi mafsalının kaç derecelik açı yapacağı ve kolun nasıl hareket edeceği gibi prosedürler bu alt prosedürlerde belirtilir.
Reactive programlamada ise önemli olan olaylardır. Burada bilgisayara satır satır ne yapacağını vermek yerine, bir olay olduğunda uygulamanın nasıl davranması gerektiğini söyleriz. Burada tasarım, programın akış sürecinde olası tepkileri anında ya da sürecin herhangi bir t noktasında işleyebilecek şekilde yapılır. Adım adım kod takip etmek yerine olay takip edilir.
Imperative programlamada nasıl sorusuna cevap olacak komutlar ön plana çıkarken reactive programlamada ne sorusuna cevap olacak komutlar ön plana çıkar. Imperative programlamada hedef komutların doğru şekilde ve dizilimde verilmesidir. Imperative programlamadaki her fonksiyon belirli, bir başka emirin alt parçalarıdır ve sürekli olarak programcının emir vermesi gerekir. Reactive programlama yaklaşımında yazılan her fonksiyon birer amaca hizmet eder. Kısacası imperative programlamada robotumuzun her işlemini bir emre bağlarken, reactive programlamada belirli olaylara karşı vereceği tepkileri komutlandırırız.
Bu örnekte a değişkeninin değerini 2 olarak değiştirdiğimizde c değişkeninin değeri 3 olarak kalır, ancak reactive dünyada işler farklıdır ve c değeri a’ya bağlıdır ve a değerini 2 olarak değiştirdiğimizde c değeri de otomatik olarak 4 olur.
Reactive programlamayı Swift’te Key/Value Observing (KVO) ve didSet kullanarak elde edebiliriz, ancak bu hantal bir çözüm olabilir.
Alternatif olarak, Swift’te çeşitli frameworkler bulunmaktadır. Bu yazıda da RxSwift’i ele alacağız.
RxSwift
RxSwift, iOS için geliştirilmiş reactive programlama kütüphanesidir. Veri değişikliklerine ve kullanıcı olaylarına yanıt veren dinamik uygulamaları programlamayı kolaylaştırır. RxSwift’i 4 madde ile ele alacağız;
- Observables & Observers
- Subjects
- Operators
- Schedulers
Observables & Observers
Observable ve Observer iki temel kavramdır.
Observable (gözlemlenebilir): değişiklikleri yayınlar. Tedarikçi olarak düşünülebilir. Verileri diğer bileşenler için işler ve bu bileşenlere verileri sağlar.
Observer (gözlemleyici): Bir observable’a abone olur ve observable değiştiğinde bildirim alır.
Bir observable’ı dinleyen birden fazla observer olabilir. Observable değiştiğinde, tüm observerları bilgilendirir. Veri akışındaki değişiklikleri okuyabilmek için observable değerlerimize abone olmalıyız (subscribe).
Buradaki next()
fonksiyonu, kendisine abone olan observerlar için bir veri sunar. complete()
fonksiyonu ise artık veri akışının sonlandığını bildirir ve aynı return
deyimi gibi kendinden sonra gelecek ifadeleri çalıştırmadan fonksiyondan çıkar. Her bir observer’ın ise next
, error
ve complete
fonksiyonları bulunabilir:
- .next(value: T): Verinin alınıp üzerinde işlem yapılmasını sağlar.
- .error(error: Error): Bir hata oluştuğunda bildirilmesini sağlar.
- complete: Veri akışının sonlandığını bildirir.
Dispose Bag
DisposeBag, RxSwift’in ARC ve bellek yönetimi ile başa çıkmasına yardımcı olan ek bir araçtır. DisposeBag’i tutan nesne üzerinde deinit() çağrıldığında, observer’lardan da düzgün bir şekilde kurtulmak için DisposeBag aracını kullanırız. Bu, ARC’nin belleği normalde olduğu gibi geri almasını sağlar.
Subject
Subject, Observable’ın özel bir şeklidir, abone olabilir ve dinamik olarak ona eleman ekleyebilirsiniz. Normalde observable.subscribe(observer) metodu ile yalnızca bir observer, bir adet observable’a abone olmaktadır (unicast). Birden fazla observer’ın bir observer’a abone olması (multicast) için subject kullanılır.
PublishSubject: Bir observer abone olduktan sonra gerçekleşecek tüm olayları alır.
BehaviorSubject: Tüm abonelere yeni olayları, yeni abonelere en yeni (veya başlangıç değerinden itibaren) değeri yayınlar.
ReplaySubject: İlk abonelikte yeni abonelere en son ögeden daha fazlasını yeniden yayınlamak istiyorsanız, bir ReplaySubject kullanmanız gerekir. Bir ReplaySubject ile yeni abonelere en son kaç tane öge yayınlamak istediğinizi tanımlayabilirsiniz.
Variable: Bir BehaviorSubject ögesini kapsar, böylece yeni abonelere en son (veya ilk) değeri yayar. Variable ayrıca geçerli değer durumunu da korur. Değişken asla bir error yayınlamaz. Ancak, otomatik olarak bir completed yayınlar ve deinit() çağırıldığında sonlanır.
Variable RxSwift 5.0 versiyonu ile kullanımdan kaldırılmıştır ve bu tür bir davranışa ihtiyacınız varsa önerilen yaklaşım bunun yerine BehaviorRelay kullanmaktır.
PublishSubject: 0
BehaviourSubject & Variable: 1
ReplaySubject: N
Şimdi PublishSubject’in davranışına bir bakalım.
onNext() işlevini kullanarak publishSubject’e yeni değerler ekleyebiliriz. onCompleted() diziyi tamamlar ve onError(error) bir hata olayı yayınlamayı sağlar.
publishSubject’e onNext() kullanarak “Hello” ve “World” değerlerini ekledikten sonra abone olan bir observer, bu iki değeri alamaz. BehaviourSubject kullanıyor olsaydık, en son yayınlanan “World” yeni abone olan observerlar tarafından alınabilecekti.
Şimdi aşağıdaki örneği inceleyelim.
Örnekte basit olarak, bir email ve password ile login işlemi gerçekleştirilmektedir. Bunun için ilk olarak kullanıcının girdiği email ve password bilgilerini bir değişkende tutmamız gerektiğinden LoginViewModel içerisinde “email” ve “password” adında BehaviorRelay subject oluşturuyoruz. Bu iki değişkende veri tutacağımız için Variable subject gibi çalışan bir subjecte ihtiyacımız var. Fakat daha önce de bahsettiğim gibi RxSwift 5 ile birlikte Variable’ın yerine BehaviorRelay kullanılması önerildiği için burada BehaviorRelay tercih ediyoruz.
Login işleminin başarılı ya da başarız olma durumu için “isSuccess”, loading durumu için “isLoading” ve son olarak error durumu için de “errorMsg” subjectlerini oluşturuyoruz. Bir olayı ya da durumu izleyeceğimizden dolayı PublishSubject kullanıyoruz. Ardından result değişkeninin değerini kontrol ediyoruz ve “isLoading”e false değerini, “isSuccess”e de result değişkeninin değerini aktarıyoruz. Böylece isSuccess değerine abone olmuş olan observerlara bu değeri yayınlayabiliyoruz.
LoginViewController’a gelecek olursak, ilk olarak observerlarımızı ayarlıyoruz. txtFieldEmail ve txtFieldPassword textFieldlarının textlerinde herhangi bir değişiklik olduğunda bu değerleri “txtEmail” ve “txtPassword” değişkenlerine aktarıyoruz. Ardından bu değişkenlerin değerlerinde meydana gelen herhangi bir değişiklikte ilgili değerleri LoginViewModel içerisindeki “email” ve “password” observable subjectlere .bind(to: ) ile aktarıyoruz. Son olarak da loginButton’a tıklandığında LoginViewModel içerisindeki validateCredentials fonksiyonu ile kullanıcının girmiş olduğu email ve password bilgilerinin uygunluğunu kontrol ediyoruz, uygun ise loginUser fonksiyonunu çağırıyoruz.
loginUser fonksiyonu içerisinde ilk olarak isLoading’i true yapıyoruz. “result” değişkenine kayıtlı kullanıcı bilgisi ile girilen kullanıcı bilgisini karşılaştırarak sonucu aktarıyoruz.
LoginViewController içerisindeki createCallbacks fonksiyonunda “isSuccess” değerinin observer’ında bu değeri kontrol ederek login işleminin başarılı ya da başarısız olma durumunu kontrol ediyoruz.
Örneğimizin ardından, bu yazının sonuna gelmiş bulunuyoruz.
Özetle; reactive programlama ve RxSwift’e bir giriş yaparak, yapılarını ve nasıl kullanabileceğimizi incelemiş olduk. RxSwift’te Operators ve Schedulers konu başlıklarını bir sonraki yazıda ele alacağız.
Diğer yazılarda görüşmek üzere.
References