Swift’s Property

Burak Gül
Swiflearners
Published in
13 min readFeb 7, 2024

Merhaba arkadaşlar bugün size Swiftte olan property kavramını anlatacağım.Bu yazıda computed property,stored property, get (getter) , set (setter), property observers (willSet,didSet),type property ve instance property nedir bunları anlatmaya çalışacağım.Hadi başlayalım.

Temel olarak ne işe yaradıklarını kısaca özet geçerek başlamak istiyorum daha sonra tek tek değineceğiz zaten. Başta bunları hakkında bir fikir sahibi olmanızı istiyorum ki karışmasın öğrendikçe (Bende öyle oldu :) ).

Photo by Medhat Dawoud on Unsplash

Instance Property : Bir type (Class,Structure,Enumeration)’ın bir nesnesini oluşturduğumuzda sadece o nesneye ait olan propertyler.

Type Property : Sadece oluşturduğumuz o type (Class,Structure,Enumeration)’a ait propertyler.

Stored Property : Constant veya variable olan değerleri bir instance’ın veya type’ın parçası olarak depolayan propertyler.

Computed Property : Constant veya variable olan değerleri bir instance’ın veya type’ın parçası olarak depolamak yerine bu değerleri hesaplayan propertylerdir.

Property Observers : Bir property’nin değerindeki değişiklikleri izlemek için kullandığımız yapılar

Stored Properties

Instance ve Type Property’ler için sonradan değinmek yerine burada tek tek ayırarak açıklamak istiyorum.

En basit haliye “Stored Property” : Constant veya variable olan değerleri bir instance’ın veya type’ın parçası olarak depolayan propertylerdir.

“Instance Stored Property” : Belirli bir Class’ın veya Structure’ın bir instance’ının parçası olarak depolanan bir variable ya da constant’dır.

“Type Stored Property” : Belirli bir Class , Structure veya Enumeration’un type’ının bir parçası olarak depolanan variable ya da constant’dır.

Örnek olarak ders notlarını düşünelim bunun üzerinden ilerleyelim.Okulda bir dersimiz olur ve onun notu olur. Notumuz 0 dan aşağı ve 100 den yukarı olamaz.0 dan düşük bir not girmeye kalkarsak 0, 100 den yukarı bir not girmeye kalkarsak ise 100 olarak sisteme geçmektedir.

class Grade {
static let minumumScore = 0
static let maximumScore = 100
let currentScore : Int

init(_ currentScore : Int) {
if currentScore < 0 {
self.currentScore = currentScore
}else if currentScore > 100 {
self.currentScore = 100
}else {
self.currentScore = currentScore
}
}
}

Burada “minumumScore” ve “maximumScore” birer “Type Stored Property” (constant aynı zamanda let sabit yani.) yani Type’a ait iken “currentScore” ise “Instance Stored Property” dir yani o Instance’a ait sadece. Burada “static” kelimesi bize o değişkenin Type ile ilgili olduğunu anlatmaktadır.

Aynı yapıyı “Structure” ile de oluşturabiliriz ama “Enumeration” ile oluşturamayız çünkü Enumeration’lar Instance Stored Property içeremezler.(currentScore)

struct Grade {
static let minumumScore = 0
static let maximumScore = 100
let currentScore : Int

init(_ currentScore : Int) {
if currentScore < 0 {
self.currentScore = currentScore
}else if currentScore > 100 {
self.currentScore = 100
}else {
self.currentScore = currentScore
}
}
}

Computed Properties

Stored Property’lere ek olarak Instance Property veya Type Property olmasına bakılmaksızın Class , Structure ve Enumeration’larda tanımlanabilen ve değer depolamak yerine diğer propertylerin değerlerini almak(retrieve,get) ve ayarlamak(set) için get(getter) ve set(setter) blocklarını kullanırlar.

Aşağıda sıcaklık ile ilgili örnek var. “fahrenheit” depolanmak yerine hesaplanır sadece. get bloğu biz “fahrenheit” property’sini kullanınca değerini okumaya çalışınca çalışırken , set bloğu ise değer setlemeye ayarlamaya çalışınca çalışır.

Haliyle set bloğunu çalıştırmak için bir parametre vermemiz gerekecek ki o parametre ile ne yapacağını belirlesin kullansın.

