UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Classes (Sınıflar)

Swift, karmaşık veri tipleri oluşturmak için Class (sınıf) adında bir başka yola daha sahiptir. Struct'lara (yapı) benzer, ama şunları da içeren bazı önemli farklılıkları vardır:

  • Class'larınızla birlikte otomatik bir üyelik başlatıcısı gelmez; kendiniz yazmanız gerekir.
  • İstediğiniz yeni şeyleri ekleyerek, başka bir Class'ı temel alan bir Class tanımlayabilirsiniz.
  • Bir Class'ın örneğini oluşturduğunuz zaman, artık o bir nesne (object) olarak anılır. Eğer bu nesneyi kopyalarsanız, ön tanımlı olarak ikisi de aynı veriyi kopyalar; birini değiştirirseniz, diğer kopya da değişir.

Bu üçü de büyük farklardır. Ben de devam etmeden önce, bunları derinlemesine ele alacağım.

Bir nesneyi başlatma

Eğer bizim Person struct'ını Person class'ına döndürseydik, Swift şu şekilde yazmamıza izin vermezdi:

class Person {
    var clothes: String
    var shoes: String
}

Bunun sebebi, hatırlarsanız daha önce söylediğimiz gibi, mutlaka bir değere sahip olması gereken iki String tipinde değer tanımladığımız içindir. Struct için bir sorun yok, çünkü Swift bu iki özelliğe değer vermemiz için bizi zorlayarak, üyelik başlatıcıyı otomatik olarak üretiyor. Ama class'larda durum böyle değil, o yüzden Swift verilecek değerden emin olamıyor.

Bunun için üç tane çözüm var: İki değeri de optional String yapmak, veya onlara birer başlangıç değeri atamak, ya da kendi başlatıcımızı yazmak. İlk seçenek, tüm kod geneline gerek olmasa da optional'ları tanıtıyor. İkinci seçenek işe yarar, ama bu başlangıç değerleri gerçekten kullanılmadıkça ziyan edilmiş olurlar. Geriye kalan üçüncü seçenek, kesinlikle doğru olan: Kendi başlatıcımızı yazmak.

Bunu yapmak için, class içerisinde bizi ilgilendiren iki parametreyi alan ve init() adında bir metod oluşturun:

class Person {
    var clothes: String
    var shoes: String

    init(clothes: String, shoes: String) {
        self.clothes = clothes
        self.shoes = shoes
    }
}

Bu kodda dikkatinizi çekebilecek iki şey var:

İlki, init() metodundan önce func yazmayacaksınız, çünkü bu özel bir metoddur. İkincisi ise, parametre adları ile atamak istediğiniz özellik adları aynı olduğu için self. kullanın. Kolayca kavramanız için şöyle söyleyebiliriz: "Bu nesnenin clothes özelliği, değerini aktaracağı clothes parametresiyle aynı olması gerekli". İstediğiniz isimleri verebilirsiniz onlara, tamamen size kalmış.

Önemli: Swift, başlatıcının sonuna kadar optional olmayan tüm özelliklerin bir değere sahip olmasını gerektirir veya o zamana kadar başlatıcı, herhangi bir diğer metodu çağırır; artık hangisi önce gelirse.

Class inheritance (Sınıf kalıtsallığı)

Struct'lar ve class'lar arasındaki ikinci önemli fark, sınıf kalıtsallığı olarak da bilinen daha büyük şeyler üretmek için birbiri üzerine kurulabilir. Bu teknik, en basit programlarda bile yoğun bir şekilde Cocoa Touch içinde kullanılır, o yüzden iyi bir şekilde anlamanız gerekli.

Basit birşeyle başlayalım: name (isim) ve age (yaş) özellikleri olan bir Singer class'ı oluşturalım. Metodlar olarak, özelliklere değer atayabilmek için basit bir başlatıcı olacak, bir de birkaç sözcük yazdıracak olan sing() metodu:

class Singer {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func sing() {
        print("La la la la")
    }
}

Başlatıcıyı çağırarak bu nesnenin bir örneğini oluşturalım şimdi. Ardından da özelliklerini yazdıralım ve metodunu çağıralım:

var taylor = Singer(name: "Taylor", age: 25)
taylor.name
taylor.age
taylor.sing()

