Узнайте, почему копирование класса в Swift копирует указатель, а не данные. Разберитесь в работе ссылочных типов и научитесь создавать настоящие независимые копии с методом copy().
Вы когда-нибудь копировали объект класса, меняли что-то в копии и обнаруживали, что оригинал тоже изменился? Это не баг, а особенность ссылочных типов. Давайте разберёмся, как работают классы в Swift, и научимся управлять этим поведением.
Создадим простой класс для персонажа-ниндзя:
class Ninja {
var name: String
var powerLevel: Int
init(name: String, powerLevel: Int) {
self.name = name
self.powerLevel = powerLevel
}
}Теперь создадим экземпляр, скопируем его и изменим что-то в копии:
var naruto = Ninja(name: "Naruto", powerLevel: 9000)
var naruto2 = naruto
naruto2.name = "Sasuke"
print(naruto.name) // "Sasuke" 😱
print(naruto2.name) // "Sasuke"Мы изменили naruto2, но naruto тоже изменился? Да. Это не баг — так работают классы.
Когда вы создаёте структуру, Swift хранит данные прямо в переменной. Копирование структуры похоже на ксерокопию документа — два независимых листа. Измените один — другой не тронется.
Когда вы создаёте класс, Swift хранит данные где-то в памяти, а переменной даёт указатель на это место. Копирование класса не копирует данные — оно копирует указатель. Теперь у вас два указателя, ведущих к одним данным.
var naruto = Ninja(name: "Naruto", powerLevel: 9000)
// naruto — указатель на данные: {name: "Naruto", powerLevel: 9000}
var naruto2 = naruto
// naruto2 — ВТОРОЙ указатель на ТЕ ЖЕ данные
naruto2.name = "Sasuke"
// Вы пошли по указателю naruto2 и изменили данные
// naruto указывает на те же данные, поэтому naruto.name тоже "Sasuke"Существует только один набор данных. Обе переменные — просто разные способы указывать на него.
Это не ошибка, а осознанное решение, которое очень полезно. Представьте реальное iOS-приложение: у вас есть профиль пользователя, который показывается на экране настроек, на главном экране и в уведомлениях. Если бы все три экрана имели независимые копии данных, и пользователь изменил бы имя, вам пришлось бы обновлять три копии. Пропустите одну — получите неконсистентные данные.
С классом все три экрана смотрят на одни и те же данные через свои указатели. Измените имя один раз — и каждый экран, имеющий ссылку на этого пользователя, увидит обновлённое значение автоматически. Именно это нужно для общего состояния.
Поэтому Swift называет классы ссылочными типами — вы передаёте ссылки (указатели) на данные, а не копии самих данных. Структуры — типы-значения: значение и есть данные, и копирование создаёт свежую независимую копию.
Иногда вам действительно нужна независимая копия — два отдельных ниндзя, которые могут меняться без влияния друг на друга. Swift не делает это автоматически для классов, но вы можете создать метод copy(), который создаёт новый экземпляр с теми же значениями:
class Ninja {
var name: String
var powerLevel: Int
init(name: String, powerLevel: Int) {
self.name = name
self.powerLevel = powerLevel
}
func copy() -> Ninja {
Ninja(name: name, powerLevel: powerLevel)
}
}Теперь, когда вам нужна настоящая независимая копия, вызывайте copy() явно:
var naruto = Ninja(name: "Naruto", powerLevel: 9000)
var naruto2 = naruto.copy()
naruto2.name = "Sasuke"
print(naruto.name) // "Naruto" — не изменилось ✅
print(naruto2.name) // "Sasuke" — изменилось только здесь ✅Два указателя, два отдельных набора данных. Метод copy() создаёт новый экземпляр Ninja в памяти с теми же начальными значениями — теперь каждая переменная работает со своими данными.
Когда вы выбираете класс вместо структуры, вы делаете заявление о том, как данные должны использоваться. Вы говорите: «Я хочу, чтобы несколько частей кода разделяли и наблюдали одни и те же данные».
Когда вы выбираете структуру, вы говорите: «Каждый владелец должен получить свою независимую копию».
Большинство разработчиков Swift по умолчанию склоняются к структурам именно потому, что поведение «копия — это копия» проще и понятнее. К классу обращаются, когда общие синхронизированные данные — это именно то, что нужно, например, модель данных, которую нужно наблюдать и обновлять в нескольких представлениях SwiftUI.
Указатель — это фича, а не баг. Просто нужно знать, что он существует.
Копирование класса копирует указатель, а не данные. Обе копии указывают на один и тот же объект, поэтому изменение одной меняет то, что видит другая.
Если вам нужна настоящая независимая копия, напишите метод copy(), который создаёт новый экземпляр. Swift не сделает это за вас автоматически — и это осознанно, потому что заставляет вас явно указать важное намерение. 🌸
Прямо сейчас откройте Xcode и проверьте, как ведут себя классы в вашем проекте. Если вы используете классы для моделей данных, которые передаются между экранами, убедитесь, что вы понимаете, где могут возникнуть побочные эффекты. При необходимости добавьте метод copy(), чтобы явно контролировать создание независимых копий.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →