Swift 4 Otomatik Referans Sayacı (Automatic Reference Counting) Mekanizması #22

Özcan Akkoca
Etiya
Published in
6 min readJun 29, 2017

--

Swift’te Java ve .NET ortamlarındaki gibi bir çöp toplama mekanizması yoktur. Apple Objective-C’ye böyle bir mekanizmayı isteğe bağlı olarak eklemişse de bundan pişman olmuştur. Otomatik referans sayacı mekanizması işlev olarak çöp toplama mekanizmasıyla benzerdir. Yani her iki mekanizmanın da amacı heap’te artık kümsenin kullanmadığı nesnelerin otomatik serbest bırakılmasıdır. Ancak çöp toplama mekznizması ile otomatik referans sayacı mekanizmasının işletilmesinde önemli teknik farklılıklar vardır. Hangi mekanizmanın daha etkin olduğu konusunda da tartışmalar devam etmektedir. Ancak Apple’ın tercihi Swift’te Otomatik Referans Sayacı (ORS) olmuştur.

Belli bir anda heap’te yaratılmış lan bir nesneyi belli bir sayıda referans göstermektedir. Buna o nesnenin referans sayacı denilmektedir. Örneğin:

var s = Sample()

var t = s

— ->

Ok ile gösterilen noktada Sample nesnesi iki referans tarafından gösterilmektedir. O halde söz konusu Sample nesnesinin o noktadaki referans sayacı 2'dir. Nesnenin referans sayacı artabileceği gibi eksile de bilir. Nesnenin referans sayacı sıfıra geldiğinde artık nesneyi hiçbir referans göstermiyor durumdadır. Zaten bu noktadan sonra nesneyi bir referansın göstermesi de mümkün değildir. İşte Swift’te nesnenin referans sayacı sıfıra düşer düşmez ORS mekanizması yoluyla nesne heap’ten silinir. Örneğin:

class Sample {

var a: Int

init()

{

a = 0

}

init(a: Int)

{

self.a = a

}

//…

}

func bar(s: Sample)

{

// RS = 3

var m = s

// RS = 4

}

func foo()

{

var s = Sample() // RS’si izlenecek nesne

// RS = 1

var k = s

// RS = 2

bar(k)

// RS = 2

}

// RS = 0

foo()

// RS = 0

Bu örnekte foo çağrıldığında onun içerisinde yaratılan Sample nesnenin referans sayacı duruma göre artıp azalmıştır. foo metodunun çağrılması bitince o nesnenin referans sayacı sıfıra düşmüştür. İşte ORS mekanizması o nesnenin referans sayacı sıfıra düşer düşmez deterministik bir biçimde (yani zamanı ve yeri belirlenecek biçimde) nesneyi heap’ten yok eder.

Bir nesnenin referans sayacının düşürülmesinin bir yolu onu gösteren referansa nil değeri yerleştirmektir. Tabii bu durumda türün de seçeneksel olması gerekmektedir. Örneğin:

var s: Sample? = Sample()

// nesnenin referans sayacı 1'dir.

s = nil

// artık nesnenin referans sayacı 0 durumdadır

ORS mekanizmasındaki önemli handikaplardan biri birbirini gösteren sınıf nesneleridir. Bu durumda bir döngüsellik oluşur. İki nesnenin de referans sayacı bir türlü sıfır olamaz.

Şekilden de görüldüğü gibi iki sınıf nesnesinin property’leri birbilerini gösteriyor durumdaysa onları kullanan programcı kullandığı referansları nil yaparak ya da onların yok olmasını sağlayarak bu iki nesnenin yok edilmesine neden olamaz. İşte bu sorunu çözmek için dile zayıf (weak) referans ve sahipsiz (unowned) referans kavramları sokulmuştur.

weak bir referans seçeneksel türlerle bildirilebilir. Bunun için referans bildiriminin başına weak anahtar sözcüğü getirilir. Örneğin:

var s: Sample? = Sample()

weak var k: Sample?

k = s