struct Temperature {
// Instance Stored Property
var celsius: Double

init(celsius: Double) {
self.celsius = celsius
}

// Instance Computed Property
var fahrenheit: Double {
get {
print("get block worked")
return (celsius * 9 / 5) + 32
}
set(newFahrenheit){
print("set block worked")
celsius = (newFahrenheit - 32) * 5 / 9
}
}
}

var temperature = Temperature(celsius: 100)
print("Water boils at \(temperature.fahrenheit) fahrenheit.") // In here get block works
print("----------------------------------------------")
temperature.fahrenheit = 451 // In here set block works
print("paper spontaneously ignites at \(temperature.celsius) degrees Celsius")

// Output is
//get block worked
//Water boils at 212.0 fahrenheit.
//----------------------------------------------
//set block worked
//paper spontaneously ignites at 232.77777777777777 degrees Celsius

Set bloğunda parametre vermezsek default olarak newValue adlı bir parametre verilir.

struct Temperature {
// Instance Stored Property
var celsius: Double

init(celsius: Double) {
self.celsius = celsius
}

// Instance Computed Property
var fahrenheit: Double {
get {
print("get block worked")
return (celsius * 9 / 5) + 32
}
set{
print("set block worked")
celsius = (newValue - 32) * 5 / 9
}
}
}

var temperature = Temperature(celsius: 100)
print("Water boils at \(temperature.fahrenheit) fahrenheit.") // In here get block works
print("----------------------------------------------")
temperature.fahrenheit = 451 // In here set block works
print("paper spontaneously ignites at \(temperature.celsius) degrees Celsius")
// Output is
//get block worked
//Water boils at 212.0 fahrenheit.
//----------------------------------------------
//set block worked
//paper spontaneously ignites at 232.77777777777777 degrees Celsius

Bu örnek hem get hem de set bloğu olan bir Computed Property içindi.

Şimdi ise sadece get bloğu olan bir Computed Property’e bakalım.Sadece. get bloğu olan Computed Property’lere “Read-Only Computed Property ” denir.

struct Temperature {
// Instance Stored Property
var celsius: Double

init(celsius: Double) {
self.celsius = celsius
}

// Instance Computed Property
var fahrenheit: Double {
get {
print("get block worked")
return (celsius * 9 / 5) + 32
}

}
}

var temperature = Temperature(celsius: 100)
print("\(temperature.celsius) celcius is equal to \(temperature.fahrenheit) fahrenheit.") // In here get block works
//Output is
//get block worked
//100.0 celcius is equal to 212.0 fahrenheit.

Burada Swift Compiler’ı set bloğu olmadığı için get bloğunun olması gerektiğini bildiğinden dolayı get kelimesini ve parantezlerini kaldırabiliriz.

struct Temperature {
// Instance Stored Property
var celsius: Double

init(celsius: Double) {
self.celsius = celsius
}

// Instance Computed Property
var fahrenheit: Double {

return (celsius * 9 / 5) + 32
}
}

var temperature = Temperature(celsius: 100)
print("\(temperature.celsius) celcius is equal to \(temperature.fahrenheit) fahrenheit.") // In here get block works
//Output is
//100.0 celcius is equal to 212.0 fahrenheit.

Swift tek satırlı yerlerde değeri return etmesi gerektiğini bildiğinden return kelimesini de kaldırabiliriz.

struct Temperature {
// Instance Stored Property
var celsius: Double

init(celsius: Double) {
self.celsius = celsius
}

// Instance Computed Property
var fahrenheit: Double {
(celsius * 9 / 5) + 32
}
}

var temperature = Temperature(celsius: 100)
print("\(temperature.celsius) celcius is equal to \(temperature.fahrenheit) fahrenheit.") // In here get block works

//Output is
//100.0 celcius is equal to 212.0 fahrenheit.

Not: Swift dilinde Computed Property’ler ya Read-Only Computed Property olabilir(yani sadece get bloğu kullanabiliriz) ya da get ve set bloklarını beraber kullanmalıyız. Sadece set bloğunu kullanamıyoruz maalesef.

Not : Computed Property’leri variable olarak (var kelimesi) ile deklare etmeliyiz çünkü değerleri sabit değildir. Let kelimesi yalnızca constant’lar için kullanılır.

Property Observers

Bir property’nin değerindeki değişiklikleri izleyen ve ona cevap veren yapılardır.

Property’nin değeri (value) setlendiğinde Property Observer çağrılır , property’nin yeni değeri şuanki,mevcut değeri ile aynı olsa bile Property Observer çağrılır.

