Custom Swift Collection Oluşturmak

Merhaba arkadaşlar, Swift dersleri serimize Custom Swift Collection oluşturma ile devam edeceğiz. Bilindiği üzere Swift standart kütüphanesi üç farklı collection sunar  (Array, Dictionary ve Set). Bu derslere linkinden erişebilirsiniz. Bunun yanında kendi collection tipimizi de oluşturabiliriz. Bugün kendi collection tipimizi oluştururken ihtiyacımız olan ve işimize yarayacak protokolleri inceleyeceğiz. Ardından farklı bir ders içeriğinde ise bu protokolleri kullanıp collection tipimizi oluşturacağız. Bahsedeceğimiz protokoller sırasıyla  Hashable , Sequence & IteratorProtocol, CustomStringConvertible protokolleri.

Hashable

Hashable protokolü Swift standart kütüphanesinde çokça kullanılmaktadır. Özellikle Set ve Dictionary tiplerinde kullanımı görülmektedir. Bu tiplerden Dictionary key’i ve set elemanları Hashable olmak zorundadır. Peki Neden Hashable olmak zorunda…

Hashable olan tiplerin her bir instance’ı için bir hashValue üretilir ve bu hashValue unique(tek)’tir. Ne demek istedik hemen bakalım; örneğin Int tipi, Swift standart kütüphanesinde, Hashable protokolünü conform ederek gelir. İki adet değişkenimiz olduğunu varsayalım;

var firstValue: Int = 80
var secondValue: Int = 90

Her iki değer de Int olmasına rağmen, birbirlerinden farklı oldukları için farklı hashValue üreteceklerdir. Konumuza geri dönmek gerekirse bizim Dictionary key ve Set elemanlarımız unique olmak zorundadır ve bu kontrol hashValue ile yapılmaktadır.

Hashable ve Custom Tip Kullanımı

Swift Collection kullanırken custom bir tip kullanabilmek için oluşturduğumuz custom tipimizin Hashable protokolünü conform etmesi gerekir. Dictionary örneği üzerinden devam edelim; her key’in bir değeri bulunmaktadır. Buradaki key’lerin unique olması için hashValue karşılaştırması yapıldığını söylemiştik. Bizim tipimizin Hashable olmadığı durumda hashValue üretilemez. Dolayısıyla custom bir tipi Dictionary key’i olarak kullanmak istersek Hashable protokolünü conform etmeliyiz.

class Car: Hashable{
    
    let serialNumber: Int?
    let brand: String?
    
    
    init(serialNumber: Int?, brand: String?) {
        self.serialNumber = serialNumber
        self.brand = brand
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(serialNumber)
    }
    
    static func == (lhs: Car, rhs: Car) -> Bool {
        return lhs.serialNumber == rhs.serialNumber
    }
}

Yukarıda custom bir sınıf oluşturduk ve Hashable protokolünden conform ettik. Konuşmadığımız iki adet metot bulunmakta, bunlardan ilki hash(into:) metodu. Bu metot bizim hashleyeceğimiz değişken veya sabitlerin hash değerlerini üretiyor. İkinci metot ise “==” metodu. Bu metot ise bizim hashValue değerlerimizin eşitliklerini kontrol etmemize olanak sağlıyor. Bu iki metodu ekleyerek oluşturacağımız nesnelerin unique olup olmadığını kontrol ediyor olabileceğiz. Önce hatalı bir örnek yazıp bunu düzeltelim:

let car1 = Car(serialNumber: 1, brand: "Bmw")
let car2 = Car(serialNumber: 1, brand: "Audi")
        
let carArray: [Car: String] = [
    car1: "M3",
    car2: "RS3"
]

Yukarıdaki kodu yazdığımızda  “Thread 1: Fatal error: Dictionary literal contains duplicate keys” hatasını alacağız çünkü; her iki nesnemizin seri numaraları aynı ve unique olabilmeleri için sağladığımız tek parametre bu olduğu için derleyici her iki nesnenin hashValue değerlerini karşılaştırdığında aynı olduğunu görecektir ve bir Dictionary aynı key’i iki defa kullanamayacağı için hata verecektir. İki şekilde düzeltilebilir; ilki brand parametresini de hash metoduna sokmak. Şu şekilde yapacağız:

class Car: Hashable{
    
    let serialNumber: Int?
    let brand: String?
    
    
    init(serialNumber: Int?, brand: String?) {
        self.serialNumber = serialNumber
        self.brand = brand
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(serialNumber)
        hasher.combine(brand)
    }
    
    static func == (lhs: Car, rhs: Car) -> Bool {
        return (lhs.serialNumber == rhs.serialNumber) || (lhs.brand == rhs.brand)
    }
}

