Урок 21. Инициализаторы Класса

11 Апреля 2023

Инициализация свойств.

Прежде чем понять что такое инициализаторы, нам необходимо вспомнить такое понятие как инициализация свойств

Прежде чем работать со свойством мы обязаны его инициализировать, либо сделать опциональным, то есть мы не cможем написать следующим образом:

				
					let numberOne: Int
let nummberTwo = 5

let result = numberOne + nummberTwo
				
			

При запуске, этот код выдаст ошибку, которая будет гласить: “Constant ‘numberOne’ used before being initialized”, то есть мы пытаемся использовать свойство до его инициализации, до того момента как мы зададим ему какое то значение. В память мы помещаем только то что содержит значение, если значения нет, то это просто зарезервированная область. Мы могли бы сделать его опциональным:

				
					var numberOne: Int?
let nummberTwo = 5

let result = (numberOne ?? 0) + nummberTwo
				
			

В таком случае мы можем использовать свойство, но обязаны извлечь из него опциональное значение, я здесь использую значение по умолчанию.

Обратите внимание, что опциональное  свойство необходимо сделать переменной, потому что оно содержит в себе заглушку в виде nil, то есть можно сказать у него уже есть значение, и оно равно nil, а если у константы уже есть значение, то мы не можем его менять, и свойство навсегда останется со значением nil. Поэтому система выдает ошибку, и говорит нам что необходимо либо задать какое то начальное значение, либо сделать свойство переменной, что бы была возможность задать значение по умолчанию, при извлечении опционального значения: numberOne ?? 0. 

Проще говоря не инициализированное опциональное свойство всегда должно быть переменной. 

Мы наконец подошли к сути вопроса. 

Инициализаторы класса

Внутри классов точно так же мы не можем создать не инициализированные свойства. Следующий код выдаст нам ошибку:

				
					class Person { // Class 'Person' has no initializers
	let name: String
	let age: Int
}

				
			

Система попросит нас задать либо начальные значения, либо сделать свойства опциональными, либо добавить инициализатор.

Если с первым и вторым все понятно, то что же такое эти инициализаторы? Прежде чем в этом разобраться давайте вспомним пример из прошлого урока, где мы задавали значения для свойств внутри класса, используя созданный нами метод:

				
					class Button {
	var color = ""
	var title = ""
	var cornerRadius = 0

	func setTitle(_ value: String) {
		title = value
	}
}
				
			

Когда будет вызван этот метод, система возмёт переданное в параметр значение value и передаст его в title, свойство класса:

				
					let button = Button()
button.setTitle("Кнопка")
				
			

Прежде чем читать дальше, ответьте на вопрос: Чему будет равен title в экземпляре класса button?

				
					let button = Button()
button.setTitle("Кнопка")
button.title // Кнопка
				
			

title будет иметь значение “Кнопка”, если вы ответили на вопрос верно, то можете смело двигаться дальше. 

Давайте теперь увеличим количество параметров в методе и переиспользуем все свойства класса (зададим новые значения для свойств):

				
					class Button {
	var color = ""
	var title = ""
	var cornerRadius = 0

	func initAllProperties(_ value: String, color: String, radius: Int) {
		title = value
		self.color = color
		cornerRadius = radius
	}
}

let button = Button()
button.initAllProperties("Кнопка", color: "красный", radius: 15)
button.title
button.color
button.cornerRadius
				
			

Проанализируйте код и снова ответьте на вопрос, какие значения будут содержать свойтсва title, color и cornarRadius?

Думаю вы все ответили на вопрос верно, но если все таки запутались, ловите пояснение:

Давайте на минуту забудем что такое класс, и выполним тот же самый код в не класса:

Чему будут равны свойства title, colorValue и cornerRadius? Ответ очевиден, после вызова метода, свойства приобрели новое значение, которые мы передали в параметры функции:

С классом обстоят дела точно так же, только что бы обратится к методу и свойствам, нам необходимо создать экземпляр класса. 

Обратите внимание что self для свойства color уже не будет работать, потому что self это сам класс, а здесь класса у нас нет, поэтому мы вынуждены придумать другое название, что бы системе было понятно к чему мы обращаемся и во что передаем значения. 

Наконец вы готовы к тому что бы узнать что же такое инициализаторы.

Инициализаторы – это методы, которые вызываются при создании нового объекта класса. Они могут принимать параметры, которые передаются при создании объекта, и использовать их для установки начальных значений свойств класса.

Теперь вы знаете что это методы, который вызывается в момент создания объекта класса. Имя такого метода init,  и выглядит он следующим образом:

				
					class Button {
	var color = ""
	var title = ""
	var cornerRadius = 0

	init() {
		
	}
}
				
			

Как вы можете заметить у него нет ключевого слова func, есть только скобки для параметров и фигурные скобки для кода. Этот метод есть у каждого класса по умолчанию и он вызывается в момент создания экземпляра класса. 

Давайте посмотрим на вызов этого метода:

				
					class Button {
	var color = ""
	var title = ""
	var cornerRadius = 0

	init() {
		
	}
}

let button = Button.init()
				
			

Но есть возможность не писать имя метода, а сразу использовать круглые скобки:

				
					let button = Button.init()
let buttonTwo = Button()
				
			

И в первом и во втором случае мы вызываем метод init, просто в последнем варианте запись короче и все используют ее. 

Вторая запись вам уже знакома и теперь вы понимаете откуда появились эти круглые скобки, это по сути параметры метода, который мы вызываем. 

В данном примере init уже есть в классе по умолчанию init() {} и нам нет необходимости его прописывать.

