--
В тази лекция ще разгледаме вътрешната организация на паметта, когато пишем нашата програма, за да можем да спазваме законите, определени от Swift. Така приложенията ще могат да пестят системни ресурси и да работят по-бързо. Също така ще обърнем внимание на шабноните (функции, класове, структури и изброими типове) в Swift.
Всяка програма използва памет. Образно можем да си мислим, че това е работната площадка на компютъра (процесора му) и той чете данни от там и ги записва там. Ние като потребители на тази умна система трябва да спазваме правилата, за да може да използваме всички ресурси, които тя ни предоставя. Ако разхищаваме и не следваме правилата, вероятността да стигнем до непредсказуемо състояние е голяма.
Променливите, които ползваме в нашите програми до сега, се пазят в паметта. В зависимост вида им, те се съхраняват в различни части от паметта. Стойностните типове се записват в програмния стек - това е памет, която се управлява от Swift автоматично. Ние нямаме контрол над нея. Приемаме, че Swift се грижи вместо нас. По-интересни са референтните типове, които се записват в хийп-а. Това е друга памет, която се използва за съхранение на големи обеми от данни. Можем да си мислим, че е неизчерпаема, понеже операционната система има механизми, чрез които може да я разширява, но все пак е с крайно голям обем.
Важно е да знаем, че класовете са референтни типове. Те се заделят в хийпа (този тип памет) и имаме референция (знание, къде се намират в паметта) към тях.
Това е механизмът, който се ползва от Swift за управление на паметта.
Една инстанция се пази в паметта докато има референции към нея. Ако няма повече референции, тогава тя бива деинициализирана. Стандартните референции са силни референции (strong), защото задържат паметта сочена от тях и тя няма да бъде деинициализирана.
Можем да говорим за ARC при класовете (т.е. референтните типове) и клоужърите. Типовете, които се предават по стойнонст като структури и изброими типове, не са част от ARC управлението на паметта. Те се управляват от друг различен механизъм, които приемаме за даденост.
Автоматичното управление на паметта ни позволява да се фокусриаме над истинските проблеми, а не над управлението на паметта в компютъра. Има различни механизми за управление на памет. Първият - най-базовият е ръчно управление на памет. Среща се в езиците като C, C++. Характерно е, че всяка динамично заделена инстанция заема памет и тази памет трябва експлицитно да бъде освободена след като няма да бъде използвана за напред. Второ ниво на автоматизиране е ARC (механизмът използван от Swift). Характерно за него е, че всяка истанция знае броя референции към нея. Т.е. ако имам две променливи, които сочат конкретна инстанция, то тази инстанция знае, че има поне две референции. Освобождаването на паметта настъпва автоматично, когато броят на референциите стане равен на нула и вече никой няма да използва обекта. Последното ниво на автоматизация (пълна автоматизация) е механизъм, който разчита на garbage collector (гарбидж-колектор). Това е алгоритъм, който се грижи за автоматичното разпознаване на ненужните обекти и освобождаването им. Предимството му е, че програмистът не трябва да се занимава с управлението на паметта - което не е напълно вярно за ARC. Непредсказуемостта му на изпълнение (кога ще се стартира) е основен недостатък.
За да илюстрираме освобождаването на памет, ще отпечатване текст при автоматичното извикване не deinit метода на класа.
class Car {
private var name:String
init(name:String) {
self.name = name
print("Initalize a car instance with name: \(name)")
}
deinit {
print("Deinit a car instance with name \(self.name) ")
}
}
var tesla:Car? = Car(name: "Tesla")
tesla = nil
Заделянето на памет настъпва когато инициализраме нова инстанция - обект от даден тип. Това става, когато неявно извикаме init
метода на един клас.
Освобождаването на заетата памет става, когато нямаме повече референции към даден обект. Това е факт, когато занулим съответните променливи, както в примера по горе.
В общия случай ARC се справя с управлението на паметта, с изключения когато се получи цикъл от референции. Тогава броячите на всяка инстанция не стигат до 0 и паметта не може да бъде освободена.
Точно такива цикли са причината за memory leak-ове ("изтичане на памет"). Следва пример, който илюстрира цикъл от референции.
Сега ще дадем пример за референтен цикъл.
Нека да реализираме следните два класа:
- Книга (има точно един автор)
- Автор (има точно една книга)
Това е частен случай на реалността, но е напълно достатъчен да покаже проблема.
class Book {
let title:String
let author:Author
var genre:String?
var pages:Int = 0
init(title:String, author:Author) {
self.title = title
self.author = author
}
deinit {
print("Deinit a book instance with title \(self.title) ")
}
}
class Author {
let name:String
//трябва да добавим weak пред пропъртито, за да
//можем да прекъснем цикъла
//weak var book:Book?
var book:Book?
var age:Int
var isAlive:Bool
init(name:String, age:Int, isAlive:Bool) {
self.name = name
self.age = age
self.isAlive = isAlive
}
deinit {
print("Deinit an Auhtor instance with name \(self.name) ")
}
}
var author:Author? = Author(name: "Достоевски",age: 73, isAlive: false)
var book:Book? = Book(title: "53",author:author! )
author!.book = book
//не можем да го прекъснем
book = nil
author = nil
Можем да направим следните изводи:
- ARC е добър, когато няма цикли.
- Ако имаме цикли от референции, трябва да използваме съответните механизми, за да ги разрешим.
- ARC има нужда от малка подсказка, за да може да реши проблема.
weak
реферeцията е такава, която позволява на ARC да деинициализира променливата, сочена от референцията. В резултат на това, тази променлива има стойност nil
. Не можем да направим константа и типът е винаги опционален (optional).
Трябва да използваме такава референция, когато реферираният елемент може да бъде заменен.
Наблюдателите (observers) на пропъртита не се активират, когато ARC промени стойността на nil
.
При езиците с гарбидж-колектор (алтернативен механизъм на ARC) weak референциите имат друго значение. Те често се ползват, когато се реализира кеш от обекти, който трябва да се освободи, само когато няма достатъчно памет. Освобождаването се извършва от гарбидж-колектора. При ARC weak
се различава и не може да бъде ползвана по този начин, тъй като референциите(паметта) биват освободени веднага.
unowned
реферeнцията е такава, която позволява на ARC да деинициализира променливата, но тук интересното е, че 'дължината на живот' на тази променлива е същата или по-дълга. Т.е. няма да има случай в който тя да сочи към мястото в паметта, а то да е nil
.
Трябва да се използва unowned
когато сме сигурни, че референцията ще сочи инстанция, която няма да е деинициализирана. Ако се опитате да достъпите такава инстанция ще се получи грешка при изпълнение (runtime грешка).
Ето и пример, който можем да разрешим с помощта на unowned
модификатора.
Да се реализира примерна йерархия:
- Студент (който има студентска книжка)
- Студентска книжка (StudentCard, която има студент)
class Student {
let name: String
var age = 19
var card:StudentCard?
init(name:String, age:Int) {
self.name = name
self.age = age
print("Init a student instance.")
}
func printStrudent() {
print("Name: \(name) ")
print("Age: \(age) ")
}
deinit {
print("deInit a student instance")
}
}
class StudentCard {
let university: String
let number: String
// unowned(unsafe) let student:Student
// unowned let student:Student
let student:Student
init(uni:String, number:String, student:Student) {
university = uni
self.number = number
self.student = student
}
deinit {
print("deInit a student-card instance - \(self.number)")
}
}
var student:Student? = Student(name: "Г. Петров", age: 21)
var studentId:StudentCard? = StudentCard(uni: "СУ св. 'Климент Охридски'", number: "35123", student: student!)
student?.card = studentId
studentId = nil
student = nil
Можем да използваме варианта unowned(unsafe)
, където проверката дали паметта не е занулена, е изключена. Този вариант е по-бърз от стандартния, но носи рискове в случаите, когато инстанцията е деалокирана.
Референтни цикли можем да получим когато използваме и други референтни типове, примерно клоужъри.
Понеже тялото на клоужър (closure) запомня (capture) променливи и ако го използваме в клас - запомня self
, тогава можем да стигнем до цикъл. Тъй като клоужърите и те са референтен тип, те могат да образуват цикъл от референции.
Сега ще разгледаме следния пример:
class DataType {
let name:String
var properties: Array<String> //[String]
let prettyPrint = true
init(name:String) {
self.name = name
properties = []
}
lazy var toSwift: () -> String = {
//списъка с променливите в клоужъра ни
//позволява да упражним допълнителен контрол
// [unowned self, name = self.name] in
var swiftCode:String = "class \(self.name) {"
if self.prettyPrint {
swiftCode += "\n"
}
for property in self.properties {
if self.prettyPrint {
swiftCode += "\t"
}
swiftCode += property
}
if self.prettyPrint {
swiftCode += "\n"
}
swiftCode += "}"
return swiftCode
}
deinit {
print("Deinit dataType instance \(self.name)")
}
}
var student:DataType? = DataType(name: "Student")
student?.properties.append("var name:String = \"Без име\"")
print(student!.toSwift())
student = nil
За да решим проблема трябва да използваме списъка с променливите, които клоужъра зaпомня и да ги определим като unowned
или weak
. Този списък се нарича capture list - или списък със запомнени променливи, които се използват в тялото на клоужъра. Той позволява добавяне на допълнителни модификатори към променливите и дори дефиниране на нови, които пази клоужъра.
Ето и един пример, който показва каква е разликата между клоужър с и без такъв списък.
var myA = 0
var myB = 0
let f: () -> () = { [myA] in
print("A = \(myA)")
print("B = \(myB)")
}
myA = 7
myB = 7
f()
//A = 0
//B = 7
Ето как изглежда примера за референтни типове.
var myA = Car(name: "Tesla A 1.0")
var myB = Car(name: "Tesla B 1.0")
let f: () -> () = { [weak myA] in
print("A = \(myA?.name ?? "???")")
print("B = \(String(describing: myB.name))")
}
myA.name = "Tesla A 2.0"
myB.name = "Tesla B 2.0"
//"Tesla A 2.0"
//"Tesla B 2.0"
На базата на примера можем да направим промяна, като добавим [unowned self]
преди параметрите на клоужъра.