İşte bizim basit class'ımız, ama onun üzerinde çalışmaya devam edeceğiz: Singer class'ının yaptığı her şeyi yapan, ama sing() metodunu çağırdığımda "Trucks, guitars, and liquor" yazmasını istediğim bir CountrySinger sınıfı tanımlamak istiyorum.

Orjinal Singer class'ını yeni oluşturduğun CountrySinger class'ına tabiki de kopyalayabilirdin. Ama bu programlamanın tembel bir şekli. Hem sonrasında Singer class'ında bir değişiklik yapıp, diğerinde yapmayı unutursan bu değişiklikler geri gelip seni bulur. Bunun yerine, Swift'in daha akıllıca bir çözümü var: Singer class'ını baz alarak CountrySinger sınıfını oluşturabiliriz. Böylece oluşturmamız gereken tüm bu özellikler ve metodlar bize hazır gelmiş olur:

class CountrySinger: Singer {

}

Bu iki nokta üst üste sihrin oluştuğu şeydir: "CountrySinger class'ı Singer class'ını genişletir" anlamına gelir. Artık yeni CountrySinger class'ı (subclass –alt sınıf– olarak adlandırılır), Singer class'ına (parent class veya superclass –ana sınıf veya süper sınıf– olarak adlandırılır) hiçbir şey eklemez. Biz de zaten kendi has sing() metodu olsun istiyoruz, ama Swift dilinde yeni bir anahtar sözcük daha öğrenmeniz gerekli: override. Bunun anlamı "Bu metodun parent class'ımda tanımlandığını biliyorum, ama bu subclass için bunu değiştirmek istiyorum"dur.

override anahtar sözcağüne sahip olmak faydalı bir şey, çünkü ne yapmak istediğinizi açıklığa kavuşturuyor. Aynı zamanda Swift'in kodunuzu kontrol etmesini sağlıyor: Eğer override kullanmasaydınız, Swift superclass'ınızdan gelen metodu değiştirmenize izin vermeyecekti veya override kullansaydınız ama geçersiz kılacağınız (override) birşey olmasaydı, Swift hatanızı size işaret edecekti.

O yüzden, aşağıdaki gibi bir override func kullanmamız gerekiyor:

class CountrySinger: Singer {
    override func sing() {
        print("Trucks, guitars, and liquor")
    }
}

Şimdi taylor nesnesinin oluşturulma şeklini değiştirin:

var taylor = CountrySinger(name: "Taylor", age: 25)
taylor.sing()

Eğer CountrySinger nesnesini sadece Singer nesnesine değiştirirseniz, sonuç panelinde beliren farklı mesajları görebilmeniz gerekiyor.

Şimdi birşeyleri karmaşıklaştıralım ve HeavyMetalSinger adıyla yeni bir class tanımlayalım. Ama bu kez, heavy metal şarkıcının mikrofonundan geçen çığlığın ne kadar gürültülü olduğunu belirleyen noiseLevel (gürültü seviyesi) adını verdiğimiz bir özelliği saklayacağız.

Bu bir soruna sebep olur ve çok özel bir şekilde çözülmeyi gerektirir:

  • Swift dili, tüm optional olmayan özelliklerin bir değere sahip olmasını ister.
  • Singer class'ımız noiseLevel özelliğine sahip değildir.
  • O yüzden, bu gürültü seviyesini kabul eden HeavyMetalSinger için ısmarlama bir başlatıcı oluşturmamız gerekiyor.
  • Bu yeni başlatıcı aynı zamanda heavy metal şarkıcısının name ve age özelliklerini bilmeye ihtiyacı var. Bunu Singer superclass'ı aracılığıyla değer geçebilir.
  • Superclass'taki veriye değer geçmek metodun çağrılmasıyla yapılır ve özelliklerinizin hepsine değer verene kadar başlatıcıdaki metod çağrılarını yapamazsınız.
  • O yüzden, önce özelliğimizi ayarlamamız gerekiyor (noiseLevel), ardından da kullanılacağı superclass için diğer parametreleri geçersiniz.

Bu size korkunç bir şekilde karışık gelebilir, ama kodun içinde doğrudan oluyor. Burada HeavyMetalSinger class'ını, kendi sing() metoduyla tamamlayın:

class HeavyMetalSinger: Singer {
    var noiseLevel: Int

    init(name: String, age: Int, noiseLevel: Int) {
        self.noiseLevel = noiseLevel
        super.init(name: name, age: age)
    }

