Property Wrapper Nasıl Kullanılır?

Evren Yaşar
NSAnkara
Published in
6 min readMar 15, 2020
photo by ibrahim rifath

Property wrapper SwiftUI ile birlikte göz önüne çıkan, daha önce geliştiricilerin getter/setter metodlarını ile sağladığı bir yaklaşımdı. İlk olarak 2015in sonlarına doğru Jeo Groff ve Doug Gregor’ un Property Behavior olarak isimlendirdiği yaklaşımı biz lazy ve NSCopying ile görmüştük.

Apple’ın Core takımı bu özelliği Swift 5.1 ile birlikte alt sürüm uyumlu(backward compatible) olarak Swift Evolution 0258 Proposal da tanıttı.

Yazının içeriği şu şekilde olacak;

  • Property wrapper nedir?
  • SwiftUI’ da kullanımı
  • Nasıl Kullanılır?
  • Örnekler
    - Büyük Harf
    - UserDefaults
    - Değer aralığı
    - Süre Kontrolü
  • Projected Value
  • Özet

Property Wrapper Nedir?

Bir propertynin okuma yazma işlemlerine ek özellikler, filtreler ekleyerek yeni davranışlar ekleyen generic data yapılarılarıdır. Daha önceden propertynin get set metodları ile yaptığımız işlemleri generic bir halde tek yapıda tanımlanmasıdır.

swift property wrapper

Property wrapper struct, class veya enum tanımlayarak oluşturulur. Bir property wrapper oluşturmak için nesnenin başına @propertyWrappereklenir. Daha sonra zorunlu olarak wrappedValue özelliği tanımlanmak zorundadır.

Property wrapper decoder design patterne fazlasıyla benzerlik göstermektedir. Daha fazla bilgi için;

SwiftUI’ da Kullanımı

Property wrapper SwiftUI ve Combine içinde yoğun kullanılan veri yapılarıdır. Yüzeysel olarak bu property wrapperlara göz atalım;

  • @State: Sadece tanımlandığı view içindeki kullanılan property wrapperdır. Değeri değiştiğinde viewi günceller.
  • @Binding: State tanımlandığı view içinde kullanılıyor demiştik. Eğer bu State’e tanımlandığı view değilde başka bir view den ulaşmamız gerektiğinde kullanacağımız property wrapperdır.
  • @ObservedObject: State’e çok benzemektedir. En büyük farkı state tanımlı olduğu View içinde gözlenebilirken ObservedObject tanımlandığı tüm Viewlerde çalışmaktadır. ObservedObject örneği oluşturularak ayağa kalkmaktadır ve dinlenmesi istediği sınıfa ObservableObject protokolü uygulanmak zorundadır. ViewModel katmanını tanımlerken ObservableObject, bunun dinlendiği view de tanımlandığı yerdede ObservedObject olarak düşünebilirsiniz.
  • @EnviromentObject: Tüm projede için paylaşılacak bir yapıdır. ObservedObject gibi davranır ancak View ayağa kalkarken parametre olarak EnviromentObject i kabul etmek zorundadır. View içinde örneğini oluşturmamız doğru olmaz. Coordinate içinde oluşturulup paylaşılan bir nesne olarak düşünebilirsiniz.

Yukarıdakiler haricinde daha çok property wrapper mevcut. Ancak bunlar en çok kullanılan temel veri yapılarıdır.

Yukarıda da bahsettiğimiz gibi @State ile tanımladığınız bir propertyi güncellediğinizde View otomatik olarak güncellenir. Örneğin @state var isShow = false şeklinde bir kod yazdınız. Bu propertyyi bir butonla değiştirdiniz. Butonun her tıklandığında çalışan isShow.toggle() kod ile değeri değiştirirken, siz herhangi bir şey yapmasanızda tüm View yenilenir. Bu yapıyı sağlayan wrappedValue içinde işlenmektedir.

SwiftUI frameworkü içinde yer alan @State property wrapperı şu şekildedir;

SwiftUI @State

