Swift Generics
Generics, Swift’in çok güçlü yanlarından biridir. Başlangıçta anlaması kolay olmasa da ve çok değişik kullanımları olsa da özellikle kod tekrarını önlemek, Framework veya SDK yazarken genele hitap edebilmek açısından kullanımı oldukça önem arz etmektedir.
Generics in Functions
Bazı fonksiyonlar birçok tipe hitap etmesi amacıyla yazılırlar. Örneğin hem Int hem de Double değerler arasında toplama işlemi olabilir. Eskiden bunu fonksiyon overload ile yapıyorduk ve temelde aynı kodu tekrar etmiş oluyorduk.
Örneğin elimizdeki dizinin ilk elemanını dönecek olan bir fonksiyon yazdığımızı düşünelim. İlk aşamada elimizde bir Int dizisi olsun ve buna uygun bir fonksiyon yazalım.
1 2 3 4 5 6 7 8 9 10 11 12 |
func getFirst(array: [Int]) -> Int? { if array.count < 1 { return nil } return array[0] } |
Proje ilerledikten sonra şimdi de bir String dizisinin ilk elemanını bize dönen bir fonksiyona ihtiyaç duyduğumuzu düşünelim. Bu kez fonksiyonumuzu aşağıdaki hale getirecektik.
1 2 3 4 5 6 7 8 9 10 11 12 |
func getFirst(array: [String]) -> String? { if array.count < 1 { return nil } return array[0] } |
Bu durum proje ilerledikçe, ihtiyaçlar ortaya çıktıkça böyle sürüp gidecekti. Fakat Generics kullanarak bunun önüne geçmemiz mümkün. Fonksiyon tanımında herhangi bir tip belirtmek yerine Generic ifade kullanıp, fonksiyonumuzu tüm tiplere açık hale getirebiliriz.
1 2 3 4 5 6 7 8 9 10 11 12 |
func getFirst<T>(array: [T]) -> T? { if array.count < 1 { return nil } return array[0] } |
Burada fonksiyon tanımlarken “< >” ifadeleri arasına yazdığımız yalnızca bir label’dır. Bu label derleme anında asıl türü olarak davranmaya başlayacaktır.
1 2 3 4 5 6 7 8 9 |
var denemeArray = [1,2,3,0,5,17,7,8,9,10,4] var denemeArray2 = ["Merhaba", "Swift", "Generics"] print(String(describing: getFirst(array: denemeArray))) print(String(describing: getFirst(array: denemeArray2))) |
Yukarıdaki şekilde iki dizi oluşturup fonksiyonumuzu çağırırsak aşağıdaki gibi bir sonuç elde ederiz.
Kendi oluşturduğumuz bir Struct’ı da bu fonksiyon içerisinde kullanabiliriz.
1 2 3 4 5 6 |
var denemeArray3 = [Deneme(name: "Merhaba", code: 1), Deneme(name: "Swift", code: 2), Deneme(name: "Generics", code: 3)] print(String(describing: getFirst(array: denemeArray3)?.name)) |
Generics Constraining
Kullanmak istediğimiz Generic yapıların belirli kurallara uymasını veya belirli protokolleri karşılamasını isteyebiliriz. Bu durumda generic tipimizin hangi kurala uymasını istediğimizi constraint kullanarak belirtebiliriz.
Örneğin bir dizi içerisindeki minimum değeri elde etmek için fonksiyon yazdığımızı düşünelim. Bu durumda minimum elemanı bulmak için karşılaştırma işlemleri yapmak zorundayız. Yani bizim tanımladığımız generic tip “Karşılaştırılabilir” olmak zorundadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func getMin<T: Comparable>(array: [T]) -> T? { if var min = array.first { for e in array { if e < min { min = e } } return min } else { return nil } } |
Yukarıdaki fonksiyonumuzun tanımında <T: Comparable> ifadesini kullanarak generic tipimizin karşılaştırılabilir olmasını kısıt olarak sunduk.
Bu durumda kendi oluşturduğumuz Struct için de “Comparable” şartını yerine getirmemiz gerekiyor. Bu işlemi bir extension yazarak gerçekleştirelim.
1 2 3 4 5 6 7 8 9 |
extension Deneme: Comparable { static func < (lhs: Deneme, rhs: Deneme) -> Bool { return (lhs.code ?? 0) < (rhs.code ?? 0) } } |