Как работает 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
это необязательно, поэтому это допустимо.