Быстрое делегирование - когда использовать слабый указатель на делегат

Может кто-нибудь объяснить, когда и когда не использовать "слабое" назначение указателю делегата в Swift и почему?

Я понимаю, что если вы используете протокол, который не определен как класс, вы не можете или не хотите, чтобы ваш указатель делегата был слабым.

protocol MyStructProtocol{
    //whatever
}

struct MyStruct {
    var delegate: MyStructProtocol?
}

Однако, когда ваш протокол определяется как протокол типа класса, вы хотите установить свой делегат на слабый указатель?

protocol MyClassProtocol:Class{
    //whatever
}

class MyClass {
    weak var delegate: MyClassProtocol?
}

Правильно ли я? В руководстве Apple swift на примерах протоколов классов не используются слабые назначения, но в моем тестировании я вижу сильные ссылочные циклы, если мои делегаты слабо ссылаются.

Ответ 1

Вы обычно делаете протоколы классов (как определено с ключевым словом class) слабыми, чтобы избежать риска "сильного эталонного цикла" (ранее называвшегося "циклом сохранения" ). Неспособность сделать делегат слабым не означает, что у вас по сути есть сильный ссылочный цикл, а просто, что вы можете его иметь.

При типах struct сильный риск ссылочного цикла значительно уменьшается, поскольку типы struct не являются "ссылочными" типами, поэтому сложнее создать сильный опорный цикл. Но если объект делегата является объектом класса, тогда вы можете сделать протокол протоколом класса и сделать его слабым.

По моему мнению, слабым делегатам класса является лишь частично, чтобы облегчить риск сильного эталонного цикла. Это действительно вопрос "собственности". Большинство протоколов делегатов - это ситуации, когда объект, о котором идет речь, не имеет бизнеса, претендующего на владение над делегатом, а просто там, где объект, о котором идет речь, предоставляет возможность сообщить делегату что-либо (или запросить что-нибудь из него).

Ответ 2

Делегаты должны (править: обычно) всегда быть слабыми.

Допустим, b является делегатом a. Теперь свойство a delegate является b.

В случае, если вы хотите, чтобы b высвободился, когда c пропал

Если c содержит строгую ссылку на b и c освобождает, вы хотите, чтобы b освободил место для c. Однако, используя свойство сильного делегата в a, b никогда не будет освобожден, поскольку a сильно удерживает b. Используя слабую ссылку, как только b потеряет сильную ссылку из c, b освободит место, когда c освободит.

Обычно это предполагаемое поведение, поэтому вам следует использовать свойство weak.

Ответ 3

Как сказал Роб:

Это действительно вопрос "собственности"

Это очень верно. "Сильный референсный цикл" - это все о том, чтобы получить право собственности.

В следующем примере мы не используем weak var. Все же оба объекта будут освобождены. Почему?

protocol UserViewDelegate: class {
    func userDidTap()
}

class Container {
    let userView = UserView()
    let delegate = Delegate()
    init() {
        userView.delegate = delegate
    }

    deinit {
        print("container deallocated")
    }
}

class UserView {
    var delegate: UserViewDelegate?

    func mockDelegatecall() {
        delegate?.userDidTap()
    }

    deinit {
        print("UserView deallocated")
    }
}

class Delegate: UserViewDelegate {
    func userDidTap() {
        print("userDidTap Delegate callback in separate delegate object")
    }
}

Использование:

var container: Container? = Container()
container?.userView.mockDelegatecall()
container = nil // will deallocate both objects

График владения памятью (без цикла)

    +---------+container +--------+
    |                             |
    |                             |
    |                             |
    |                             |
    |                             |
    |                             |
    v                             v
userView +------------------> delegate

Чтобы создать сильный ссылочный цикл, этот цикл должен быть завершен. delegate должен указывать на container, но это не так. Так что это не проблема. Но чисто по соображениям собственности и, как сказал Роб здесь:

В иерархии объектов дочерний объект не должен поддерживать строгие ссылки на родительский объект. Это красный флаг, указывающий на сильный ссылочный цикл

Поэтому, несмотря на утечку, все еще используйте weak для своих объектов делегата.


В следующем примере мы не используем weak var. В результате ни один из классов не будет освобожден.

protocol UserViewDelegate: class {
    func userDidTap()
}

class Container: UserViewDelegate {
    let userView = UserView()

    init() {
        userView.delegate = self
    }

    func userDidTap() {
        print("userDidTap Delegate callback by Container itself")
    }
    deinit {
        print("container deallocated")
    }
}

class UserView {
    var delegate: UserViewDelegate?

    func mockDelegatecall() {
        delegate?.userDidTap()
    }

    deinit {
        print("UserView deallocated")
    }
}

Использование:

var container: Container? = Container()
container?.userView.mockDelegatecall()
container = nil // will NOT deallocate either objects

График владения памятью (имеет цикл)

     +--------------------------------------------------+
     |                                                  |
     |                                                  |
     +                                                  v
 container                                           userview
     ^                                                  |
     |                                                  |
     |                                                  |
     +------+userView.delegate = self //container+------+

с помощью weak var позволит избежать сильного опорного цикла

Ответ 4

protocol MyDelegate: class {
// ...
}
class MyViewController: UIViewController {
    weak var delegate: MyDelegate? 
}