    override func sing() {
        print("Grrrrr rargh rargh rarrrrgh!")
    }
}

Başlatıcının nasıl üç parametre aldığına ve ardından Singer superclass'ındaki name ve age değişkenlerine değer geçmek için super.init() çağrısına dikkat edin; ama tabi sadece kendi özellikleri ayarlandıktan sonra. Nesnelerle çalışırken super ifadesini çoğu kez göreceksiniz. Bu sadece "türetiğim sınıfın metodunu çağır" anlamına geliyor. Genellikle "önce parent class'ımın ihtiyaç duyduğu her şeyi yapmasına izin ver, sonrasında ben fazladan birşeyler yapacağım" anlamında kullanılır.

Sınıf kalıtsallığı büyük bir konudur, o yüzden henüz tam olarak anlamadıysanız üzülmeyin. Zaten, bilmeniz gereken bir şey daha var: Sınıf kalıtsallığı sıklıkla birçok aşamaya yayılır. Örneğin A, B'den türemiş olabilir ve B ise C'den türemiş olabilir, hatta C de D'den türemiş olabilir. Bu böyle gider. Bu, yazdığınız kodun modüler ve anlamasının kolay olmasına yardımcı olan class'ları fonksiyonel bir şekilde kurmanıza ve yeniden kullanmanıza izin verir.

Objective-C koduyla çalışmak

Eğer Swift class metodlarınızı çağıran Apple'ın işletim sisteminin bazı parçalarına sahip olmak istiyorsanız, özel bir nitelik ile onu işaretlemeniz gerekir: @objc. Bu, “Objective-C”nin kısaltmasıdır ve çoğunlukla tüm iOS, macOS, watchOS ve tvOs için olan daha eski Objective-C kodunu çalıştıran metodu etkin bir şekilde işaretleyen niteliktir. Örneğin, bir saniye geçtikten sonra metodunuzun çağrılmasını sisteme söylemek için, onu @objc ile işaretlemeniz gerekli.

Şimdilik @objc konusuna çok takılmayın; sadece devam eden konular içinde onu açıklamaya çalışmayacağım, aynı zamanda Xcode da her zaman bunu sana gerektiğinde söyleyecek. Alternatif olarak, eğer bireysel metodlar için @objc kullanmak istemezseniz, otomatik olarak Objective-C için class'ınız tüm metodlarını uygun hale getirmeden önce @objcMembers koyabilirsiniz.

Değerler ve referanslar

Bir struct'ı kopyaladığınızda, tüm değerleriyle birlikte her şey çoğaltılır. Bu, bir struct'ın bir kopyasını değiştirerek, diğer kopyaların değiştiremeyeceğiniz anlamına gelir; hepsi bireyseldir. Class'larla, bir nesnenin her bir kopyası aynı orjinal nesneyi işaret eder. Yani birini değiştirirseniz, hepsini değiştirmiş olursunuz. Swift struct'ları "değer tipleri" olarak tanımlar, çünkü onlar sadece bir değeri işaret eder. Ama class'lar "referans tipleri"ndendir, çünkü nesneler gerçek değerlerin paylaşımlı referanslarıdır.

Bu önemli bir farktır ve struct'lar ile class'lar arasında seçimin önemli olduğu anlamına gelir:

  • Eğer, değer geçilmesi ve yerinde değiştirilmesi için paylaşımlı bir hale sahip olmak istiyorsanız, class'ları arıyorsunuz demektir. Fonksiyonların içine onları aktarırsınız veya dizilerde saklarsınız, orada değiştirirsiniz ve programın kalan kısmına bu değişiklikleri yansıtırsınız.
  • Eğer, bir kopya değiştiğinde diğerlerini etkilemesini istiyor ve paylaşımlı bir durumdan kaçınıyorsanız, o zaman struct'ları arıyorsunuz demektir. Fonksiyonların içine onları aktarırsınız veya dizilerde saklarsınız, orada değiştirirsiniz, ama referans olduğu başka hiçbir yerde değişmezler.

Struct'lar ve Class'lar arasındaki anahtar farkı özetlemem gerekseydi, şunu söylerdim: Class'lar daha esnek, ama struct'lar daha güvenli. Basit bir kural olarak, belirli bir neden dolayı ihtiyaç duyana kadar, her zaman struct'ları kullanmalısınız.

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.