Теоретически могут возникнуть проблемы с памятью, если вы передадите очень большие struct
s, что приведет к их копированию.Несколько предостережений / наблюдений:
На практике это редко является проблемой, потому что мы часто используем собственные «расширяемые» свойства Swift, такие как String
, Array
, Set
, Dictionary
, Data
и т. Д., И те имеют поведение «копировать при записи» (CoW).Это означает, что если вы создаете копию struct
, весь объект не обязательно копируется, а скорее использует внутреннее ссылочное поведение, чтобы избежать ненужного дублирования при сохранении семантики типа значения.Но если вы измените объект, о котором идет речь, только тогда будет сделана копия.
Это лучшее из обоих миров, где вы наслаждаетесь семантикой значения (без непреднамеренного совместного использования), без ненужного дублирования данных для этих объектов.конкретные типы.
Рассмотрим:
struct Foo {
private var data = Data(repeating: 0, count: 8_000)
mutating func update(at: Int, with value: UInt8) {
data[at] = value
}
}
Приватный Data
в этом примере будет использовать поведение CoW, поэтому при создании копий экземпляра Foo
, большая полезная нагрузка выигралаНе копировать, пока вы не измените его.
В итоге вы задали гипотетический вопрос, и ответ на самом деле зависит от того, какие типы задействованы в вашей большой полезной нагрузке.Но для многих нативных типов Swift это часто не проблема.
Давайте представим, однако, что вы имеете дело с крайним случаем, когда (a) ваша общая полезная нагрузка велика;(б) ваш struct
был составлен из типов, которые не используют CoW (то есть, не один из вышеупомянутых расширяемых типов Swift);и (c) вы хотите продолжать пользоваться семантикой значений (то есть не переходить на ссылочный тип с риском непреднамеренного совместного использования).В видео WWDC 2015 Создание улучшенных приложений с типами значений они показывают нам, как самостоятельно применять шаблон CoW, избегая ненужных копий и все же применяя истинное поведение типа значения после того, как объект мутирует.
Обратите внимание:
struct Foo {
var value0 = 0.0
var value1 = 0.0
var value2 = 0.0
...
}
Вы могли бы переместить их в закрытый ссылочный тип:
private class FooPayload {
var value0 = 0.0
var value1 = 0.0
var value2 = 0.0
...
}
extension FooPayload: NSCopying {
func copy(with zone: NSZone? = nil) -> Any {
let object = FooPayload()
object.value0 = value0
...
return object
}
}
Затем вы можете изменить свой выставленный тип значения, чтобы использовать этот закрытый ссылочный тип, а затем реализовать семантику CoW в любом изметоды мутации, например:
struct Foo {
private var _payload: FooPayload
init() {
_payload = FooPayload()
}
mutating func updateSomeValue(to value: Double) {
copyIfNeeded()
_payload.value0 = value
}
private mutating func copyIfNeeded() {
if !isKnownUniquelyReferenced(&_payload) {
_payload = _payload.copy() as! FooPayload
}
}
}
Метод copyIfNeeded
выполняет семантику CoW, используя isKnownUniquelyReferenced
для копирования только в том случае, если на эту полезную нагрузку не ссылаются однозначно.
Это может бытьнемного, но это иллюстрирует, как добиться шаблона CoW для ваших собственных типов значений, если ваша большая полезная нагрузка еще не использует CoW.Я бы только предложил сделать это, если (а) ваша полезная нагрузка велика;(б) вы знаете, что соответствующие свойства полезной нагрузки еще не поддерживают CoW, и (в) вы определили, что вам действительно нужно такое поведение.
Если вы столкнулись сПротоколы как типы, Swift автоматически использует CoW, сам за кулисами, Swift будет делать новые копии больших типов значений только тогда, когда тип значения мутирует.Но если ваши несколько экземпляров не изменились, он не создаст копии большой полезной нагрузки.
Для получения дополнительной информации см. Видео WWDC 2017 Что нового в Swift: CoW Existential Buffers :
Чтобы представить значение неизвестного типа, компилятор использует структуру данных, которую мы называем экзистенциальным контейнером.Внутри экзистенциального контейнера есть встроенный буфер для хранения небольших значений.В настоящее время мы переоцениваем размер этого буфера, но для Swift 4 он остается теми же 3 словами, что и в прошлом.Если значение слишком велико, чтобы поместиться в встроенный буфер, оно выделяется в куче.
И хранилище в куче может быть очень дорогим.Это то, что вызвало прекращение работы, которое мы только что видели.Итак, что мы можем с этим поделать?Ответ - буферы коровы, экзистенциальные буферы CoW ...
... CoW - это сокращение от "копировать при записи".Возможно, вы слышали, что мы говорим о тего раньше, потому что это ключ к высокой производительности с семантикой значения.В Swift 4, если значение слишком велико, чтобы поместиться во встроенный буфер, оно выделяется в куче вместе со счетчиком ссылок.Несколько экзистенциальных контейнеров могут совместно использовать один и тот же буфер, если они только читают из него.
И это позволяет избежать дорогостоящего выделения кучи.Буфер нужно копировать с отдельным выделением только в том случае, если он модифицирован, когда на него имеется несколько ссылок.И Swift теперь управляет сложностью этого для вас полностью автоматически.
Для получения дополнительной информации о экзистенциальных контейнерах и CoW, я отсылаю вас к видео WWDC 2016 Понимание Swift Performance .