- Görkem Güray/
- 100 Günde SwiftUI Notları/
- 10.Gün - Swift Struct - 1 : Struct, Computed Property ve Property Observer/
10.Gün - Swift Struct - 1 : Struct, Computed Property ve Property Observer
Table of Contents
Struct Nasıl Oluşturulur? #
Swift’teki struct, kendi değişkenleri ve kendi fonksiyonları olan kapsamlı ve kendimize özel veri türü oluşturmamıza olanak sağlar.
Basitçe bir struct aşağıdaki gibi gözükür;
struct Album {
let title: String
let artist: String
let year: Int
func printSummary() {
print("\(title) (\(year)) by \(artist)")
}
}
Yukarıdaki kod, Album
adında yeni bir tür (type) oluşturur. Bu tür, title
ve artist
olmak üzere iki adet String, year
adında bir Int ve printSummary()
adında bir fonksiyon içerir.
Struct’ın isimlendirmesini yaparken ilk harfi büyük yazılır. Bizim örneğimizdeki Album
’ün de büyük harfle başladığına dikkat edelim.
Artık, Album tipinde değişken ve sabitler oluşturabilir, değer atayabilir veya kopyalama işlemlerini yapabiliriz. Tıpkı daha önce String veya Int oluşturduğumuz gibi.
let red = Album(title: "Red", artist: "Taylor Swift", year: 2012)
let wings = Album(title: "Wings", artist: "BTS", year: 2016)
print(red.title)
print(wings.artist)
red.printSummary()
wings.printSummary()
//ÇIKTI:
//----------------------------------------
//Red
//BTS
//Red (2012) by Taylor Swift
//Wings (2016) by BTS
Fonksiyon çağırır gibi yeni Album
türünde veriler oluşturabiliyoruz. Gördüğünüz gibi hem red
hem de wings
Album
türünde olmasına rağmen birbirlerinden tamamen ayrılar.
Bu durumu her struct üzerinde printSummary()
fonksiyonunu çağırdığımızda görebiliriz. Aynı struct’tan türemiş red
ve wing
üzerinde printSummary()
fonksiyonunu çağırdığımızda bize farklı değerler döndürür.
İşlerin farklılaştığı yer, değişebilen değerlere sahip olmak istediğimiz zamandır. Örneğin gerektiğinde tatil yapabilen bir Employee
struct oluşturabiliriz.
struct Employee {
let name: String
var vacationRemaining: Int
func takeVacation(days: Int) {
if vacationRemaining > days {
vacationRemaining -= days
print("I'm going on vacation!")
print("Days remaining: \(vacationRemaining)")
} else {
print("Oops! There aren't enough days remaining.")
}
}
}
Yukarıdaki kodu yazmak istediğimizde, Swift kodu oluşturmak istemeyecektir.
Swift struct’ları ve sahip oldukları verileri sabit olarak oluşturur. Bu Swift’in daha hızlı çalışmasını sağlar. Fakat struct içerisindeki bir fonksiyon, bir veriyi değiştirmek istediğinde buna izin verilmez, çünkü tüm veriler sabitti.
Sonuçta, struct’a ait veriyi değiştiren bir fonksiyonumuz varsa bu durum mutating
ile işaretlenmelidir. Veriyi değiştirmeyen sadece okuyan bir fonksiyonumuz varsa, işini sorunsuzca yapabilir mutating
ile işaretlenmesi gerekmez.
mutating func takeVacation(days: Int) {
Struct kodumuzda yukarıdaki değişikliği yaptıktan sonra gayet iyi çalışacaktır. Employee
struct’tan bir sabit oluşturalım:
let archer = Employee(name: "Sterling Archer", vacationRemaining: 14)
archer.takeVacation(days: 5)
print(archer.vacationRemaining)
Yukarıdaki kodu oluşturmak istediğimizde yine hata alacağız. Çünkü mutating
fonksiyon barındıran bir struct’ı sabit olarak tanımladığımızda veri değiştirilemez.
Çalışabilecek kod aşağıdaki gibi olmalıdır.
var archer = Employee(name: "Sterling Archer", vacationRemaining: 14)
archer.takeVacation(days: 5)
print(archer.vacationRemaining)
Struct ile ilgili önemli noktalar;
- Struct’a ait sabit ve değişkenler property olarak adlandırılır.
- Struct’a ait fonksiyonlar method olarak adlandırılır.
- Bir struct’tan bir sabit veya değişken oluşturulduğuna buna instance denilmektedir.
- Struct’tan bir instance oluştururken aşağıdaki gibi bir initializer kullanırız.
Album(title: "Wings", artist: "BTS", year: 2016)
Bir struct’tan instance oluştururken, aslında init
fonksiyonu kullanılır. Fakat Swift bu işlemi bizim yerimize yaptığı için bir ayrıca init
yazarak instance oluşturmayız. Bu duruma syntactic sugar adı verilmektedir. Aşağıdaki kod örneklerinin ikiside aynı şeyi yapar, ve aynı şekilde Employee
struct’tan instance oluşturur.
var archer1 = Employee(name: "Sterling Archer", vacationRemaining: 14)
var archer2 = Employee.init(name: "Sterling Archer", vacationRemaining: 14)
init()
fonksiyonu bizim struct’ı oluştururken belirlediğimiz property ‘lere göre otomatik olarak oluşturulmaktadır.
Örneğin struct’ımızda 2 adet property’imiz olsun;
let name: String
var vacationRemaining = 14
Swift vacationRemaining
için, eğer init esnasında belirtmezsek, varsayılan olarak 14 değerini atayacaktır.
let kane = Employee(name: "Lana Kane")
let poovey = Employee(name: "Pam Poovey", vacationRemaining: 35)
Fakat burada dikkat edilecek önemli bir nokta var: Eğer herhangi bir struct property’sini sabit (let
) olarak tanımlayıp değer atarsak, bu property init fonksiyonu içinde gözükmeyecektir. (Çünkü bir sabite değer ataması bir kez yapılabilir.) Varsayılan değer ataması kullanabilmek için mutlaka değişken (var
) kullanmalıyız.
Struct Computed Property #
Struct’lar iki çeşit property ’ye sahip olabilir.
Stored Property : Struct’ın bir instance ‘ı içinde bir veri parçasını tutan değişken (var
) veya sabittir (let
)
Computed Property : Her erişildiğinde property ‘nin değeri dinamik olarak hesaplanır. Bu durum computed property ‘yi, stored property ile fonksiyonun karışımı haline getirir. Stored property gibi erişilir fakat fonksiyon gibi çalışır.
Daha önceki Employee
struct’ımızın basitleştirilmiş halini örneğimiz olarak kullanalım.
struct Employee {
let name: String
var vacationRemaining: Int
}
var archer = Employee(name: "Sterling Archer", vacationRemaining: 14)
archer.vacationRemaining -= 5
print(archer.vacationRemaining)
archer.vacationRemaining -= 3
print(archer.vacationRemaining)
//ÇIKTI:
//----------------------------------------
//9
//6
Yukarıdaki struct işe yarıyor fakat, bazı değerli bilgileri kaybediyoruz. Struct’ı oluştururken çalışanın izin hakkı sayısını vacationRemainig
değişkeninde tutmuştuk. Fakat çalışan izin kullandıkça çalışanın izin hakkı bilgisini kaybediyoruz.
Bu problemin üstesinden computed property kullanarak gelebiliriz.
struct Employee {
let name: String
var vacationAllocated = 14
var vacationTaken = 0
var vacationRemaining: Int {
vacationAllocated - vacationTaken
}
}
Artık vacationRemaining
’i doğrudan atamak yerine, izin hakkından kullanılan izni çıkararak hesaplıyoruz.
vacationRemaining
‘i öğrenmek istediğimizde standart stored property gibi okuyabiliyoruz.
var archer = Employee(name: "Sterling Archer", vacationAllocated: 14)
archer.vacationTaken += 4
print(archer.vacationRemaining)
archer.vacationTaken += 4
print(archer.vacationRemaining)
//ÇIKTI:
//----------------------------------------
//10
//6
Bu gerçekten güçlü bir özellik. Normal bir property olarak gözüküyor fakat biz okumak istediğimizde hesaplamalar yapılıyor.
ÖNEMLİ NOT : Sabitler (let
) computed property olamazlar. (Neden? çünkü sabitler 😁 değeleri bir kez atanabilir.)
Fakat vacationRemaining
property’sine veri yazamayız, çünkü Swift’e bunu nasıl yapması gerektiğini söylemedik. Bunu yapabilmek için bu property de getter ve setter sağlamalıyız. Getter değeri okuyan kod, Setter ise değeri yazan kod anlamına gelmektedir.
Employee
struct’ımıza getter ve setter ekleyelim.
var vacationRemaining: Int {
get {
vacationAllocated - vacationTaken
}
set {
vacationAllocated = vacationTaken + newValue
}
}
get
ve set
yukarıdaki örnekteki gibi yazılabilmektedir. Burada önemli olan newValue
. Bu bize Swift tarafından otomatik olarak sağlanır ve kullanıcının property ‘ye atamak istediği değeri saklar.
getter ve setter ‘ı sağladıktan sonra vacationRemaining
’i istediğimiz gibi değiştirebiliriz.
var archer = Employee(name: "Sterling Archer", vacationAllocated: 14)
archer.vacationTaken += 4
archer.vacationRemaining = 5
print(archer.vacationAllocated)
//ÇIKTI:
//----------------------------------------
//9
Property Observer #
Property değiştiğinde çalışan kod parçalarına property observer denilmektedir. Property observer iki şekilde olabilir: Property değiştiğinde didSet
, property değişmeden önce willSet
observer’ı çalışır.
Property obsever ‘a neden ihtiyaç duyacağımızı anlamak için aşağıdaki gibi bir kod düşünelim;
struct Game {
var score = 0
}
var game = Game()
game.score += 10
print("Score is now \(game.score)")
game.score -= 3
print("Score is now \(game.score)")
game.score += 1
//ÇIKTI:
//----------------------------------------
//Score is now 10
//Score is now 7
Yukarıdaki kodda, score
property’si değiştiriliyor ve her değişimden sonra güncel score print
ile yazdırılıyor. Fakat bir problem var: en son score değişikliğinden sonra herhangi birşey yazdırılmıyor.
Aynı kodu property observer ile birlikte yazalım.
struct Game {
var score = 0 {
didSet {
print("Score is now \(score)")
}
}
}
var game = Game()
game.score += 10
game.score -= 3
game.score += 1
//ÇIKTI:
//----------------------------------------
//Score is now 10
//Score is now 7
//Score is now 8
Ayrıca didSet
içinde kullanılabilen ve Swift tarafından sağlanan oldValue
sabitide bulunmaktadır. Tabiki willSet
içinde otomatik sağlanan newValue
sabiti de vardır.
struct App {
var contacts = [String]() {
willSet {
print("Current value is: \(contacts)")
print("New value will be: \(newValue)")
}
didSet {
print("There are now \(contacts.count) contacts.")
print("Old value was \(oldValue)")
}
}
}
var app = App()
app.contacts.append("Adrian E")
app.contacts.append("Allen W")
app.contacts.append("Ish S")
//ÇIKTI:
//----------------------------------------
//Current value is: []
//New value will be: ["Adrian E"]
//There are now 1 contacts.
//Old value was []
//Current value is: ["Adrian E"]
//New value will be: ["Adrian E", "Allen W"]
//There are now 2 contacts.
//Old value was ["Adrian E"]
//Current value is: ["Adrian E", "Allen W"]
//New value will be: ["Adrian E", "Allen W", "Ish S"]
//There are now 3 contacts.
//Old value was ["Adrian E", "Allen W"]
didSet
ve willSet
kullanırken dikkatli olmak gerekir. Çünkü property observer ‘lara çok fazla iş yüklemek performans sorunlarına sebep olabilir.
Not : Property observer sabitler (let
) ile kullanılmazlar. (Çünkü sabitlerin değeri yalnızca bir kez belirlenir)
Struct Özel Initializer Nasıl Oluşturulur? #
Initiliazer, kullanılacak yeni bir struct instance’ı hazırlamak için tasarlanmış özel method’lardır. Daha önce struct içerisine koyduğumuz property’lere göre nasıl otomatik olarak oluşturulduklarını görmüştük. Fakat kurala uyduğumuz sürece biz de kendi özel initializer’ımızı oluşturabiliriz. Olmazsa olmaz kuralımız: initializer sona erdiğinde tüm property’lerin bir değeri olmalıdır.
Otomatik oluşturulan initializer’a bakalım;
struct Player {
let name: String
let number: Int
}
let player = Player(name: "Megan R", number: 15)
Swift yeni bir Player
instance’ı için mevcut iki property ile varsayılan olarak initializer oluşturur. Swift’te buna memberwise initializer denilmektedir.
İstersek init
fonksiyonunu kendimiz de yazabiliriz.
struct Player {
let name: String
let number: Int
init(name: String, number: Int) {
self.name = name
self.number = number
}
}
Yukarıdaki kod, az önce yazdığımız kod ile aynı şeyi yapar. Fakat burada initializer bize aittir ve istersek işlevsellik ekleyebiliriz.
Dikkat etmemiz gereken bazı hususlar;
func
keyword’ü yoktur. Sözdizimi açısından bir fonksiyon gibi görünüyor ancak Swift burada initializer’a bir ayrıcalıkta bulunuyor.init
yeni birPlayer
instance’ı oluştursa da, initializer’ların sabit bir dönüş tipi yoktur.- Struct’da kullanılan
name
ile init içerisinde kullanılanname
’i birbirinden ayırmak içinself
keyword’ünü kullanırız. (self.name
property olandır.)
Elbette özel olarak oluşturduğumuz initializer’ın, otomatik oluşturulan memberwise initiliazer gibi çalışması gerekmez. Örneğin, name
değişkeni mutlaka kullanıcı tarafından verilebilirken, number
değişkeni rastgele atanabilir.
struct Player {
let name: String
let number: Int
init(name: String) {
self.name = name
number = Int.random(in: 1...99)
}
}
let player = Player(name: "Megan R")
print(player.number)
Initializer sona erdiğinde tüm property’lerin bir değeri olmalıdır. Eğer number
değişkenine bir değer atanmasaydı yukarıdaki kod hata verecekti.
Struct’a birden fazla initializer ekleyebilir, harici parametre adı ve varsayılan değer gibi özelliklerden yararlanabiliriz. Şunu unutmamak gerekir; custom initializer oluşturduğumuzda, -aksi belirtilmedikçe- Swift tarafından otomatik oluşturulan memberwise initializer’a erişimimizi kaybederiz.
Memberwise Initializer ile Custom Initializer’ın Beraber Kullanımı #
Eğer custom initializer kullanırsak, memberwise initializer’ın kullanımının kalkacağından bahsetmiştik. Fakat Swift’de bu durumun istisnasını oluşturabiliriz.
İstisnayı oluşturmak için, extension
kullanacağız.
struct Employee {
var name: String
var yearsActive = 0
}
extension Employee {
init() {
self.name = "Anonymous"
print("Creating an anonymous employee…")
}
}
// name değişkenini belirleyerek roslin i oluşturabiliriz.
let roslin = Employee(name: "Laura Roslin")
// herhangi bir değişkeni belirlemeden de anon değişkenini oluşturabiliriz.
// bu durumda name değişkeni Anonymous olacaktır.
let anon = Employee()
Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.