Как правильно сделать ленивые получена собственность на изменения структуры в Swift?
Я создаю мутирующую структуру с очень дорогим для вычисления производным значением. Поэтому я хочу лениво вычислить это производное значение и хранить результат до тех пор, пока структура снова не мутирует, и в этот момент производное значение больше не является допустимым и нуждается в повторном вычислении.
(ошибка) Вариант 1: генерируемое СВОЙСТВО
Если производное значение является сгенерированным свойством (как показано ниже), правильное значение всегда возвращается, но всегда пересчитывается.
(не удалось) Вариант 2: свойство Lazy-loaded
Если это свойство является ленивым, то вычисление выполняется только один раз... когда-либо. Таким образом, как только структура мутирует, полученное значение неверно и не будет пересчитано. Кроме того, я не могу получить доступ к свойству, если я назначаю постоянное значение из структуры.
Есть ли какое-либо возможное решение в Swift 1.2 или мне нужно подать радар?
struct Struct {
var value: Int
// Option 1: Generated property
var derivedValue: Int {
println("Doing expensive calculation")
return self.value * 2
}
// Option 2: Lazy property
lazy var derivedValue: Int = {
println("Doing expensive calculation")
return self.value * 2
}()
init(value: Int) {
self.value = value
}
mutating func mutate() {
value = random()
}
}
var test = Struct(value: 2)
test.derivedValue
test.derivedValue // If not lazy, expensive calculation is done again here
test.mutate()
test.derivedValue // If lazy, this has wrong value
let test2 = test
test2.derivedValue // Compiler error if using lazy implementation
2 ответа:
Использование встроенного класса позволяет обойти ограничения на изменение структуры. Это позволяет использовать тип by-value, который не выполняет дорогостоящие вычисления до тех пор, пока они не понадобятся, но все равно запоминает результат позже.
Приведенный ниже пример структуры
Numberвычисляет и запоминает свое квадратное свойство таким образом, что ведет себя точно так же, как вы описываете. Сама математика смехотворно неэффективна, но это простой способ проиллюстрировать решение.struct Number { // Store a cache in a nested class. // The struct only contains a reference to the class, not the class itself, // so the struct cannot prevent the class from mutating. private class Cache { var square: Int? var multiples: [Int: Int] = [:] } private var cache = Cache() // Empty the cache whenever the struct mutates. var value: Int { willSet { cache = Cache() } } // Prevent Swift from generating an unwanted default initializer. // (i.e. init(cache: Number.Cache, value: Int)) init(value: Int) { self.value = value } var square: Int { // If the computed variable has been cached... if let result = cache.square { // ...return it. print("I’m glad I don’t have to do that again.") return result } else { // Otherwise perform the expensive calculation... print("This is taking forever!") var result = 0 for var i = 1; i <= value; ++i { result += value } // ...store the result to the cache... cache.square = result // ...and return it. return result } } // A more complex example that caches the varying results // of performing an expensive operation on an input parameter. func multiple(coefficient: Int) -> Int { if let result = cache.multiples[coefficient] { return result } else { var result = 0 for var i = 1; i <= coefficient; ++i { result += value } cache.multiples[coefficient] = result return result } } }И вот как это происходит. выполняет:
В качестве более реалистичного примера я использовал этот метод на структурах дат, чтобы убедиться, что нетривиальные вычисления для преобразования между календарными системами выполняются как можно меньше.// The expensive calculation only happens once... var number = Number(value: 1000) let a = number.square // “This is taking forever!” let b = number.square // “I’m glad I don’t have to do that again.” let c = number.square // “I’m glad I don’t have to do that again.” // Unless there has been a mutation since last time. number.value = 10000 let d = number.square // “This is taking forever!” let e = number.square // “I’m glad I don’t have to do that again.” // The cache even persists across copies... var anotherNumber = number let f = anotherNumber.square // “I’m glad I don’t have to do that again.” // ... until they mutate. anotherNumber.value = 100 let g = anotherNumber.square // “This is taking forever!”
Это действительно интересный вопрос. У меня есть несколько разных идей, которые могут помочь.
Во-первых, вы слегка злоупотребляете идеей свойстваlazy. Вы можете иметь только lazy сохраненные свойства, потому что все, что делает lazy, это задерживает выполнение до тех пор, пока оно не будет выполнено первым. После этого это значение будетstoredв свойстве. Вы имеете дело с вычисляемым свойством, которое не может быть использовано таким образом. Вы, конечно, можете подать радар, но я думаю, что это безнадежное дело потому что ваш вариант использования не является допустимым ленивым случаем IMO. С учетом сказанного, я думаю, у вас есть несколько вариантов.Вариант 1-использовать класс с наблюдателями свойств
Преимущество здесь в том, что вы все еще можете лениво вычислитьclass Calculator { var value: Int { didSet { valueChanged = true } } var valueChanged = false var derivedValue: Int { if valueChanged { println("Doing expensive calculation") valueChanged = false } return self.value * 2 } init(value: Int) { self.value = value } func mutate() { value = random() } }derivedValueв точке, где свойство называется. Недостатком является то, что вы больше не используете объект "по значению".Вариант 2-вычислить дорогое значение в методе Mutate
struct SortOfLazyCalculator { var value: Int var expensiveComputedValue: Int = 0 // just guessing var derivedValue: Int { return self.value * 2 } init(value: Int) { self.value = value } mutating func mutate() { value = random() expensiveComputedValue = random() // not sure what the expensive calculation is } }Преимущество этого подхода это то, что вы все еще можете сохранить свой объект "по стоимости", но вы должны вычислить дорогостоящее значение в момент мутации. Вы не можете сделать это внутри свойства
derivedValue, потому что вы не можете мутироватьselfвнутри вычисляемого свойства для структуры.Вариант 3-использовать статическую структуру для отслеживания изменений значений
struct Struct { var value: Int var derivedValue: Int { struct Static { static var previousValue: Int? } if Static.previousValue == nil { println("Setting previous value since it is nil") Static.previousValue = value } if value != Static.previousValue! { println("Doing expensive calculation") Static.previousValue = value } return self.value * 2 } init(value: Int) { self.value = value } mutating func mutate() { value = random() } }Этот подход позволяет сохранить ваш объект "по стоимости", а также позволяет лениво вычислять дорогостоящее значение. Однако главная проблема здесь заключается в том, что это будет работайте только для одного объекта. Если вы создаете несколько объектов, это плохой подход.
Резюме
К сожалению, это не допустимый вариант использования для ленивого свойства. Однако существуют и другие подходы к решению этой проблемы. Надеюсь, одного из них будет достаточно. Основываясь на всей информации, которую вы предоставили, я рискну предположить, что Вариант 2, вероятно, ваш лучший выбор.