Swift Generics

13 minute read

Swift Generics

Bu yazımda şu konuları anlatmaya çalışacağım:

  • Generic nedir?
  • Genericlere neden ihtiyaç var?
  • Generic bir fonksiyon tanımı nasıl yapılır?
  • Genericler sadece fonksiyonlarla mı tanımlanabilir?
  • Swift Generic Type Constraints
  • Generic Protocol Kavramı
  • Generic protocol nasıl oluştururuz?

Generic nedir?

  • Türden bağımsız işlem yapabilmeyi sağlayan mekanizmadır.
  • C++ dilinde template olarak geçmektedir. Java ve C# dillerinde generic olarak eklenmiştir.
  • Swift dilinde ki generic yapılar daha fazla C++ dilinde ki template mekanizma işleyişine benzemektedir.

Genericlere neden ihtiyaç duyuyoruz?

  • Bazı senaryolarda farklı türler için aynı işi yapan birden fazla fonksiyon yada metodun yazılması gerekebilir. Tüm bu fonksiyonları tek tek yazmamak için biz generic mekanizmasını kullanıp bir tane şablon yazarız ve derleyici bizim yerimize türden bağımsız, diğer türler için gerekli işlemleri yapar.

Senaryo: Parametre olarak liste alan bir fonksiyon geriye listenin en büyük elemanını döndürsün.

 1func getMax(dizi items: [Int]) -> Int
 2{
 3    var max = items[0]
 4
 5    for number in items {
 6        if max < number{
 7            max = number
 8        }
 9    }
10    return max
11}
12
13let sayiDizisi = [97, 34, 1, 129, 444]
14getMax(dizi: sayiDizisi)    // 444
15let sayiDizisi2 = [97.0, 34.34, 1.12, 129.33, 97.34, 129.34]
16getMax(dizi: sayiDizisi2)  // HATA
17// Cannot convert value of type '[Double]' to expected argument type '[Int]'
  • Yazdığımız fonksiyon Int dizisi için çalışıyor ama bizim senaryomuzda böyle bir kısıtlama yok. Yani Double dizide gönderseler sonucu doğru şekilde görmeliyiz. Swift dilinde bunu yapabilmek için Double içinde ayrı bir fonksiyon yazmalıyız. İşte burada her tür için fonksiyon yazmak yerine GENERIC konusu gelip bizi kurtarıyor. Ve bu kısmı SWIFT COMPILER bizim için hallediyor.

Generic bir fonksiyon tanımı nasıl yapılır?

  • Swift dilinde generic fonksiyon tanımı, fonksiyon isminden sonra <> gibi açısal parantezler arasında tür parametrelerinin bildirilmesiyle yapılır. Örneğin
1func genericExam<T, K, M>(tmp1: T, tmp2: K, tmp3: M) -> T
2{
3  // KODLAR
4}

Yukarıda ki kodda gördüğümüz T, K, M herhangi bir tür parametresi. Buraya istediğiniz harflendirmeyi yapabilirsiniz. Genel olarak bu isimlendirmelerde tek harf kullanılır ama bu bir kural değil tercihtir.

Senaryo: Bir swap fonksiyonunu generic kullanarak yazalım.

 1func swap<T>(item1: inout T, item2: inout T)
 2{
 3    let tmp = item1
 4    item1 = item2
 5    item2 = tmp
 6}
 7
 8var name: String = "Kerem", surname: String = "Vatandas"
 9swap(&name, &surname)
10print("isim: \(name), soyisim: \(surname)") // "isim: Vatandas, soyisim: Kerem\n"
11var no1: Int = 99, no2: Int = 33
12swap(&no1, &no2)
13print("no1: \(no1), no2: \(no1)")  // "no1: 33, no2: 33\n"
14
15var dNo1: Double = 12.3, dNo2: Double = 20.5
16swap(&dNo1, &dNo2)
17print("dNo1 = \(dNo1), dNo2 = \(dNo2)")  // "dNo1 = 20.5, dNo2 = 12.3\n"

Sonuç: Swap fonksiyonunu tanımlarken T herhangi bir tür oluyor. Burada derleyici argümanlara bakarak türü otomatik tespit ediyor. Örneğin fonksiyon çağırılırken parametreler string olduğu için derleyici T türünün String olduğunu anlıyor.

  • Swift dilinde C++ dilinde olduğu gibi generic türlerin açıkça belirtilmesi özelliği yoktur. Bu nedenle swift dilinde tüm generic tür parametrelerinin fonksiyon imzası içerisinde kullanılması zorunludur. Söylemek istediğimi kod üzerinde göstereyim.
1func exam1<T>(){}   // HATA
2func exam2<Double>()   // C++ bu ozellik var, Swift dilinde yok

Genericler sadece fonksiyonlarla mı tanımlanabilir?

  • Tabi ki hayır. Generic class, struct, enum tanımlanabilir. Önemli nokta swift dilinde prokoller GENERİC OLAMAZ.
  • Generic sınıf, yapı, enum tanımlamak, fonksiyon tanımlamakla aynı yapıyı kullanıyor. Örneğin
1class AlgoList<T>
2{
3   // KODLAR
4}

