Swift Class ve Struct Kullanımı, Aralarındaki Farklar

Merhaba arkadaşlar, Swift dersleri serimize  Swift Class ve Struct yapılarından, nasıl kullanıldıklarından ve aralarındaki farklardan bahsederek devam edeceğiz. Özellikle reference type ve value type karşılaştırması yaparak, bu tipleri detaylıca açıklayacağız. Bu yazımıza başlamadan önce eğer okumadıysanız, Swift Class yapısı ile Swift Init ve Deinit yazımızı okumanızı tavsiye ederiz.

Peki nedir bu Class ve Struct?

    Bir önceki dersimizden kısaca hatırlayalım:

Class, herhangi bir nesneye ait özellikleri(property) barındıran yapıdır. Class içine tanımlanabilecek metodlar(functions) sayesinde, oluşturulan nesneye işlevsellik kazandırabiliriz.

Örnek bir film class’ı tanımlayalım,

class Movie{
    
    var title: String?
    var duration: Int?
    
    init(movieTitle: String?,movieDuration: Int?){
        self.title = movieTitle
        self.duration = movieDuration
    }
    
    func changeMovieTitle(newTitle: String){
        self.title = newTitle
    }
}

Yukarıda oluşturduğumuz class, içine title(başlık) ve duration(süre) özelliklerini almaktadır. init() metodu vasıtasıyla oluşturulan yeni nesne için bu özelliklerini doldurur ve changeMovieTitle() fonksiyonu ile de filmin başlığını değiştirebilmektedir.

Peki Struct nedir?

Struct yapı olarak sınıf ile benzer özellikler gösterir. Aynı şekilde tanımlanır, içerisine özellikler ve metodlar alabilir. Bir örnek ile structı nasıl oluşturduğumuzu gösterip, aralarındaki benzerlik ve farklılıklara değinelim.

struct Series {
    var name: String?
    var season: Int?
    
    init(seriesName: String?,seriesSeason: Int?) {
        self.name = seriesName
        self.season = seriesSeason
    }
    
    func newSeasonDate(){
        print("Yeni sezonumuz 25 Ekim itibariyle ekranlarda!!")
    }
}

Not 1: Structlar init() metoduna ihtiyaç duymazlar. Yazdığımız özelliklere göre otomatik olarak init() metodu oluşturulur. Yukarıda yazdığımız init() metodunu yazmasak herhangi bir problem ile karşılaşmazdık.

Özelliklere(property) nasıl erişebiliriz?

Oluşturduğumuz nesnelerin içine tanımlanan özelliklere “.”(nokta) kullanarak erişebiliriz. Nasıl?

   let actionMovie = Movie(movieTitle: "Mad Max", movieDuration: 110)
   let madMaxDuration: Int = actionMovie.duration ?? 0
   print(madMaxDuration) // Konsol çıktısı -> 110

 

Not 2: Swift ile kodlama yaparken, Class ve Struct tanımlamalarımızı UpperCamelCase(class BinekArac veya struct TicariArac vb.), metod ve özellik tanımlamalarımızı ise lowerCamelCase(func getPlakaNo() veya var aracSeriNo vb.) kullanarak yazmamız kod okunurluğunu artırır ve geliştiriciler arasında kabul görmüş standarda uygun olur. Ancak bu şekilde yazmadığımızda derleyicide bir hata ile karşılaşmayız.

Swift Class ve Struct Ortak Özellikleri ve Farkları

Ortak Özellikler

Class ile aynı şekilde struct oluşturup, içine özellikler ve fonksiyonlar ekleyebiliyoruz. Peki başka ortak özellikleri de var mı bu kardeşlerin?

  • Özellikler tanımlayıp, bu özellikler vasıtasıyla değerleri depolayabiliriz. (örneğin: Film init ettiğimizde artık o filmin ismini ve süresini nesnemiz vasıtasıyla depolamış oluruz.)
  • Metodlar tanımlayarak oluşturduğumuz nesnelere işlevsellik kazandırabiliriz. (filmin ismini nesnemiz ve changeMovieTitle() metodu vasıtasıyla değiştirebiliriz.)
  • init() metodu sayesinde ilk değer ataması yapabiliriz.
  • Her ikisi de extend edilebilirler. (Extensions kullanılarak)
  • Belirli metodları tanımlamak için protokoller ile uyumlu çalışabilirler. (Protokol konusunu gelecek derslerimizde anlatacağız.)
  • Subscript yapısını kullanarak her ikisinin değerlerine erişilebilir. (Subscript konusunu da gelecek derslerimizde anlatacağız. 🙂 )

Eee bunlar aynı şeyler zaten neden iki farklı kavram çıkmış ortaya demeyelim hemen aralarında ne gibi farklılıklar var bunlara bakalım.

Farklar     

