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