Swift’te weak ya da unowned olmayan referanslara güçlü referanslar “strong references” de denilmektedir. Zayıf referanslar nesnenin referans sayacını artırmamaktadır. Yani yukarıdaki örnekte k = s atamasına rağmen nesnenin referans sayacı hala 1'dir. Dolayısıyla biz k’ya nil atayarak nesnenin referans sayacını da azaltamayız. Ancak s’ye nil atarsak nesnenin referans sayacını azaltıp sıfır yaparız:

var s: Sample? = Sample()

weak var k: Sample?

k = s

s = nil // nesne artık silinecek

Kuvvetli bir referansın gösterdiği yerdeki nesne yok edildiğinde sistem onu gösteren bütün zayıf referansların içerisine nil değerini yerleştirir. Örneğin:

var s: Sample? = Sample()

weak var k: Sample?

k = s

s = nil // nesne artık silinecek

print(k == nil ? “k == nil” : “k != nil”)

Burada ekrana “k == nil” yazısı çıkacaktır.

Şüphesiz yeni yaratılan bir nesnenin adresinin zayıf bir referansa atanması anlamsızdır. Çünkü o nesne yaratılır yaratılmaz referans sayacı da artırılmadığı için yok edilecektir. Örneğin:

weak var s: Sample? = Sample() // anlamsız ama geçerli

Aşağıdaki kod parçasında counry referansına nil atadığımızda Country ve City nesneleri yok edilemeyecektir:

class City {

var cityName: String

var country: Country?

init(cityName: String)

{

self.cityName = cityName

}

}

class Country {

var countryName: String

var capital: City

init(countryName: String, capitalName: String)

{

self.countryName = countryName

capital = City(cityName: capitalName)

capital.country = self

}

}

var country: Country? = Country(countryName: “Türkiye”, capitalName: “Ankara”)

country = nil // Burada döngüsel durum yüzünden nesneler yok edilemeyecektir

country referansına nil atadığımızda hala country nesnesini gösteren bir referans olduğu için Country nesnesinin referans sayacı 0'a düşmez, 1'de kalır. Böylece City nesnesinin referans sayacı da 0'a düşmemiş olur. İşte çözüm zayıf referans kullanmaktır. City sınıfındaki country referansı zayıf olarak bildirilirse bu döngüsel durum sorunu çözülür. Çünkü zayıf referans nesnenin referans sayacını artırmayacaktır. Bu durumda country referansı nil’e çekildiğinde artık Country nesnesini gösteren bir referans kalmamış olacaktır. Bu durumda ORS Country nesnesini yok edecektir. Böylece City nesnesini de gösteren referans kalmamış olacaktır. Dolayısıyla o da yok edilecektir.

Yukarıdaki örnekte her iki sınıftaki referansın da zayıf olamayacağına dikkat ediniz. Ayrıca kodun yazımına göre Country sınıfındaki capital referansı da zayıf olarak bildirilemez.

Sahipsiz (unowned) referanslar da zayıf referanslar gibi nesnenin referans sayacını artırmazlar. Ancak sahipsiz referanslar seçeneksel olmak zorunda değildir. Sahipsiz bir referansın gösterdiği yerdeki nesne yok edilirse sahipsiz referansın içindeki değer değişmez. Ancak bu adresin geçerliliği de yoktur. Dolayısıyla sahipsiz referansın gösterdiği yerdeki nesne yol edilmişse bizim artık o sahipsiz referansı kullanmamamız gerekir. Kullanırsak exception oluşur. Örneğin:

var s: Sample? = Sample()

unowned var k: Sample

k = s!

s = nil

k.a = 10 // exception oluşur

O halde zayıf referanslarla sahipsiz referanslar arasındaki benzerlikler ve farklılıklar şunlardır:

1) Hem zayıf hem de sahipsiz referanslar nesnenin referans sayacını artırmazlar.

2) Zayıf referanslar seçeneksel türden olmak zorundadır. Ancak sahipsiz referanslar seçeneksel türden olamazlar.

3) Zayıf referansların gösterdiği nesne yok edilince sistem zayıf referanslara nil değeri yerleştirir. Ancak aynı durum sahipsiz referanslar için geçerli değildir.

Sahipsiz referansları aşağıdaki gibi bir dönsüsellikte kullanabiliriz:

