Swift Generics Nedir? Generic Kullanım Örnekleri

Merhaba arkadaşlar, Swift dersleri serimize Swift Generics ile devam ediyoruz. Generics proje içinde bize çok yardımcı olmaktadır. Aşağıda detaylı şekilde bunu açıklayacağız ancak en kısa tanımıyla generics türlere bakılmaksızın(türleri tek bir türe dönüştürerek) işlem yapmamıza olanak verir. Bu sayede tek bir sefer yazıp birçok farklı tip için kullanacağımız yapılar kurmamıza izin verir. Başlamadan önce okumadıysanız protokol konulu konulu dersimizi okumanızı tavsiye ederiz. Hemen dersimize başlayalım o halde:

Swift Generics Nedir?

Swift programlama dili güçlü tip yapısına sahiptir yani bir sabit veya değişken oluştururken tipini açık veya kapalı(implicit, explicit) şekilde belirtmelisiniz. Type Casting dersinde bu işleme daha detaylı değinmiştik buradan inceleyebilirsiniz. Belirttiğimiz bu tipi değiştiremeyiz. Bu birçok konuda bizim için faydalı olsada bazı istisnai durumlar oluşmaktadır. Şimdi generic bir metot örneğiyle konuya daha yakından bakalım:

Swift Generics Metotlar

Önce örneği yazalım daha sonra üzerinden anlayarak devam edelim:

func test<T>(temp: T) -> T {
    let temp2 = temp
    return temp2
}

Yukarıda en basit haliyle generic bir metot bulunmaktadır. Generic olmayan metot yazdığımızda, metot içinde gelen parametrenin tipini ve dönüş tipini belirtmemiz gerekir, ancak generic metotlarda gelen tipler genel bir tiptir. Bu tipi kendimiz istediğimiz şekilde belirleyebiliriz. Metot parametrelerini yazdığımız yuvarlak parantezlerden önce “<>” işaretleri arasında generic tipi belirlememiz gerekmektedir. Yukarıdaki örnekte generic tipi yazdığımız alanlar görülmektedir. Metot içinde gelen ve dönüş tipimiz generic bir tiptir. Yukarıdaki örnek bize temp parametresinin tipine bakılmaksızın geri aynı tipte, aynı parametreyi verecektir.

Yukarıdaki örneği işimize yarayacak şu hale güncellememiz mümkün. Örneğin dizi içindeki bir elemanın indisini bulan temel bir metot yazalım. Önce generic olmadığı halini yazalım:

func indisBul(itemArray: [Int], item: Int) -> Int?{
    for (i, temp) in itemArray.enumerated(){
        if temp == item{
            return i
        }
    }
    return nil
}

Yukarıda da görüldüğü gibi generic olmadığı haliyle yazdığımızda tiplerini belirtmemiz gerekiyor. Bu şekilde string, double vb. tipler için ayrı ayrı metot yazmamız gerekmektedir. Generic yazdığımız durumda tek bir metot yazmamız yeterli olmaktadır ve tek bir metot ile gönderdiğimiz değişik tipler ile işlem yapabiliriz. Şimdi bu metodu generic yapalım:

func genericIndisBul<T: Equatable>(itemArray: [T], item: T) -> Int?{
    for (i, temp) in itemArray.enumerated(){
        if temp == item{
            return i
        }
    }
    return nil
}

Yukarıda aynı metodumuzu generic şekilde yazdık ve diziler ile de nasıl kullanıldığını görmüş olduk. Tek bir metot ile işlemimizi hallettik ve farklı tipler için metot yazmamıza gerek kalmadı. Artık içerisine farklı tipler göndererek işlemlerimizi yapabiliriz.

Not: Yukarıda kullanılan Equatable protokolü ve neden kullandığımız aşağıda detaylıca açıklanacaktır.

Swift Generics Veri Yapısı

Bir veri yapısında farklı tipte veriler tutulabilir. Bunu gerçeklemek için generic bir yapı kurulması gerekmektedir. Aslında mantık yukarıda yazdığımız metodun aynısı. Hadi generic bir stack yazalım:

public struct Stack<T> {
    private var array = [T]()
    
    public var isEmpty: Bool {
        return array.isEmpty
    }
    
    mutating func push(_ element: T) {
        array.insert(element, at: 0)
    }
    
    mutating func pop() -> T? {
        if isEmpty {
            return nil
        } else {
            return array.removeFirst()
        }
    }
}