Bir property üzerinde “willSet” ve“didSet” observer’larından yalnızca birini veya her ikisini de tanımlama şansımız vardır.

willSet Observer’ını implemente edersek , yeni property değerini (new property value) içeren constant bir parametre olarak iletilir. willSet implementasyonu için parametre adı belirtebiliriz eğer belirtmezsek default olarak parametre adı “newValue” ile kullanılabilir hale gelir.

didSet Observer’ını implemente edersek , eski property değerini (old property value) içeren bir constant parametre olarak iletilir. didSet implementasyonu için parametre adı belirtebiliriz eğer belirtmezsek default olarak “oldValue” parametre adını kullanabiliriz. Kendi “didset” Observer’ı içinde property’e yeni bir değer atarsak atadığımız yeni değer az önce ayarlananın yerini alır.

Miras aldığımız Computed, Stored Property’lere ve tanımladığımız Stored Property’lere Property Observer’lar eklenebilir.

Miras aldığımız bir property için subClassta bulunan bu property’i override ederek Property Observer ekleriz.

Şimdi tanımladığımız Stored Property’lere Property Observer eklemeyi göstererek başlayalım.

class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

Burada “totalSteps” adında bir Stored Property’miz (Instance Stored Property) var. willSet içinde eklenecek yeni değerle işlem yapılıyor yeni değer eklenmeden/değiştirilmeden önce. didSet içinde ise değer atandıktan sonra eski ve yeni değer kullanılarak başka bir işlem yapılıyor.

Tanımladığımız Stored Property(Instance Stored Property) Structure içinde de kullanılabilir. O halde şöyle de yazabiliriz.

struct StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
var stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

Burada dikkat etmemiz gereken konu Structure nesnesi variable olmak zorunda , constant olursa property’leri variable olsa bile değiştiremeyiz.Bunun sebebi Value Type , Reference Type’dır.

Şimdi yukarıdaki örneklerde ilk başlangıç değerini 0 olarak veriyoruz ama init(Constructor başka dillerden geliyorsanız) ile de verebiliriz farkeden bir şey olmayacaktır.

class StepCounter {
var totalSteps: Int {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
init(totalSteps: Int) {
self.totalSteps = totalSteps
}
}
let stepCounter = StepCounter(totalSteps: 0)
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
struct StepCounter {
var totalSteps: Int {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}

init(totalSteps: Int) {
self.totalSteps = totalSteps
}
}
var stepCounter = StepCounter(totalSteps: 0)
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

Şimdi ise Enumeration’larda Instance Stored Property kullanamadığımız için Enumeration’larda Type Stored Property ile aynı örneği yapalım.

enum ExampleEnum {
static var totalSteps : Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}

case instance1
case instance2
}

ExampleEnum.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
ExampleEnum.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
ExampleEnum.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

Peki Enumeration’larda Type Stored Property için Property Observer ekledim peki Class ve Structure için yapamaz mıyım ? Elbette yapabilirim.

class StepCounter {
static var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}

StepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
StepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
StepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
struct StepCounter {
static var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
StepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
StepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
StepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps

Burada Instance Stored Property’den farkları şu : Property başına “static” kelimesini ekliyoruz ve kullanacağımız zaman Instance (nesne) oluşturmamıza gerek yok çünkü Type ile alakalı bir property Instance ile alakalı değil.

Şimdi ise Apple bize diyor ki “Tanımladığınız Computed Property için : bir observer oluşturmaya çalışmak yerine , değer değişikliklerini gözlemlemek ve yanıtlamak için property’nin setter’ını kullan.”.

Biz şimdi tam tersini deneyelim oluyor mu olmuyor mu diye :) .

Computed Property olduğu için getter’a sahip olmalı kesinlikle ve setter yerine willSet didSet karışımlarını deneyelim.

class StepCounter {
var totalSteps: Int {
get{
print("get çalıştı")
return 1
}
}
}

var stepCounter = StepCounter()
print(stepCounter.totalSteps)
//Output is
//get worked
//1

Burası gayet normal olan.

Burada göreceğiniz üzere kendi tanımladığımız Computed Property ‘e Property Observer olan “willSet” ve “didSet” i eklememize izin vermiyor.

Şimdi ise setter ile yapınca halloluyor.

Şimdi ise miras aldığımız Stored Property’lere Property Observer eklemeye bakalım.Bu yüzden Class’lar ile işlem yapacağız Structure ve Enumeration’larda miras özelliğimiz yok.

