Когда вы пишете более крупные и сложные программы, вам нужно будет обращать внимание на то, где вы объявляете свои константы и переменные. Каково оптимальное размещение в вашем коде? Если вы объявите переменную var вверху, вы можете обнаружить, что ваш код сложнее читать и гораздо сложнее отлаживать.
На этом уроке вы научитесь писать хорошо структурированный код, который легко читается. Вы сделаете это, правильно определив область видимости ваших констант и переменных.
Что вы изучите:
- Как различать глобальную и локальную область видимости.
- Как создавать переменные и функции в глобальной и локальной области видимости.
- Как повторно использовать имена переменных с помощью теневого копирования переменных.
Словарь:
- глобальная область видимости
- локальная область видимости
- область видимости
- теневое копирование переменных (variable shadowing)
Каждая константа и переменная существует в некоторой области видимости, где она видна и доступна. Существует два разных уровня области видимости: глобальная и локальная. Любая переменная, объявленная в глобальной области видимости, называется глобальной переменной, а переменная, объявленная в локальной области видимости, — локальной переменной.
Глобальная область видимости относится к коду, который доступен отовсюду в вашей программе. Например, когда вы начинаете объявлять переменные в Xcode playground, вы объявляете их в глобальной области видимости. После того как вы определили переменную в одной строке, она доступна в каждой последующей строке. Когда вы заканчиваете ввод в playground, код выполняется построчно, начиная с этой глобальной области видимости.
Всякий раз, когда вы добавляете пару фигурных скобок ({ }), будь то для структуры, класса, функции, оператора if, цикла for или чего-то еще, область внутри скобок определяет новую локальную область видимости. Любая константа или переменная, объявленная внутри скобок, определяется в этой локальной области видимости и не доступна из любой другой области видимости.
Рассмотрим следующий блок кода:
var age = 55
func printMyAge() {
print(”My age: \(age)”)
}
print(age)
printMyAge()
Console Output:
55
My age: 55
Обратите внимание, что переменная age определена в начале кода, а не внутри структуры управления или функции. Это означает, что она находится в глобальной области видимости и может быть доступна по всей программе. Функция printMyAge может ссылаться на age, даже если она не была передана в качестве параметра. Аналогично, функция printMyAge не определена внутри структуры или класса, поэтому она находится в глобальной области видимости и, следовательно, доступна в последней строке кода.
Теперь рассмотрим другой блок кода:
func printBottleCount() {
let bottleCount = 99
print(bottleCount)
}
printBottleCount()
print(bottleCount)
Переменная bottleCount определена внутри функции printBottleCount, которая имеет свою собственную локальную область видимости между фигурными скобками. Таким образом, bottleCount находится в локальной области видимости и доступна только внутри функции, внутри фигурных скобок. Последняя строка в коде вызовет ошибку, так как она не сможет найти переменную с именем bottleCount.
Рассмотрим еще один пример:
func printTenNames() {
var name = “Grey”
for index in 1...10 {
print(”\(index): \(name)”)
}
print(index)
print(name)
}
printTenNames()
В приведенном выше коде переменная name является локальной переменной и доступна всему, что определено в той же области видимости. Она также доступна в еще меньшей локальной области видимости: в цикле for на следующей строке. Переменная index, хотя и определена внутри функции, была определена внутри цикла, который можно рассматривать как более узко определенный подраздел функции. Следовательно, index доступна только внутри цикла. Поскольку print(index) находится сразу за пределами цикла, это вызывает ошибку.
Переменное затенение
Следующий пример определяет переменную, называемую points, в двух разных местах: в локальной области видимости функции и в локальной области видимости цикла for. Это называется переменным затенением. Это допустимый код на Swift, но может быть неочевидно, что произойдет при выполнении этого кода.
func printComplexScope() {
let points = 100
print(points)
for index in 1...3 {
let points = 200
print(”Loop \(index): \(points+index)”)
}
print(points)
}
printComplexScope()
Console Output:
100
Loop 1: 201
Loop 2: 202
Loop 3: 203
100
Сначала переменная points объявляется и устанавливается в значение 100. Это значение выводится на следующей строке. Внутри цикла for объявляется еще одна переменная points, на этот раз со значением 200. Вторая переменная points полностью затеняет переменную в области видимости функции, что означает, что первая переменная points становится недоступной. Любое обращение к переменной points будет обращаться к той, которая находится ближе к той же области видимости. Таким образом, когда внутри цикла вызывается оператор print, он выведет значение 200 пять раз. После завершения цикла оператор print выведет единственную переменную points, к которой он может получить доступ: ту, которая имеет значение 100.
Чтобы избежать ненужной путаницы в этом конкретном примере, можно предложить изменить имя внутренней переменной points. И, вероятно, это будет правильным решением. Однако есть несколько случаев, когда затенение переменных может быть полезным. Представьте, что у вас есть опциональная строка name, и вы хотите использовать синтаксис if let для работы с её значением. Вместо того чтобы придумывать новое имя переменной, например, unwrappedName, вы можете повторно использовать name в области видимости фигурных скобок if let:
var name: String? = “Brady”
if let name = name {
// name is a local `String` that shadows the global `String?` of the same name
print(”My name is \(name)”)
}
Вы также можете использовать затенение переменных, чтобы упростить присвоение имен развернутым параметрам из инструкции guard.
func exclaim(name: String?) {
if let name = name {
// Inside the braces, `name` is the unwrapped `String` Value
print(”Exclaim function was passed: \(name)”)
}
}
func exclaim(name: String?) {
guard let name = name else { return }
// name: `String?` is no longer accessible, only name: `String`
print(”Exclaim function was passed: \(name)”)
}
Затенение и Инициализаторы
Вы можете воспользоваться своими знаниями о затенении переменных, чтобы создавать чистые и легко читаемые инициализаторы. Предположим, вы хотите создать экземпляр Person, передавая имя и возраст в качестве двух параметров. Также предположим, что каждый экземпляр Person имеет свойства name и age:
struct Person {
var name: String
var age: Int
}
let tim = Person(name: “Tim”, age: 35)
print(tim.name)
print(tim.age)
Console Output:
Tim
35
Когда вы пишете инициализатор, вы захотите сделать его максимально простым и логичным: присваивая параметр name свойству name и параметр age свойству age.
init(name: String, age: Int) {
self.name = name
self.age = age
}
Поскольку name и age являются именами параметров внутри области видимости функции, они затеняют свойства name и age, определенные в области видимости Person. Вы можете использовать ключевое слово self перед именем свойства, чтобы явно ссылаться на свойство и избегать любой путаницы, которую может вызвать затенение переменных у компилятора и читателя. Этот синтаксис делает совершенно ясным, что свойства name и age присваиваются значениям параметров name и age, переданным в инициализатор.
Лабораторная работа
Откройте и завершите упражнения в Lab—Scope.playground.
Отрывок из книги
Develop in Swift Fundamentals
Apple Education
https://books.apple.com/ru/book/develop-in-swift-fundamentals/id1581182804