Senaryo: Bir stack veri yapısını generic mekanizması kullanarak yazalım.

 1struct Stack<T> {
 2    private var stack: [T]
 3
 4    init()
 5    {
 6        stack = [T]()
 7    }
 8
 9    mutating func push(val: T)
10    {
11        stack.append(val)
12    }
13
14    mutating func pop() -> T
15    {
16        return stack.removeLast()
17    }
18
19    var isEmpty: Bool {
20        return stack.isEmpty
21    }
22
23    var count : Int {
24        return stack.count
25    }
26}
27
28// Int icin
29var stackInt = Stack<Int>()
30
31stackInt.push(val: 10)
32stackInt.push(val: 20)
33stackInt.push(val: 30)
34
35while !stackInt.isEmpty {
36    print(stackInt.pop())
37}
38
39// String Icin
40var stackString = Stack<String>()
41
42stackString.push(val: "Kerem")
43stackString.push(val: "Swift")
44stackString.push(val: "Python")
45
46while !stackString.isEmpty {
47    print(stackString.pop())
48}

Swift Generic Type Constraints

  • Bir generic tanımında tür parametreleri hangi türden açılırsa açılsın anlamla olmak zorundadır. Aksi durumda compile time’da hata oluşur. Ne demek istediğimizi hemen bir örnek üzerinden açıklayım.
 1
 2func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
 3    for (index, value) in array.enumerated() {
 4        if value == valueToFind {
 5            return index
 6        }
 7    }
 8    return nil
 9}
10// Binary operator '==' cannot be applied to two 'T' operands
11let stringList = ["Python", "C++", "Swift", "Lisp", "Lua"]
12
13if let result = findIndex(of: "Swift", in: stringList) {
14    print(result)
15}
16else {
17    print("Bulunamadi")
18}
19

Evet burada bir problem var. Şöyle bir hata alıyoruz.

1// Binary operator ‘==’ cannot be applied to two ‘T’ operands

Burada her T türünün == operator fonksiyonu olmak zorunda değil. İşte bu tür durumlarda generic parametrelerine bazı kısıtları sağlamak zorunluluğu getirebiliriz.


Swift dilinde type constraints nasıl yapıyoruz?

1<turParametresi> : <ProtocolListesi>
  • Bu şekilde gene anlaşılmamış olabilir. Yukarıda yazdığımız fonksiyon parçamıza ekleyelim.
 1func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
 2    for (index, value) in array.enumerated() {
 3        if value == valueToFind {
 4            return index
 5        }
 6    }
 7    return nil
 8}
 9
10let stringList = ["Python", "C++", "Swift", "Lisp", "Lua"]
11
12if let result = findIndex(of: "Swift", in: stringList) {
13    print(result)   // 2
14}
15else {
16    print("Bulunamadi")
17}

Peki burada neler oldu?

  • Burada derleyici T türünün Equatable protokolünü destekleyen bir türle açılacağı GARANTİSİ verildi. Artık fonksiyon Equatable protokolünü desteklemeyen bir türle çağırmaya çalışırsak derleme aşamasında error oluşur.
  • Equatable protokolü ==, != destekliyor. Bunların dışında başka bir operatör kullanalım. Koda göre çok mantıklı olmasa da sadece hatayı görmek için <= kullanacağım. Alacağımız derleyici hatasını görmek için.
1// KODLAR
2// Binary operator '<=' cannot be applied to two 'T' operands
3if value <= valueToFind {
4    return index
5}
6//KODLAR
  • Şimdi yukarıda verdiğimiz formüle, yazım kuralanı göre : vardı. Tabi burada protocol konusunuda bilmemiz gerekiyor. Equatable prokolu sadece == ve != destekliyordu.

Yukarıda ki hatayı kaldırabilmek için ne yapabiliriz?

  • Comparable protokolü Equatable protokolünden türetilmiştir. Bu protokolde yalnızca < operatör fonksiyonu vardır.
  • Yani Comparable protokolünü destekleyen bir tür hem == hem de < operatör fonksiyonlarını bulundurmak zorundadır.
  • Bu iki operatör fonksiyonu bulunduğunda kütüphanedeki !=, <=, >= ve > generic operatör metotları devreye girerek != <=, >= ve > işlemlerini == ve < operatörlerini kullanarak yapmaktadır.
  • Dolayısıyla bir tür Comparable protokolünü destekliyorsa biz o turu 6 karşılaştırma operatörüyle de işleme sokabiliriz. Kafamız karışmış olabilir. Hemen yukarıda ilk tanımladığımız getMax fonksiyonu üzerinden örnek verelim.
 1func getMax<T: Comparable>(dizi a: [T]) -> T
 2{
 3    var max = a[0]
 4
 5    for i in 1..<a.count {
 6        if max < a[i] {
 7            max = a[i]
 8        }
 9    }
10
11    return max
12}
13
14let sayiDizisi = [97, 34, 1, 129, 444]
15getMax(dizi: sayiDizisi)    // 444
16let sayiDizisi2 = [97.0, 97.12, 1, 97.88, 97.9]
17getMax(dizi: sayiDizisi2)    // 97.90000000000001

Swift dilinde generic protocol kavramı var mı?

  • Swift dilinde generic protocol kavramı yok. Ama dolaylı yollardan oluşturabiliyoruz.

Generic protocol nasıl oluştururuz?

  • Swift dilinde associatedtype adında anahtar sözcük bulunuyor. Bu anahtar sözcük ile bir tür ismi oluşturursak protokolün elemanlarını buna dayalı olarak oluşturabiliriz. Böyle anlaması gene zor oluyor. Örnek üzerinden gidelim.
1
2protocol P {
3    associatedtype T
4
5    func test(item: T) -> T
6}
7

Yukarıda yazdığımız protocol bir tür destekleyecek ise T türü ne olursa olsun, protocol SADECE BİR TANE uygun method bulundurmak zorundadır.

 1protocol P {
 2    associatedtype T
 3
 4    func test(x: T) -> T
 5}
 6
 7class Example : P {
 8
 9    func test(x: Example) -> Example
10    {
11        return x
12    }
13}
14

Kaynaklar:

Generic Docs STL