class City {

var cityName: String

unowned var country: Country

init(cityName: String, country: Country)

{

self.cityName = cityName

self.country = country

}

deinit {

print(“City deinit”)

}

}

class Country {

var countryName: String

var capital: City?

init(countryName: String)

{

self.countryName = countryName

}

deinit {

print(“Country deinit”)

}

}

var country: Country? = Country(countryName: “Türkiye”)

var capital = City(cityName: “Ankara”, country: country!)

country = nil

Pekiyi ne zaman zayıf referans ne zaman sahipsiz referans kullanmalıyız? Eğer referansımız seçeneksel olacaksa bu durumda mecburen zayıf referans, seçeneksel olmayacaksa sahipsiz referans kullanırız.

Sınıfların deinit Metotları

Nasıl Java ve C#’ın Finalize metotları, C++’ın destructor metotları varsa Swift’te de sınıfların deinit metotları vardır. Bir sınıf nesnesi ORS mekanizması tarafından heap’ten yok edilmeden hemen önce o nesne için o sınıfın deinit metodu çağrılır. deinit metodunun parametresi ve geri dönüş değeri yoktur. Bildiriminin genel biçimi şöyledir:

deinit {

//…

}

Örneğin:

import Foundation

class Sample {

var a: Int

init()

{

a = 0

print(“Sample init”)

}

init(a: Int)

{

self.a = a

print(“Sample init”)

}

deinit {

print(“Sample deinit”)

}

}

var s: Sample? = Sample()

print(“test1”)

s = nil

print(“test2”)

Swift’te tıpkı C++’ta olduğu gibi deinit metodu deterministiktir. Buradaki deterministik terimi deinit metodunun tam olarak hangi çağrılacağının belirli olması anlamına gelir. Halbuki Java ve C#’taki Finalize metotları deterministik değildir. Çünkü bu dillerde nesnenin referans sayacı 0'a düştüğünde nesnenin hangi süre sonra heap’ten yok edileceği belli değildir. Dolayısıyla Java ve C#’taki Finalize metotlarının tam olarak hangi hangi noktada çağrılacağı belirsizdir. Maalesef o dillerde bu Finalize metotları nda onların bu özelliklerinden dolayı pek çok şey yapılamamaktadır. Halbuki Swift’te nesnenin referans sayacı 0 olur olmaz hemen deinit metodu çağrılmaktadır.

İçerme ilişkisinde Swift’te içeren nesnenin referans sayacı 0 olunca hemen içeren nesne için deinit metodu çağrılır. Bu çağrıdan sonra içerilen nesne silinecek ve onun için deinit metodu çağrılacaktır. Böylece biz Swift’te içeren nesnenin deinit metodu içerisinde içerilen nesne referansını kullanabiliriz. (Bunu Java ve C#’ta yapamaycağımızı anımsayınız).

Türetme durumunda türemiş sınıfın deinit metodu ana bloğun sonunda taban sının deinit metodunu olarak çağırmaktadır. Bunun için programcının birşey yapmasına gerek yoktur. Bu çağrılma biçimi tamamen C++’ta olduğu gibidir. Örneğin:

class A {

init()

{

print(“A init”)

}

deinit {

print(“A deinit”)

}

}

class B : A {

override init()

{

print(“B init”)

super.init()

}

deinit {

print(“B deinit”)

}

}

var b: B? = B()

b = nil

print(“ok”)

deinit metotları programcı tarafından çağrılamaz. Onları ya ORS mekanizması ya da yukarıda belirtildiği gibi türemiş sınıfın deinit metotları ana (bloğun sonunda) gizlice çağırmaktadır.

Türetme durumunda türemiş sınıfn deinit metodunun programcı tarafından yazılmadığını ancak taban sınıfın deinit metodunun yazılmış olduğunu varsayalım. Türemiş sınıf nesnesinin referans sayacı 0'a düştüğünde ORS mekanizması türemiş sınıfta bir deinit olmadığı için türemiş sınıfn deinit metodunu çağırmaya çalışmayacaktır. Ancak taban sınıfın deinit metodu yine çağrılacaktır.

--

--