Bazı özellikler sadece Classlarda bulunmaktadır ve structlarda bu işlemler yapılamaz. Bunlar:

  • Inheritance -> şurada daha detaylı görebilirsiniz. (Swift inheritance)
  • Classlardan oluşturduğumuz nesnelerin eşit olup olmadıklarını kontrol edebiliriz. Nasıl mı?
    class FirstClass{}
    class SecondClass{}
    
    let first = FirstClass()
    let second = SecondClass()

    func compare(){
        if first === second{
            print("first ve second aynı sınıftan oluşmuş nesnelerdir.")
        }else{
           print("first ve second farklı sınıflardan oluşmuş nesnelerdir.")
        }
    }
  • Classlardan oluşturduğumuz nesneleri deinit edebiliriz. Şöyle ki;
    class Car {
        var redCar: Car?
        
       init(newCar: Car?){
          self.redCar = newCar
       }

        deinit {
            redCar = nil
        }
    }
  • ARC(Automatic Reference Counting) bir class nesnesine birden fazla referans verebilir. (ARC pek yakında gelecek 🙂 )

Not 3: Classlar sağladığı ek özellikler sayesinde avantajlı gibi görünebilir ancak class kullanımı karmaşıklığı artıracaktır. Bu sebeple sağladığı ek özelliklere ihtiyacımız olmadığı sürece structları kullanmak daha faydalı olacaktır.

Reference Type vs  Value Type

Geldik yazımızın can alıcı noktasına. Swift dilinde yeni bir özellik, metod, closure, enumeration, Int, String, struct … vb. tanımlarken aslında bir tip tanımlamış oluruz. Swift dilinde 2 tane tip vardır. Referans tipi (reference type) ve değer  tipi (value type). Class ve struct arasındaki en önemli farklardan biri de classlar referans tipi, structlar ise değer tipidir.

Reference Type

Classların referans tipi olduğunu söylemiştik. Peki referans tipi nedir? Ne iş yapar?

Bir classtan yeni bir nesne oluşturduğumuzda, oluşturduğumuz nesneyi bir değişkene veya sabite atayabiliriz, fonksiyon parametresi olarak gönderebilir, gönderdiğimiz fonksiyon içerisinde kullanabiliriz.  Bu işlemleri yaparken oluşturduğumuz nesne kopyalanmaz, oluşturulan nesnenin bellekteki referansı verilir. Ne kadar farklı değişkene atarsak atayalım hepsi aslında aynı nesnedir yani hepsinin bellekteki yeri aynıdır. Herhangi birinin bir özelliğine erişip değiştirdiğimizde, o nesneyi atadığımız bütün değişken ve sabitlerde, fonksiyon parametrelerinde de aynı değişikliğin olduğunu görürüz çünkü tekrar söyleyelim “Hepsi bellekte aynı alandan referans alır”.

Bir örnek vasıtasıyla iyice anlamaya çalışalım. Önünüzde bir kova var ve hortumu elinizde tutuyorsunuz.  Bu kovayı doldurmaya başladınız ancak farkettiniz ki iki kova suya ihtiyacınız var. Hemen aynı kovadan bir tane daha buldunuz ve ilk kovaya eşitlediniz. Şimdi sadece bir tanesini doldurmanıza rağmen her ikisinde de aynı değişim meydana geliyor. Aynı zamanda herhangi bir kovadan bir miktar su azalttığınızda her ikisinde de aynı azalmanın meydana geldiğini gözlemliyorsunuz. Kova sayısını ne kadar artırırsanız artırın sonucun değişmediği sistem referans tipidir. Buradaki ilk kova classtan oluşturduğumuz ilk nesnedir.  Daha sonra değişken ve sabitler vasıtasıyla kova sayısını artırsak da hep aynı şekilde dolduklarını yada bir tanesinden bir miktar azaldığında hepsinden azaldığını düşünelim. Bellekteki tek bir alana referans verildi ve referans verilen bütün nesneler aynı alanda değişiklik yapmaktadır. Hemen örneklere göz atalım:

let comedyMovie = Movie(movieTitle: "Home Alone", movieDuration: 98)
print("comedyMovie ilk atama   ->  \(comedyMovie.title)")

var homeAlone = comedyMovie //yeni oluşturduğumuz değişkene classtan oluşturduğumuz nesneyi atadık.
// Bu andan itibaren homeAlone değişkeni comedyMovie nesnesinin bellekte işaret ettiği alana işaret etmektedir.
print("homeAlone ilk atama   ->  \(homeAlone.title)")

homeAlone.changeMovieTitle(newTitle: "Home Alone 2") 

print("comedyMovie değiştikten sonra  ->  \(comedyMovie.title)")
print("homeAlone değiştikten sonra  ->  \(homeAlone.title)")

 

