Avatar ·

Как вывести адрес памяти переменной в Swift? (для отладки)

Всем привет! Я начинающий iOS-разработчик, и у меня возникла проблема при отладке кода. Мне нужно понять, ссылаются ли две переменные на один и тот же объект в памяти или это разные копии. Я знаю, что в Objective-C можно было просто использовать `%p` в `NSLog`, но в Swift с его строгой типизацией всё сложнее. Я пробовал разные способы, но пока безрезультатно. Вот пример, который меня интересует: ```swift class Person { var name: String init(name: String) { self.name = name } } let person1 = Person(name: "Alice") let person2 = person1 person2.name = "Bob" print(person1.name) // Выводит "Bob" — значит, это ссылочный тип ``` Как мне вывести реальный адрес памяти, по которому хранятся объекты `person1` и `person2`, чтобы убедиться, что они действительно указывают на один и тот же участок памяти? Пробовал использовать `print(person1)` — но это выводит описание объекта, а не адрес. Пробовал `withUnsafePointer(to: &person1)` — но это адрес самой переменной, а не объекта в куче. Подскажите, пожалуйста, как правильно получить адрес памяти объекта класса (ссылочного типа) и, если возможно, для структур (value types) тоже? Буду благодарен за примеры кода. <<>>

Проблема: получение реального адреса памяти объекта

Вы столкнулись с классической задачей отладки в Swift. Действительно, простое print() выводит описание объекта (реализуя протокол CustomStringConvertible), а withUnsafePointer(to:) даёт адрес самой переменной на стеке, а не объекта в куче. Для ссылочных типов (классов) переменная хранит лишь указатель на объект в куче — именно его адрес нам и нужен.

Решение для классов (reference types)

Самый простой и надёжный способ — использовать функцию Unmanaged.passUnretained(_:).toOpaque(). Она возвращает сырой указатель (UnsafeMutableRawPointer) на объект в куче:

class Person {
    var name: String
    init(name: String) { self.name = name }
}

let person1 = Person(name: "Alice")
let person2 = person1

let address1 = Unmanaged.passUnretained(person1 as AnyObject).toOpaque()
let address2 = Unmanaged.passUnretained(person2 as AnyObject).toOpaque()

print("person1 address: \(address1)") // 0x0000600003cba0e0 (пример)
print("person2 address: \(address2)") // 0x0000600003cba0e0 (тот же!)

Вы увидите, что адреса совпадают — это доказывает, что person2 — не копия, а ссылка на тот же объект.

Важные нюансы

  • Приведение к AnyObject: Unmanaged.passUnretained требует тип, соответствующий AnyObject. Для классов это не проблема, но если ваш тип — структура, нужно использовать другой подход (см. ниже).
  • Безопасность: passUnretained не увеличивает счётчик ссылок. Это безопасно, если вы уверены, что объект жив на момент вызова. Для отладки — идеально.
  • Альтернатива с unsafeBitCast: unsafeBitCast(person1, to: UnsafeRawPointer.self) — работает, но считается менее безопасным и читаемым.

Решение для структур (value types)

Со структурами ситуация иная: они хранятся на стеке (или внутри другого объекта), и у них нет постоянного адреса в куче. Но для отладки можно получить адрес самой переменной на стеке:

struct Point {
    var x: Int
    var y: Int
}

var point = Point(x: 10, y: 20)
withUnsafePointer(to: &point) { ptr in
    print("Point address on stack: \(ptr)") // Адрес на стеке
}

Обратите внимание: каждый раз, когда вы передаёте структуру в функцию или присваиваете новой переменной, создаётся копия. Поэтому адреса будут разными.

Удобное расширение для отладки

Чтобы не писать каждый раз громоздкий код, создайте расширение:

extension NSObject {
    var memoryAddress: String {
        return "\(Unmanaged.passUnretained(self).toOpaque())"
    }
}

// Использование:
class Person: NSObject {
    var name: String
    init(name: String) {
        self.name = name
        super.init()
    }
}

let p = Person(name: "Bob")
print(p.memoryAddress) // 0x0000600001cba2a0

Для классов, не наследующих NSObject, можно сделать аналогичное расширение для AnyObject:

extension AnyObject {
    var memoryAddress: String {
        return "\(Unmanaged.passUnretained(self).toOpaque())"
    }
}

Итог

Для отладки ссылочных типов используйте Unmanaged.passUnretained(...).toOpaque(). Для value types — withUnsafePointer(to:). Помните, что адрес структуры на стеке может меняться при оптимизации компилятора, поэтому полагаться на него в production-коде не стоит.

<<>>

Альтернативный подход: использование lldb и точек останова

Если вам не нужно выводить адрес в коде, а достаточно посмотреть его во время отладки, проще всего использовать встроенные возможности Xcode и lldb.

Поставьте точку останова (breakpoint) на строке после создания объекта. В консоли отладчика (lldb) введите:

frame variable -L person1

Или сокращённо:

v -L person1

Вы увидите что-то вроде:

0x0000600003cba0e0: (App.Person) person1 = ...

Первое число — это и есть адрес объекта в куче. Для второго объекта person2 адрес будет таким же, если это ссылка.

Преимущества этого подхода

  • Не нужно менять код — идеально для быстрой проверки.
  • Работает с любыми типами, включая замыкания и метатипы.
  • Позволяет сразу посмотреть содержимое объекта по адресу командой memory read.

Недостатки

  • Не подходит, если нужно логировать адрес в runtime (например, в лог-файл).
  • Требует запуска через Xcode или использования lldb в консоли.

Этот способ особенно полезен, когда вы отлаживаете утечки памяти или проверяете, не создаётся ли лишняя копия объекта.