Yukarıdaki yapıda generic property wrapper yapısı görmektesiniz. Nesnenin başına @property wrapper ön eki eklenmiş durumda. Daha öncede bahsettiğimiz gibi zorunlu olarak wrappedValue propertysi eklenmiş durumda. State üzerinde herhangi bir değer değişikliği yapıldığında wrappedValue’nun set methodu Viewi güncellemekle sorumludur.

Nasıl Kullanılır?

Daha öncede bahsettiğimiz gibi nesne tanımlamasının önüne @propertyWrapper ön ekini ekliyoruz ve o nesnenin içinde wrappedValue propertysi üzerinde logic katmanını gerçekleştiriyoruz.

Anlaşılırlığını artırmak adına geleneksek yöntem ile bir sorun çözelim. Daha sonrada property wrapperı uygulayalım.

En basit hali ile senaryomuz, uygulama içinde bir propertynin değer değiştiğinde bunu loglamak olacak. Geleneksel yöntemde şu şekilde uygulayabilirdik;

Oyunda gameScore değişikliklerini gözleyebilir hale geldik. Ancak bunu başka bir property için uygulayamayız. Oluşturduğumuz her değişken için bunu yazacak olursak kod hem okunabilir olmuyacak hemde uzadıkça uzayacak. Property wrapper burda imdadımıza yetişiyor.

Not: Odağı dağıtmamak adına generic yapmadık, sonraki örneklerde generic örnek olacak.

Yukarıdaki örnekte öncelikle Logger adında bir nesne oluşturduk, daha sonra bunu gameScore ve currentLevel a uyguladık. Hadi gelin kodu inceliyelim;

  • 1- property wrapper ekini kullanarak Logger adında bir struct oluşturduk.
  • 2- Değeri tutmak için value adında bir int oluşturduk.
  • 3- Int tipinde wrappedValue tanımladık. Bunu yapmak zorundayız. Okuma ve yazma işlemlerini üstlenecek.
  • 4- Okuma işlemlerinide geri dönecek değer
  • 5- Set edilen değeri burada yakalıyoruz. Asıl verinin manüple edildiği alan burası.
  • 6- Property Wrapper nasıl uygulandığı ile ilgili örnek. Başına @ öncekini koyarak, daha önce tanımladığımız property wrapper ı uyguladık.
  • 7- Logger wrapperını yeniden kullandık.
  • 8- Değişkenlere değerleri atadık.

En basit hali ile default değer ataması yapmadan property wrapper ı tanımladık ve kullandık. Şimdiden işe yarar örneklere geçelim.

ÖRNEKLER

Örnekleri hazırlarken sık kullanılabilecek örnekleri yazmayı tercih ettik.

1- Büyük Harf

Genellikle kullanıcı işlemlerinde girdilerin ilk karakterini büyük yapmak için kullanırız. String değerin .capitalized ile çok kolay uygulayabiliyoruz ancak kod arasında kaybolma ihtimali yüksek oluyor. Aşağıdaki şekilde temiz bir çözüm üretebiliriz.

  • 1- Gelen değerin ilk harfini büyük hale getirdik
  • 2- Default bir değer ile çalışmak istedik. Property wrapper oluşturulurken mecburen bir değer girilmek zorunda
  • 3- Default değer ile birlikte Capitalize wrapperımızı uyguladık. Direk olarak print(userName) yaparsak konsolda Name sonucuna ulaşacağız.

2- UserDefaults

Uygulamamızda küçük veriler kaydetmek için sık sık kullandığımız bir yapıdır. Bunu Property wrapper ile çok kullanışlı hale getirelim.

UserDefaults with Property Wrapper
  • 1- Tüm tiplere destek olması adına generic yapı oluşturduk.
  • 2- UserDefaults üzerinden verileri okuduk. Eğer veri daha önce kaydedilmemiş ise default değeri döndürdük
  • 3- UserDefaults üzerinden verileri kaydettik
  • 4- Default değere ve key e sahip bir property wrapper oluşturduk.
  • 5- Uygulamada ilk defa açıldığında 0 değerini döndürecek.
  • 6- Game score’ umuzu kaydettik.