İçerisine T tipinde elemanlar eklenip çıkarılabilen bir stack oluşturduk. Artık bu stack içinde farklı tiplerde veriler tutabiliriz. String tuttuğumuz örneği aşağıya ekleyelim o halde:

var stack = Stack<String>()
stack.push("Ali")
stack.push("Veli")
print(stack)
stack.pop()
print(stack)

Swift Generics Type Constraints

Generic bir tip oluşturduğumuzda bu oluşturduğumuz tip yalnız başınadır, yani hiçbir sınıf veya protokolden kalıtım almamıştır. Bu sebeple bazı işlemleri yapabilmek için kendimiz protokol oluşturmalıyız veya var olan bazı protokolleri kullanmalıyız. Örnek vermek gerekirse generic tipteki iki parametreyi karşılaştırmak için “==” operatörünü kullanamayız. Bu operatör Swift standart kütüphanesinde bulunan tiplerde implement edilmiştir. Buna örnek olarak Swift standart kütüphanesi içindeki Character struct’ ını verelim.

Yukarıda da görüldüğü üzere Character struct’ı içinde “==” operatörü kullanıma hazır olarak gelmektedir. Bu struct Equatable protokolü ile bu operatörü kullanmamıza izin verir.  Standart kütüphane içinde bu operatörler bizim için implement ediliyor ancak kendi tipimizi oluşturduğumuzda implement edilmemiş olacağı için bu protokolleri kendimiz  eklememiz gerekiyor. Öncelikle hata alan bir örneği bırakıyorum:

func genericIndisBul<T>(itemArray: [T], item: T) -> Int?{
    for (i, temp) in itemArray.enumerated(){
        if temp == item{
            return i
        }
    }
    return nil
}

Yukarıda örnek olarak verdiğimiz genericIndisBul metodumuzdaki T tipimize Equatable protokolünü eklemez isek aşağıdaki hatayı alacağız:

Şimdi yaygın olarak kullanılan bu protokollere birkaç tanesine göz atalım:

Equatable Protokolü

Equatable protokolü bize “==” ve “!=” operatörlerini verir. bu protokolü eklediğimiz generic tiplerimizde bu iki operatörü kullanabiliriz. Bir adet de “!=” operatörünü kullandığımız bir örnek bırakıp devam edelim:

func getDifferent<T: Equatable>(array: [T], char: T) -> Bool{
    for item in array{
        if char != item{
            return true
        }
    }
    return false
}

Comparable Protokolü

Comparable protokolü bize karşılaştırmak için kullandığımız “<“, “>”, “<=”, “>=” operatörlerini verir. Ayrıca bu protokol Equatable protokolünden kalıtım aldığı için “==” ve “!=” operatörlerine de izin vermektedir. Xcode içerinde command’a basılı tutup Comparable üzerine tıklayarak içerisinde bulunan operatörleri ve kalıtım aldığı protokolleri görebiliriz.

 func getMin<T: Comparable>(heightArray: [T]) -> T{
        var min = heightArray[0]
        for (i,_) in heightArray.enumerated(){
            if min > heightArray[i]{
                min = heightArray[i]
            }
        }
        return min
    }

Yukarıda gelen dizinin en küçük elemanını dönen generic bir metot yazdık.

Özet

Swift Generics tek derse sığamayacak kadar büyük olduğu için iki farklı ders olarak hazırlamaya karar verdim. Bu ders içerisinde Swift Generics’e giriş yapıp basic metotlar yazdık. Konunun temelini anlamaya çalıştık. Bir sonraki derste kendi protokollerimizi ekleyeceğiz, generic protokollere bakacağız, generic bir parser yapısı oluşturacağız vs. Bir sonraki dersimiz bir miktar daha karmaşık olacak, bu sebepten yukarıdaki örneklerin iyi anlaşılması gerekmektedir.  Dersi burada bitirirken herkese faydalı olmasını umarak, mutluluklar diliyorum. Soru, görüş ve önerilerinizi yorum kısmından veya soru-cevap kısmından iletebilirsiniz. Sağlıcakla…

Kaynak: https://docs.swift.org/swift-book/LanguageGuide/Generic.html

41

Ali Hasanoglu

Yorum Yaz

Haftalık Bülten

Mobilhanem'de yayınlanan dersleri haftalık mail almak ister misiniz?