Yukarıda oluşturulan iki objenin bellekteki aynı objeyi referans alması durumunu resim ile açıklamaya çalışalım. “Movie” classından iki adet nesnemiz var her ikisi de aynı alan içinde değişikliğini yapıyor. Sadece homeAlone değişkeni vasıtasıyla movieTitle değiştirmemize rağmen comedyMovie nesnesi de bu değişiklikten etkilenmiştir.

Konsol çıktısı:

comedyMovie ilk atama   ->  Home Alone

homeAlone ilk atama   ->  Home Alone

comedyMovie değiştikten sonra  ->  Home Alone 2

homeAlone değiştikten sonra  ->  Home Alone 2

Peki classlar dışında referans tipi var mı?

Elbette var. Fonksiyonlar ve Closure’lar da referans tipidir.

Value Type

Değer tipinde işler referans tipinden biraz daha farklı. Burada oluşturulan nesneler bir değişkene veya sabite atandığında veya fonksiyona parametre olarak gönderildiğinde bellekte ilk oluşturduğumuz nesneden veya özellikten oluşan yer dışında farklı bir yer oluşturuluyor. Örneğin bir struct nesnesini bir değişkene atadığımızda bu değişken için yeni bir yer oluşturulur ve ilk durumda her iki alandaki data birbirinin aynısıdır. Ancak referansları birbirinden farklı olacağı için birinin özelliklerinde bir değişiklik yaptığımızda diğerinde bir değişiklik olmayacağı görülür.

Yukarıdaki kova örneğini hatırlayacak olursak, burada her kovanın bellekteki yeri ayrıdır ve bir kova içindeki su miktarı değişikliğinden diğer kovalar etkilenmezler.

 let bestSeriesEver = Series(seriesName: "HIMYM", seriesSeason: 9)
 print("bestSeriesEver ilk atama   ->  \(bestSeriesEver.season)")

 var himym = bestSeriesEver
 print("himym ilk atama   ->  \(himym.season)")

 himym.season = 5
 print("himym değişiklik sonucu   ->  \(himym.season)")
 print("bestSeriesEver değişiklik sonucu    ->  \(bestSeriesEver.season)")

 

Buradaki nesneler ise bellekte farklı alanı işaret ediyor ve dolayısıyla birisinde yapılan değişiklik diğerini etkilemez.

Konsol çıktısı:

bestSeriesEver ilk atama   ->  9

himym ilk atama   ->  9

himym değişiklik sonucu   ->  5

bestSeriesEver değişiklik sonucu    ->  9

Struct dışındaki diğer değer tipleri:

  • Int
  • Double
  • String
  • Array
  • Dictionary
  • Set
  • Enum
  • Tuple

Hangisini Kullanmalıyız? Reference Type mı? Value Type mı?

  • Değer tipleri thread-safe’tir. Stack’te tutulurlar ve her birisi kendi alanına sahiptir. Başka bir thread direkt olarak value type alanına erişemez. Dolayısıyla herhangi bir senkronizasyona ihtiyaç duymazlar, ancak referans tipi kullanırken bir nesnede gerçekleştirdiğimiz değişikliklerin diğer nesnelerde de değişmesi için senkronize olması gerekmektedir.
  • Değer tipleri Automatic Reference Counting ve dinamik bellek yönetimi gibi uygulama için yük teşkil eden operasyonlara ihtiyaç duymazlar, öte yandan referans tipleri bu iki operasyona da ihtiyaç duyar.
  • Referans tipleri bellekte hem heap hem de stack kullanır ancak  değer tipleri sadece stack kullanır. Stack’e erişim heap’e göre daha hızlıdır, ayrıca referans tipleri bellekte iki farklı bölgede tutulduğu için daha fazla alan işgal etmiş olurlar.

Kısacası performans ve güvenlik açısından değer tipleri daha avantajlıdır ancak referans tiplerine de tabiki ihtiyacımız vardır. Bu durumda sadece ihtiyaç durumlarında referans tiplerine başvurmak uygulama performansını artıracaktır.

Özet

Class ve struct yapılarını incelediğimiz bu yazıda, bu yapıların nasıl tanımlandığından, nasıl özellik eklendiğinden, bu özelliklere erişim yönteminden bahsettik. Class ve struct arasındaki benzerlik ve farklılıkları inceledik. Bu yazının önemli noktası olan referans tipleri ve değer tiplerinin avantaj ve dezavantajlarını değerlendirdik, nasıl çalıştıklarını gördük. Buradan çıkardığımız sonuç, sadece ihtiyaç duyduğumuzda class kullanılması yönünde idi. Okuyanlar için faydalı olmasını umarak yazımı bu şekilde tamamlıyorum. Herkese mutluluklar, sağlıcakla kalın… ✋

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

43

Ali Hasanoglu

6 Yorum

Haftalık Bülten

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