Если init вызывается при создании экземпляра класса, в таком случае мы можем что-нибудь сделать в момент создания класса, давайте выведем значения свойств класса на консоль:

				
					class Button {
	var color = "Красная"
	var title = "Кнопка"
	var cornerRadius = 15

	init() {
		print("\(color), \(title), \(cornerRadius)")
	}
}

let buttonTwo = Button()
				
			

В тот момент когда мы создаем экземпляр класса buttonTwo будет вызван метод init, внутри которого мы распечатываем значения наших свойств:  Красная, Кнопка, 15:



Метод init можно воспринимать как надзирателя, который следит, чтобы все свойства внутри класса были инициализированны, то есть имели какие то значения. 

Eсли init это метод, то значит у него могут быть параметры, давайте через его параметр зададим значение для свойства title:

				
					class Button {
	var color = ""
	var title = ""
	var cornerRadius = 15

	init(text: String) {
		title = text
	}
}

let buttonTwo = Button(text: "Кнопка")
buttonTwo.title
				
			

Теперь ответьте на вопрос, какое значение будет у свойства title экземпляра класса buttonTwo?

Все верно, title имеет теперь значение “Кнопка”. Обратите внимание что изначально свойство в качестве значения имело пустую строку. 

Благодаря методу init мы можем производить первую инициализацию свойств в самом методе, то есть нам нет необходимости задавать начальное значение для свойства title:

				
					class Button {
	var color = ""
	var title: String
	var cornerRadius = 15

	init(text: String) {
		title = text
	}
}

let buttonTwo = Button(text: "Кнопка")
buttonTwo.title
				
			

В таком случае компилятор не будет выдавать нам ошибки, потому что инициализация свойства под чутким контролем метода init, мы просто не сможем создать экземпляр класса без инициализации свойства title.

Давайте все свойства инициализируем, используя инициализатор:

				
					class Button {
	var color: String
	var title: String
	var cornerRadius: Int

	init(title: String, color: String, cornerRadius: Int) {
		self.title = title
		self.color = color
		self.cornerRadius = cornerRadius
	}
}
				
			

В таком случае все свойства класса обязательны для инициализации, и мы не сможем создать экземпляр класса без вызова этого инициализатора:

Напомню! Использование self внутри инициализатора необходимо что бы была возможность создавать одинаковые имена, таким образом компилятору понятно какое свойство к чему относится. В инициализаторе лучше всегда использовать созвучные имена со свойствами класса, это позволит вам не запутаться. 

Более подробно о том что такое self, и чем оно отличается от Self мы рассмотрим на одном из уроков в дальнейшем. 


Нам осталось передать какие то значения в каждый из параметров:

				
					let button = Button(
	title: "Кнопка",
	color: "Синяя",
	cornerRadius: 20
)

button.title
button.color
button.cornerRadius
				
			

Прежде чем продолжить ответьте на вопрос, какие значения будут у свойств title, color и cornerRadius экземпляра класса button?

Таким образом мы можем создавать разные экземпляры класса и при создании сразу инициализировать свойства нужными значениями. 

Несколько инициализаторов

В разработке бывают случаи, когда одного инициализатора может быть не достаточно, и у нас есть возможность создать столько инициализаторов, сколько нужно. 

Давайте рассмотрим следующий пример:

				
					class Theme {
	var color = "Синий"
	var cornerRadius = 15
}


class Button {
	var color: String
	var title: String
	var cornerRadius: Int

	init(title: String, color: String, cornerRadius: Int) {
		self.title = title
		self.color = color
		self.cornerRadius = cornerRadius
	}
}
				
			

У нас есть дополнительный класс, который в себе содержит нужную нам информацию, и мы бы хотели иметь возможность инициализировать свойства класса Button значениями свойств класса Theme. 

Для этого мы можем создать еще один инициализатор:

Обратите внимание, что у класса Theme мы не прописываем ни какого инициализатора, он нам доступен по умолчанию init() так как все свойства инициализированы.

				
					class Theme {
	var color = "Синий"
	var cornerRadius = 15
}


class Button {
	var color: String
	var title: String
	var cornerRadius: Int

	init(title: String, color: String, cornerRadius: Int) {
		self.title = title
		self.color = color
		self.cornerRadius = cornerRadius
	}
	
	init(theme: Theme) {
		color = theme.color
		cornerRadius = theme.cornerRadius
	} // Ошибка: "Return from initializer without initializing all stored properties"
}
				
			

Этот код выдаст ошибку. Остановитесь на этом моменте и подумайте почему так произошло. 

 Все дело в том, что при создании экземпляра класса мы не можем вызвать два инициализатора одновременно, либо один либо другой. А это значит что если мы захотим воспользоваться вторым инициализатором, то мы попытаемся создать экземпляр класса с не инициализированным свойством title, как вы уже знаете система нам не даст этого сделать, все свойства на момент создания экземпляра класса обязаны быть инициализированными, давайте добавим это свойство в инициализатор:

				
					class Theme {
	var color = "Синий"
	var cornerRadius = 15
}


class Button {
	var color: String
	var title: String
	var cornerRadius: Int

	init(title: String, color: String, cornerRadius: Int) {
		self.title = title
		self.color = color
		self.cornerRadius = cornerRadius
	}
	
	init(theme: Theme, title: String) {
		color = theme.color
		cornerRadius = theme.cornerRadius
		self.title = title
	}
}
				
			

В таком случае никаких ошибок больше не будет, потому что каждый из инициализаторов позаботился о том что бы все свойства были инициализированны, и мы смело можем создать экземпляр класса с любым из инициализаторов: