← Назад к вопросам

Какой знаешь аналог weak?

1.3 Junior🔥 141 комментариев
#Управление памятью

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Аналоги weak в Swift

Что такое weak

weak — это модификатор ссылки, который не увеличивает счетчик ссылок (reference count) объекта. Используется для избежания retain cycles.

class Dog {
    weak var owner: Person?  // Слабая ссылка
}

class Person {
    var dog: Dog?  // Сильная ссылка
}

// Без weak был бы retain cycle:
// Person → retains Dog
// Dog → retains Person
// Никто не может быть deallocирован

Основные аналоги и альтернативы

1. unowned — сильно связанный аналог

// weak vs unowned

// weak: может стать nil
class Parent {
    var child: Child?
}

class Child {
    weak var parent: Parent?  // Может быть nil
    
    func greet() {
        print(parent?.name ?? "Unknown")  // Проверяем nil
    }
}

// unowned: никогда не nil (гарантия программиста)
class Parent {
    var child: Child?
}

class Child {
    unowned var parent: Parent  // НИКОГДА не nil, иначе crash
    
    func greet() {
        print(parent.name)  // Прямой доступ, parent существует всегда
    }
}

// Использование:
let parent = Parent()
let child = Child(parent: parent)  // parent существует при создании
parent.child = child  // Теперь связаны
// parent не может быть deallocирован, пока child жив
// Но child может быть deallocирован без parent

2. unowned(unsafe) — самый опасный аналог

// unowned(unsafe): не проверяет, что объект существует
class Something {
    unowned(unsafe) var reference: AnyObject  // Опасно!
}

// Используется только когда:
// 1. Абсолютно уверен, что объект существует
// 2. Нужна максимальная производительность
// 3. Согласовано с командой на жизнь и смерть

// Пример неправильного использования:
var obj: AnyObject? = SomeObject()
var unsafe = obj  // unowned(unsafe)
obj = nil  // Deallocируется
// Теперь unsafe указывает на освобожденную память
print(unsafe)  // Undefined behavior!

3. Closure captures: [weak self]

// Самый частый случай использования weak

class ViewController {
    func loadData() {
        // ❌ Проблема: closure захватывает self сильной ссылкой
        api.fetch { data in
            self.updateUI(with: data)  // Retain cycle!
            // closure → self
            // self.completionHandler → closure
        }
    }
    
    // ✅ Решение: weak self
    func loadDataSafely() {
        api.fetch { [weak self] data in
            guard let self = self else { return }
            self.updateUI(with: data)  // Безопасно
        }
    }
    
    // ✅ Альтернатива: unowned self (если VC жив пока делается запрос)
    func loadDataFast() {
        api.fetch { [unowned self] data in
            self.updateUI(with: data)  // Быстрее, но гарантия
        }
    }
}

4. Capture lists в других контекстах

// В Combine
var cancellables = Set<AnyCancellable>()

button.publisher
    .sink { [weak self] _ in
        self?.handleTap()
    }
    .store(in: &cancellables)

// В DispatchQueue
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
    self?.updateUI()
}

// В Alamofire
AF.request(url).responseJSON { [weak self] response in
    self?.handleResponse(response)
}

5. [unowned self] — в обычных closures

class DataManager {
    var onDataUpdate: (() -> Void)?
    
    func startUpdating() {
        // Если closure вызывается всегда когда self живой
        self.onDataUpdate = { [unowned self] in
            self.processData()
        }
    }
    
    // Проблема: если closure вызовется после dealloc
    func dangerousExample() {
        var callback: (() -> Void)?
        
        let dataManager = DataManager()
        callback = { [unowned dataManager] in
            dataManager.processData()  // Crash если dataManager deallocирован
        }
        
        // dataManager deallocируется
        // callback?.call()  // CRASH!
    }
}

6. Strong Weak Dance — в delegates

protocol DataDelegate: AnyObject {
    func didUpdateData(_ data: Data)
}

class DataFetcher {
    weak var delegate: DataDelegate?  // Слабая ссылка
    
    func fetchData() {
        // delegate может быть nil
        guard let delegate = self.delegate else { return }
        // Или:
        delegate?.didUpdateData(data)
    }
}

class ViewController: DataDelegate {
    let fetcher = DataFetcher()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        fetcher.delegate = self  // ViewController не retain'ится
    }
    
    func didUpdateData(_ data: Data) {
        // Обновляем UI
    }
}

Таблица сравнения

┌──────────┬──────────────────┬──────────────┬─────────────┐
│ Тип      │ Увеличивает RC    │ Может быть nil│ Crash если nil│
├──────────┼──────────────────┼──────────────┼─────────────┤
│ strong   │ Да               │ Нет (обычно) │ Нет         │
│ weak     │ Нет              │ Да           │ Нет         │
│ unowned  │ Нет              │ Нет          │ Да          │
│ unowned  │ Нет              │ Нет          │ Да (UB)     │
│ (unsafe) │                  │              │             │
└──────────┴──────────────────┴──────────────┴─────────────┘

Когда использовать что

// 1. Parent → Child: используй strong
class Parent {
    var children: [Child] = []  // Strong
}

// 2. Child → Parent: используй weak
class Child {
    weak var parent: Parent?  // Weak
}

// 3. Optional relationships: используй weak
class User {
    weak var friend: User?  // Может быть nil
}

// 4. Guaranteed lifetime: используй unowned
class CreditCard {
    unowned var owner: Person  // Owner всегда существует
}

// 5. Closures в self: используй [weak self]
self.handler = { [weak self] in
    self?.process()
}

// 6. Delegates: используй weak
class Manager {
    weak var delegate: ManagerDelegate?
}

Практические примеры

MVVM без retain cycles

class ViewController: UIViewController {
    @IBOutlet weak var nameLabel: UILabel!
    let viewModel = UserViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ✅ Правильно: weak self в binding
        viewModel.$userName
            .sink { [weak self] name in
                self?.nameLabel.text = name
            }
            .store(in: &cancellables)
    }
}

class UserViewModel: ObservableObject {
    @Published var userName: String = ""
    
    init() {
        // Не захватываем self
        Task {
            let user = await fetchUser()
            self.userName = user.name
        }
    }
}

Network requests

class APIClient {
    func fetchUser(_ id: String, completion: @escaping (User) -> Void) {
        // weak self не нужен, если closure не retainится долго
        URLSession.shared.dataTask(with: url) { data, _, _ in
            let user = parse(data)
            completion(user)  // completion вызовется сразу
        }.resume()
    }
    
    func fetchUserSafely(
        _ id: String,
        completion: @escaping (User) -> Void
    ) {
        URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
            guard let self = self else { return }  // Если APIClient deallocирован
            let user = self.parse(data)
            completion(user)
        }.resume()
    }
}

Avoiding multiple captures

// ❌ Плохо: множественные захваты
class Controller {
    func setup() {
        button.setTitle(title, for: .normal)
        button.setOnTap { [weak self] in
            self?.handleTap1()
        }
    }
}

// ✅ Хорошо: одна точка входа
class Controller {
    func setup() {
        bind(to: button)
    }
    
    private func bind(to button: UIButton) {
        button.setOnTap { [weak self] in
            self?.handleTap()
        }
    }
}

Инструменты для поиска retain cycles

// 1. Instruments → Memory Graph
// Показывает все references и их типы

// 2. Debug memory graph (в Xcode)
// При pause выполнения: Debug → View Memory Graph

// 3. Swiftlint rule
// swiftlint: disable weak_delegate

Выводы

  • weak — для relationships где parent держит reference
  • unowned — когда 100% уверен в lifetime
  • [weak self] — стандарт в closures
  • unowned(unsafe) — только для performance-critical code
  • Избегай retain cycles — источник утечек памяти
  • Используй Instruments — для поиска проблем