Şimdi Stored Propertylerden başlayacağım.Base Class’ımız Vehicle olsun. “currentSpeed” bir Stored Property dir. Double bir değişken depolar.

class Vehicle {
var currentSpeed : Double = 0 {
willSet {
print("Vehicle's willSet block worked. The value of currentSpeed variable will be \(newValue)")
}
didSet {
print("Vehicle's didSet block worked.New value of currentSpeed is \(currentSpeed) and old value is \(oldValue)")
}
}
}

currentSpeed”ın değeri init (Constructor)ile de verilebilir.

class Vehicle {
//MARK: - Stored Property
var currentSpeed : Double{
willSet {
print("Vehicle's willSet block worked. The value of currentSpeed variable will be \(newValue)")
}
didSet {
print("Vehicle's didSet block worked.New value of currentSpeed is \(currentSpeed) and old value is \(oldValue)")
}
}

//MARK: - init
init(currentSpeed: Double) {
self.currentSpeed = currentSpeed
}
}

Ben bu 2. yazdığım şekilde ilerleyeceğim.

SubClass olarak da Car adında bir Class oluşturalım.

class Car : Vehicle {
//MARK: - Stored Property
override var currentSpeed: Double {
willSet {
print("Car's willSet block worked. The value of currentSpeed variable will be \(newValue)")
}
didSet {
print("Car's didSet block worked. New value of currentSpeed is \(currentSpeed) and old value is \(oldValue)")
}
}
//MARK: - init
override init(currentSpeed: Double) {
super.init(currentSpeed: currentSpeed)
}
}

Burada override ettiğimiz kısım “currentSpeed” in Property Observerlarıdır.

init ile ilk değeri verdiğimizde Property Observerlar çalışmaz sebebi basit Property’nin değerinde değişiklikleri izlemek ve cevap vermekti amaç.Değişiklik olması için önce ilk değerin olması lazım.

Diyebilirsiniz ki peki Stored Property’miz optional olsa ve ilk değeri atadığımızda çalışır mı ? (ilk değeri NULL dır daha sonra bir değer atadık gibisinden) Hayır çalışmaz çünkü NULL/nil ramde yer kaplayıp kaplamamayı belirtir.

Şimdi Car Class’ındaki “currentSpeed” property’sinin Property Observer’larını override ettik ama normalde methotlarda override ettiğimizde SuperClass’ın methotları çalışmazdı çalıştırmak istersek super.methodName() ile erişirken burada öyle bir durum yok. Çok garip bir durum var. Önce SubClass’ın Property’sinin “willSet” i daha sonra SuperClass’ın Property’sinin “willSet” “didSet” i ve en son olarak ise SubClass’ın Property’sinin “didSet” i çalışmaktadır.Bunun mantığı nedir ben de çözemedim ama farkettim yazmak istedim :)

Şimdi ise miras aldığımız Computed Property’lere Property Observer eklemeye bakalım.Bu yüzden Class’lar ile işlem yapacağız Structure ve Enumeration’larda miras özelliğimiz yok.

Yukaridaki örnekten devam edeceğim. “costPerKM” adında Computed Propert ekleyelim ve “currentSpeed”e bağlı olsun.

class Vehicle {
//MARK: - Stored Property
var currentSpeed : Double{
willSet {
print("Vehicle's willSet block worked. The value of currentSpeed variable will be \(newValue)")
}
didSet {
print("Vehicle's didSet block worked.New value of currentSpeed is \(currentSpeed) and old value is \(oldValue)")
}
}
//MARK: - Computed Property
var costPerKM: Double {
get {
return currentSpeed / 100.0
}
set{
print("Vehicle's setter block worked. New value of costPerKM is \(newValue)")
}

}
//MARK: - init
init(currentSpeed: Double) {
self.currentSpeed = currentSpeed
}
}
class Car : Vehicle {
//MARK: - Stored Property
override var currentSpeed: Double {
willSet {
print("Car's willSet block worked. The value of currentSpeed variable will be \(newValue)")
}
didSet {
print("Car's didSet block worked. New value of currentSpeed is \(currentSpeed) and old value is \(oldValue)")
}
}
//MARK: - init
override init(currentSpeed: Double) {
super.init(currentSpeed: currentSpeed)
}
}

Şimdi Car Class’ımızda bu Computed Property’yi override etmeye kalkışalım.

