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

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

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

protocol MyStructProtocol{
    //whatever
}

struct MyStruct {
    var delegate: MyStructProtocol?
}

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

protocol MyClassProtocol:Class{
    //whatever
}

class MyClass {
    weak var delegate: MyClassProtocol?
}

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

3 ответа

Решение

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

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

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

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

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

Это очень верно. "Цикл сильных ссылок" - это все, чтобы получить право собственности.

В следующем примере мы не используем 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 позволит избежать цикла сильных ссылок

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

Допустим b является делегатом a, Сейчас a "s delegate свойство b,

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

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

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

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