Property Wrapper
Property wrapper Swift 5.1 ile kullanılmaya başlayan, alt sürümlere uyumlu(backward compatible) bir özellik olarak karşımıza çıkmıştır. Bir özelliğin sahip olduğu getter/setter metotlarını kapsülleyerek projenin her yerinde benzer kodların yazılmasının önüne geçmek amacıyla gündeme gelmiş ve 2015 senesinde konuşulmaya başlandıktan sonra nihayet 0258 Proposal ile beraber duyurulmuştur. Property wrapper, özelliğin kodlanması ve saklanma şekli arasında bir katman olarak düşünülmüştür.
Property wrapper sayesinde, bir property’nin yönetimini bir kez gerçekleyip, yeniden kullanılabilir hale getirebiliriz. Property wrapper class, enum ve strut ile kullanılabilir.
Kullanım Koşulları
Property wrapper kullanırken, oluşturmak istediğiiz struct, enum veya class’ı “@propertyWrapper” özelliği ile tanımlıyoruz.
Property wrapper olarak tanımlandıktan sonra da “wrappedValue” adında zorunlu bir değişkeni barındırması gerekiyor. Bu değişken derleyicinin erişmesi gereken değeri belirliyor. Ayrıca wrapper ile aynı erişim düzeyine sahip olması gerekiyor.
Örnekler
Büyük bir projede filtreleme özelliğimizde, kod tekrarlarını ve sınıfımızda filtreli ve filtresiz iki değişken yerine tek bir değişkende tutmamızı sağlar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public protocol Filterable { var filterString : String { get } } @propertyWrapper public struct Filtered<T> where T: Filterable { public var filterString: String public var wrappedValue:[T] public var filtered:[T] { if filterString.count > 0 { return wrappedValue.filter({ $0.filterString.lowercased().range(of: filterString, options: .caseInsensitive, locale: Locale(identifier: Locale.canonicalIdentifier(from: "tr"))) != nil }) } return wrappedValue } public init(filterString : String) { self.wrappedValue = [] as! [T] self.filterString = filterString } } |
Örnekte öncelikle filtre uygulamak istediğimiz modellerin “Filterable” protokolünü uygulamasını istedik. Böylece model üzerinde hangi alanlarda filtreleme yapılması istenilen alanlar belirlenebilecek.
Wrapper’ımız filtreli ve tüm elemanlar olarak ayrılacak biçimde 2 array ve filtreleme stringi olmak üzere 3 değişkene sahip olacaktır.
Ardından wrapper’ımızın sahip olduğu “filtered” değişkenine her erişilmek istenildiğinde de var ise “filterString”e göre filtreleme yaparak filtreli array’i yoksa da ilk değeri tutan array’i (wrappedValue) dönüyor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
struct Pokemon { var name: String var attack: Double var defence: Double var species: String static func getAll() -> [Pokemon] { let bulbasaur = Pokemon(name: "Bulbasaur", attack: 49, defence: 49, species: "Seed") let charmander = Pokemon(name: "Charmander", attack: 52, defence: 43, species: "Lizard") let nidoran = Pokemon(name: "Nidoran", attack: 47, defence: 52, species: "Poison") let nidoking = Pokemon(name: "Nidoking", attack: 102, defence: 77, species: "Poison") return [bulbasaur, charmander, nidoran, nidoking] } } extension Pokemon: Filterable { var filterString: String { return (name) + (species) } } |
Öncelikle “Pokemon” adında modelimizi oluşturduk ve “Filterable” protokolüne uymasını sağladık. Burada hem name hem de species alanlarına göre filtrelenebilme özelliğini verdik.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class ViewController: UIViewController { @Filtered(filterString: "") var array: [Pokemon] @IBOutlet weak var searchTF: UITextField! { didSet { searchTF.addTarget(self, action: #selector(editingChanged), for: .editingChanged) } } @IBOutlet weak var tableView: UITableView! { didSet { tableView.register(PokemonCell.self) tableView.dataSource = self } } override func viewDidLoad() { super.viewDidLoad() array = Pokemon.getAll() } @objc func editingChanged() { _array.filterString = searchTF.text ?? "" tableView.reloadData() } } |
Ardından ViewController’ımızda wrapperı’mızın wrappedValue tipini “[Pokemon]” yani Pokemon array’ı şeklinde belirterek yarattık. Sonrasında “viewDidLoad” içerisinde wrappedValue değişkenini doldurduk. Arama alanında da her değişiklik olduğunda girilen string’i wrapper’ın “filterString” ine veriyoruz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
extension ViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return _array.filtered.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: PokemonCell = tableView.dequeueReusableCell() cell.configure(pokemon: _array.filtered[indexPath.row]) return cell } } |
Son olarak tableView’ı wrapper’ımızın “filtered” değişkenini kullanarak besliyoruz. Tekrar hatırlarsak, wrapper bize eğer aranan bir kelime var ise, wrapperValue’u filtreleyip bir array dönüyordu yoksa da wrapperValue’nun kendisini yani tüm listeyi dönüyordu. Böylece istediğimiz şekilde filtreleme işlemini gerçekleştiriyoruz.
Bu örnekte projemiz boyunca yapılabilecek tüm filtrelemerde yalnızca bir wrapper yaratıp, filtrelenmek istenen model için de “Filterable” protokolünü gerçekleyip kolayca filtreleme işlemi yapılabilecektir.