Как работает Swift управление памятью?

в частности, как Swift управление памятью работает с optionals с использованием шаблона делегата?

привыкнув писать шаблон делегата в Objective-C, мой инстинкт-сделать делегата weak. Например, в Objective-C:

@property (weak) id<FooDelegate> delegate;

однако, делать это в Swift не так прямолинейно.

если мы имеем как раз нормальный смотря протокол:

protocol FooDelegate {
    func doStuff()
}

мы не можем объявить переменные этого типа как слабый:

weak var delegate: FooDelegate?

выдает ошибку:

"слабый" не может быть применен к неклассовому типу "FooDelegate"

поэтому мы либо не используем ключевое слово weak, что позволяет нам использовать structs и enums как делегаты, или мы меняем наш протокол на следующий:

protocol FooDelegate: class {
    func doStuff()
}

что позволяет нам использовать weak, но не позволяет нам использовать structs или enums.

если я не сделаю свой протокол протоколом класса, и поэтому не используйте weak для моей переменной я создаю цикл сохранения, правильно?

есть ли какая-либо мыслимая причина, по которой любой протокол, предназначенный для использования в качестве протокола делегата, не должен быть протоколом класса, чтобы переменные этого типа могли быть weak?

я в первую очередь спрашиваю, потому что в разделе делегации официальная документация Apple по протоколам Swift, они предоставляют пример неклассового протокола и не слабой переменной, используемой как делегат их класса:

protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}
class SnakesAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board: [Int]
    init() {
        board = [Int](count: finalSquare + 1, repeatedValue: 0)
        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
    }
    var delegate: DiceGameDelegate?
    func play() {
        square = 0
        delegate?.gameDidStart(self)
        gameLoop: while square != finalSquare {
            let diceRoll = dice.roll()
            delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

следует ли нам понимать это как намек на то, что Apple считает, что мы должны использовать структуры в качестве делегатов? Или это просто плохой пример, и реально протоколы делегатов должны быть объявлены как протоколы только класса, чтобы делегированный объект мог содержать слабую ссылку на свой делегат?

3 ответов


следует ли нам понимать это как намек на то, что Apple считает, что мы должны использовать структуры в качестве делегатов? Или это просто плохой пример, и реально протоколы делегатов должны быть объявлены как протоколы только класса, чтобы делегированный объект мог содержать слабую ссылку на свой делегат?

вот в чем дело. В реальном программировании Cocoa делегат, скорее всего, будет существующим классом. Это класс, потому что он существует для какой-то другой цели, что только класс может удовлетворить-потому что какао требует этого.

например, очень часто, чтобы взять iOS в качестве примера, один контроллер представления должен выступать в качестве делегата другого контроллера представления для организации сообщения между ними. Владение контроллерами представления диктуется иерархией контроллера представления и ничем другим. Итак, в Swift, как и в Objective-C, у вас было лучше сделать delegate свойства weak, потому что это было бы ужасно, если один контроллер вида внезапно взял управление памятью в собственность другого контроллера вида!

таким образом, в реальном мире рамок какао существует серьезная опасность неправильного владения или цикла удержания. И это проблема, которая weak решает. Но это работает, как вы правильно говорите, только с классами.


Да, этот пример немного странный.

поскольку в примере используется неклассовый тип протокола, он должен ожидать возможной структуры, реализующей протокол, что означает, что DiceGame экземпляр владеет своим делегатом. И действительно, это нарушает типичные предположения о шаблоне делегата.

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

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

реально можно было бы ожидать использования ссылочных типов (классов) для реализации этого в любом случае, даже если объявление протокола оставляет открытой возможность сделать это иначе. Даже в гипотетическом мире Swift-only, вероятно, имеет смысл сделать это таким образом... обычно, когда у вас есть что-то с длинной жизнью, внутренним изменяемым состоянием и шаблоном использования, к которому потенциально доступен несколько других актеров, вам нужен тип класса, даже если вы можете подделать иначе с типами значений и var.


если вы должны иметь structs и emums в вашем протоколе, тогда, если вы сделаете свой делегат nil перед тем, как закрыть контроллер вида, это прерывает цикл сохранения. Я проверил это в документах.

// view controller about to close
objectCreatedByMe.delegate = nil

это необязательно, поэтому это допустимо.