0Pricing
Swift Academy · Урок

UnsafePointer и UnsafeMutablePointer

Осторожно читайте и записывайте необработанную память.

«UnsafePointer и UnsafeMutablePointer» — бесплатный урок Swift Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.

Типизированные указатели в Swift

Безопасные ссылки Swift не раскрывают raw-адреса, но при взаимодействии с C или создании низкоуровневых структур данных используются типизированные указатели: UnsafePointer<T> для доступа только для чтения и UnsafeMutablePointer<T> для чтения и записи.

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

// A pointer to an Int we can read but not write
func readFirst(_ p: UnsafePointer<Int>) -> Int {
    return p.pointee
}

Свойство pointee

pointee — это значение, на которое в данный момент указывает указатель. Для изменяемого указателя его можно читать и присваивать ему новое значение; указатель только для чтения позволяет лишь читать его.

Обращение к pointee в неинициализированной или освобождённой памяти приводит к неопределённому поведению — система типов этого не обнаружит.

let p = UnsafeMutablePointer<Int>.allocate(capacity: 1)
p.initialize(to: 42)
print(p.pointee)   // 42
p.pointee = 99
print(p.pointee)   // 99

Выделение памяти

UnsafeMutablePointer<T>.allocate(capacity:) резервирует место для capacity элементов, но не инициализирует их. Память остаётся raw, и читать её до инициализации запрещено.

// Reserve space for 3 Doubles (uninitialized)
let buffer = UnsafeMutablePointer<Double>.allocate(capacity: 3)
// Must initialize before reading any element

Инициализация и деинициализация

Перед чтением необходимо выполнить initialize. Перед освобождением типов, требующих очистки (например, классов или строк), необходимо выполнить deinitialize, чтобы освободить занятые ими ресурсы.

Жизненный цикл таков: allocate → initialize → использовать → deinitialize → deallocate.

let p = UnsafeMutablePointer<String>.allocate(capacity: 1)
p.initialize(to: "hello")
print(p.pointee)
p.deinitialize(count: 1) // releases the String
p.deallocate()

Всегда сопоставляйте allocate с deallocate

Каждый вызов allocate должен иметь ровно один соответствующий вызов deallocate, иначе произойдёт утечка памяти. Безопаснее всего сразу после выделения использовать defer, поскольку он выполняется независимо от способа завершения функции.

func process() {
    let p = UnsafeMutablePointer<Int>.allocate(capacity: 1)
    defer { p.deallocate() }
    p.initialize(to: 7)
    print(p.pointee)
} // deallocate runs here automatically

Арифметика указателей

Типизированные указатели поддерживают индексацию и арифметику в единицах элементов, а не bytes. p + 2 перемещает указатель на два stride типа T. p[i] — это сокращённая запись (p + i).pointee.

let p = UnsafeMutablePointer<Int>.allocate(capacity: 3)
p.initialize(repeating: 0, count: 3)
p[0] = 10
p[1] = 20
p[2] = 30
print((p + 1).pointee) // 20
p.deinitialize(count: 3)
p.deallocate()

Инициализация нескольких элементов

Для блока памяти initialize(repeating:count:) записывает одно и то же значение в каждый слот, а initialize(from:count:) копирует данные из другого buffer. Оба метода полностью инициализируют диапазон, поэтому его безопасно читать.

let dst = UnsafeMutablePointer<Int>.allocate(capacity: 4)
dst.initialize(repeating: 5, count: 4)
print(dst[3]) // 5
dst.deinitialize(count: 4)
dst.deallocate()

Только чтение и изменение

Функция, которая только изучает данные, должна принимать UnsafePointer<T>; функция, которая изменяет данные, принимает UnsafeMutablePointer<T>. Изменяемый указатель неявно преобразуется в указатель только для чтения, но обратное преобразование невозможно — это закрепляет намерение на границе программного интерфейса.

func sum(_ p: UnsafePointer<Int>, _ n: Int) -> Int {
    var total = 0
    for i in 0..<n { total += p[i] }
    return total
}
// A mutable pointer can be passed here directly

withUnsafePointer для локальных значений

Чтобы получить временный указатель на существующее значение без выделения памяти, используйте withUnsafePointer(to:) или его изменяемую форму. Указатель действителен только внутри замыкания — никогда не позволяйте ему выйти за его пределы.

var value = 100
withUnsafeMutablePointer(to: &value) { ptr in
    ptr.pointee += 1
}
print(value) // 101

Распространённые ошибки

Типизированные указатели мощны, но не прощают ошибок:

  • Чтение pointee до выполнения initialize приводит к неопределённому поведению.
  • Если забыть выполнить deinitialize для типов, хранящих ресурсы, произойдёт утечка.
  • Если позволить указателю из withUnsafePointer выйти за пределы замыкания, он станет висячим указателем.

Здесь компилятор вам доверяет — проверок во время выполнения нет.

// DANGEROUS — pointer escapes the closure
// var escaped: UnsafePointer<Int>?
// withUnsafePointer(to: &x) { escaped = $0 }
// escaped!.pointee  // dangling, undefined behavior

Небольшой ручной stack

Соберём всё вместе: выделим блок, будем обращаться к его элементам по индексам, а затем выполним очистку. Это основа пользовательских коллекций и взаимодействия с C.

let n = 3
let stack = UnsafeMutablePointer<Int>.allocate(capacity: n)
stack.initialize(repeating: 0, count: n)
for i in 0..<n { stack[i] = i * i }
// stack now holds 0, 1, 4
stack.deinitialize(count: n)
stack.deallocate()

Быстрая проверка

Вспомните правильный жизненный цикл указателя.

Повторение

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

  • UnsafePointer выполняет чтение, а UnsafeMutablePointer — чтение и запись.
  • pointee обращается к значению; арифметика выполняется в единицах элементов.
  • Жизненный цикл таков: allocate → initialize → использовать → deinitialize → deallocate; в идеале его следует защищать с помощью defer.

Далее вы увидите, как указатели buffer оборачивают целую непрерывную область в интерфейс, удобный для работы с коллекциями.

Часто задаваемые вопросы

Урок «UnsafePointer и UnsafeMutablePointer» бесплатный?

Да — полный текст урока «UnsafePointer и UnsafeMutablePointer» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.

Чему я научусь в уроке «UnsafePointer и UnsafeMutablePointer»?

Осторожно читайте и записывайте необработанную память. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Swift Academy?

Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «UnsafePointer и UnsafeMutablePointer»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Swift Academy?

Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. MemoryLayout и выравнивание
  2. UnsafePointer и UnsafeMutablePointer
  3. Указатели на небезопасные буферы
  4. withUnsafeBytes и взаимодействие с C
← Назад к Swift Academy