Hiçbir şey yazmazsak Stored Property zannediyor.

Sadece get veya sadece set yazarsak olmuyor.Sebebi ise SuperClass’ta hem getter hem setter var diyor.

Hem getter hem setter ı ekleyince override işlemimiz tamamdır.

Uzun bir şekilde deneye deneye okuya okuya şu sonucu çıkarıyoruz ; Stored Property’lerde(ister miras alalım superClasstan ister kendimiz tanımlayalım) değerin değişikliğini izlemek için Property Observer ‘ları(willSet , didSet) i kullanıyoruz .

Computed Property’lerde ise değer değişikliğini izlemek için Property Observer’lar(willSet,didSet) yerine setter kullanıyoruz.

Static’ler için denerseniz static variable/property’leri override edemezsiniz.

Vehicle Class’ını değiştirerek static yapısını anlamanız için ekledim.Yine de aşağıda static yapısını hemen anlatayım.

Type Property And Instance Property

Instance Property : Bir type (Class,Structure,Enumeration)’ın bir nesnesini oluşturduğumuzda sadece o nesneye ait olan propertyler.

Type Property : Sadece oluşturduğumuz o type (Class,Structure,Enumeration)’a ait propertyler.

Şöyle düşünelim.Human yapısı(Structure anlamında değil) oluşturduğumuzda soyut bir kavramdır ama Burak , Batuhan … gibi insanlar birer nesnedir artık bizim için soyuttan somuta geçmiştir.Burada Human yapısı bizim için Class iken Burak , Batuhan .. gibi insanlar ise Instance yapısını temsil eder.

Şimdi yapımızı biraz farklı düşünelim.Diyelimki bir işyerine sahipsiniz ama kaç çalışanınız olduğunu bilmiyorsunuz.Her işçi aldığınızda tek tek baştan mı sayardınız yoksa bir değişkende tutup onun üzerinden mi giderdiniz. 2.yöntem daha sağlıklı çünkü Big-O O(n) yerine O(1) olacaktır bu da hızlı bir şekilde erişmemize olanak sağlayacktır.(Detayları Algoritma Analizidir :) ).

class Worker {
static var countOfWorker = 0
var name : String
var age : Int

init(name: String, age: Int) {
self.name = name
self.age = age
Worker.countOfWorker += 1
}
}

print(Worker.countOfWorker)
var worker1 = Worker(name: "Burak", age: 23)
print(Worker.countOfWorker)
print(worker1.name)
//Output
//0
//1
//Burak

Mesela burada Worker adında bir yapı oluşturduk. Hiç instance oluşturmadan da Worker yapısına ait özellik oldu countOfWorker. worker1 ‘in ise name özelliği Instance Type oldu.

Struct ile de yaparız.

struct Worker {
static var countOfWorker = 0
var name : String
var age : Int

init(name: String, age: Int) {
self.name = name
self.age = age
Worker.countOfWorker += 1
}
}

print(Worker.countOfWorker)
var worker1 = Worker(name: "Burak", age: 23)
print(Worker.countOfWorker)
print(worker1.name)
//Output
//0
//1
//Burak

Aynı yapıyı Enumeration ile de yaparız.

enum Worker {
case person(name: String, age: Int)

static var countOfWorker = 0

init(name: String, age: Int) {
self = .person(name: name, age: age)
Worker.countOfWorker += 1
}

var name: String {
switch self {
case .person(let name, _):
return name
}
}

var age: Int {
switch self {
case .person(_, let age):
return age
}
}
}

print(Worker.countOfWorker)
let worker1 = Worker(name: "Burak", age: 23)
print(Worker.countOfWorker)
print(worker1.name)

print(Worker.countOfWorker)
let worker2 = Worker(name: "Batuhan", age: 20)
print(Worker.countOfWorker)
print(worker2.name)
//Output
//0
//1
//Burak
//1
//2
//Batuhan

worker1 , worker2 ile ulaşabildiğimiz property’ler Instance Property iken Worker. ile ulaştığımız countOfWorker Type Property’dir.

Dokümandan Lazy Stored Property ve Property Wrapper’ı eklemedim yazıma.Onları merak edenler ise dokümana bakabilirler.

Buraya ise kendi aldığım notun bir resmini bırakıyorum

Yanlışlarım eksiklerim için developerburakgul@gmail.com mail adresimden bana ulaşabilirsiniz.

Kaynaklar

https://chat.openai.com/

--

--