Seri numaraları aynı olsa dahi markaları farklı olan iki arabanın markalarından üretilen hashValue farklı olduğu için unique olması sağlanır. İkinci çözüm ise oluşturduğumuz nesnelerin seri numaralarını değiştirmek:

let car1 = Car(serialNumber: 1, brand: "Bmw")
let car2 = Car(serialNumber: 2, brand: "Audi")
        
let carArray: [Car: String] = [
    car1: "M3",
    car2: "RS3"
]

İlk çözüm daha etkili bir çözüm olmakla birlikte, her iki durumda da derleyiciden aldığımız hatadan kurtuluruz.

Sequence & Iterator Protocol

Sequence & Iterator protokolleri dizide, dictionary’de veya kümede döngü vasıtasıyla elemanlara tek tek erişmemize olanak sağlar. Sequence protokolü sahip olduğu makeIterator() metodu vasıtasıyla, Iterator protokolünden bir nesne oluşturarak Iterator protokolü içindeki next() metoduna erişebilir ve bu metodu çağırarak sıralı bir şekilde collection elemanlarına erişir. Bu iki protokolün birlikte kullanıldığı en yaygın örneklerden birisi for-in döngüleridir.

let numbers = [1,2,3,4,5]
for number in numbers{
   print(number)
}

Yukarıdaki örnek bir dizideki elemanları sırasıyla dönme işlemini yapmaktadır. Arka planda Sequence ve Iterator protokolleri iş başında ancak standart kütüphane bu işlemi kolaylaştırmak adına bize göstermeden (implicit) hallediyor. İşin arka planına bakacak olursak şöyle bir kod bloğu ile karşılaşacağız:

let numbers = [1, 2, 3, 4, 5]
var numbersIterator = numbers.makeIterator()

while let num = numbersIterator.next() {
   print(num)
}

İlk yazdığımız kodun arka planda dönüştüğü kod bloğunu da ekledik. Diziler standart kütüphanede Sequence protokolünden conform edilmiştir, dolayısıyla önce dizimizi kullanarak bir iterator nesnesi oluşturduk ve bu iteratorun next() metodu vasıtasıyla dizi içinde eleman kalmayıncaya kadar bu dizinin elemanlarını print ettik. Sadece yukarıdaki kısa kodun (explicit) açık halini görmüş olduk.

CustomStringConvertible

Bu protokol yukarıda anlattığımız protokoller gibi eklenmesi bizim için zorunlu olmamakla birlikte eklendiği durumda debug yapma işlerimizi kolaylaştıracaktır. Örnek üzerinden giderek açıklayalım; bir nesneyi debug etmek amaçlı konsola bastırmak istediğimizde eğer CustomStringConvertible protokolünü conform etmez ise nesne içeriğini göremeyiz. Konsoldan görmek istediğimizde ise tüm parametreleri bize gösterecektir. Onlarca parametresi olan bir nesne için debug edilmesi yine zorlaşacaktır.

Konsolda gördüğümüz nesne bu şekilde olacaktır ancak içeriğini göremediğimiz için bize iyi bir debug deneyimi sunmaz. Car sınıfını CustomStringConvertible protokolünden conform ettiğimiz duruma bakalım:

class Car: CustomStringConvertible{
    
    var description: String{
        return "Marka : \(self.brand ?? ""), seri num: \(self.serialNumber ?? -1)"
    }
    
    let serialNumber: Int?
    let brand: String?
    
    
    init(serialNumber: Int?, brand: String?) {
        self.serialNumber = serialNumber
        self.brand = brand
    }
}

CustomStringConvertible’dan conform ettiğimiz sınıf içine “description” adında bir değişken tanımlatacaktır ve print ederken göstermek istediğimiz nesne özelliklerini burada istediğimiz şekilde gösterebiliriz. Konsol çıktısı şu şekilde olacaktır:

Görüldüğü gibi CustomStringConvertible bizim debug özelliklerimizi artırarak bize kolaylık sağlar. Sadece ihtiyacımız olan parametreleri basarak isteğimize ulaşırız.

Özet

Kendi collection tipimizi oluştururken ihtiyacımız olan protokollere bu dersin içeriğinde değindik. Bir sonraki dersimizde bu protokolleri kullanıp ihtiyacımıza özgü bir collection yazıp, yine ihtiyacımıza özgü metotlar ile bu collection tipimizi zenginleştirmeyi hedefliyoruz. 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…

Kaynaklar:

https://developer.apple.com/documentation/swift/hashable

https://developer.apple.com/documentation/swift/sequence

https://developer.apple.com/documentation/swift/iteratorprotocol

https://developer.apple.com/documentation/swift/customstringconvertible

11

Ali Hasanoglu

Yorum Yaz

Haftalık Bülten

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