Daha temiz ve okunaklı bir yapı oluşturduk. Eğer keyler ilede uğraşmak istemezseniz Storage nesnesi içine enum tanımlayarak daha kullanılabilir bir yapıya ulaşabilirsiniz.

3- Değer Aralığı

Bazı durumlarda girdilerin sınırlandırılması gerekmektedir. Bu hem geliştirici güvenliğini sağlar hemde oluşabilecek açıkları minimuma indirir.

Property wrapperda benim en çok sevdiğim ve kullandığım bir yapı. Yaş sınırlama, RGB renk kodu sınırlama, yüzdelik gibi bir sürü yerde kullanılabilir.

Yaş sınırlaması yapacağımız bir örnek oluşturalım.

Property Wrapper Range

Yukarıdaki örnekte default olarak en düşük ve en yüksek girilebilecek aralığı ayarlayarak bir örnek oluştruduk.

  • 1- Default wrapped ayarlamanın diğer yöntemi
  • 2- minimum ve maximum yaş aralığını ayarladık.
  • 3- Default veri girmenin diğer yöntemi. Property wrapper tanımlarken değilde direk olarak değer ataması yaptık. 1. yorum satırı ile kombine çalışır.
  • 4- Default değeri okuduk. 3. adımda yazdığımız 0 değerini değilde minAge değeri olan 9 u yazdırdı. Default halde de sınırlamamızı yapmış olduk.
  • 5- 16 değerini atadık, aralık dahilinde olduğu için sorunsuz kabul etti.

4- Süre Kontrolü

Kullanıcı girişleri için sakladığımız access token, json dosyaları vs… için süreye ihtiyaç duyarız. Belirli bir süre sonra sakladığımız verinin geçerliliğini yitirmesini isteriz.

Aşağıdaki örnekte belirlediğimiz süreden sonra değerimizi nil yapan güzel bir örneği inceleyeceğiz.

  • 1- zamanın geçerli olup olmadını kontrol eder.
  • 2- Eğer zaman geçerli ise mevcut değeri, değilse nil değerini döndürür.
  • 3- Şu anki zamana expired için geçerli zamanı ekler ve unit değişkenine atar. Tutulası istenen asıl değeri(örnekte token) saklar.
  • 4- Değerin kaç saniye aktif kalacağını default olarak belirler
  • 5- 2 Saniyelik geçerliliği olan wrapper tanımladık. Bu wrapper tokenı tutacak.
  • 6- Rastgele token değeri atadık. print sonucunda atadığımız değeri okumuş olduk.
  • 7- 5 sn bekledikten sonra tokenı tekrar okumaya çalıştığımızda sonuç nil döndü.

Yukarıdaki örneği daha geçerli kılmak için CoreData veya UserDefaults üzerde kaydedebilirsiniz.

Projected Values

Property wrapperı daha kullanılabilir hale getirmek için kullanılır. Herhangi bir tip ataması yapabileceğiniz özgür bir propertydir. Aynı zamanda oluştulan property wrapper ile bağlantıyıda sağlar.

Bir üstteki örnekte belirlediğimiz süre sonunda atadığımız değere ulaşamıyorduk. Süre dolduğunda yapı bize otomatik olarak nil değerini döndürüyor. Projected Value ile property wrapper ın özelliklerine ulaşarak atadığımız eski değere ulaşabiliriz.

  • 1- Oluşturulan wrapper döndürecek projected value tanımladık.
  • 2- Normalde token nil dönüyor ancak wrapper ın içindeki value değeri silinmedi. Projected Value sayesinde wrapperın içindeki value değişkenine ulaşmış olduk.

Projected value tanımlamasına ulaşabilmek için değikenin başına $ ön ekini eklemeniz gerekiyor. Bu sayede token string özelliklerinden ayrılıp property wrapper özelliklerine bürünüyor.

Özet

Property wrapper geliştiricilerin işlerini rahatlatıcak bir yaklaşım. Yazıda tanımlamasını yapıp, SwiftUI da nasıl kullanıldığına baktık en sonrada örnek kullanımları detaylı şekilde inceledik. Bu örnek kodları her seviyeden geliştiricinin anlaşılması için uzun uzun yazdık.

Kaynaklar

--

--