仅当其他变量已更改并请求更新时才更改变量
Change Variable only if other Variables have changed and Update is requested
我有以下问题:
struct Item {
var foo: Int
init(_ iFoo: Int = 0){
self.foo = iFoo
}
}
class TestObject {
@Published var items = [Item(1), Item(2), Item(3), Item(4)]
private var avg:Double = 0.0{
didSet{
print("didSet: avg: '\(self.avg)'")
}
}
private var cancellableSet: Set<AnyCancellable> = []
private var isItemChangedPublisher: AnyPublisher<[Item], Never>{
self.$items
.eraseToAnyPublisher()
}
init(){
self.isItemChangedPublisher
.map{ items in
var sum = 0
for item in items{
sum += item.foo
}
return Double(sum)/Double(items.count)}
.assign(to: \.avg, on: self)
.store(in: &cancellableSet)
}
func changeItem(at index: Int, to value: Int){
if self.items.count < index{
self.items.append(Item(value))
}else{
self.items[index].foo = value
}
}
func getAvg() -> Double{
//Request: Items changed --> Change Value of avg here
//Set Value of avg only if items has changed AND "Request" is called
// - don't set the new Value if Items has not changed and "Request" is called
// - don't set the new Value if Items has changed, but "Request" is not called
return self.avg
}
}
var bar = TestObject()
bar.changeItem(at: 2, to: 20)
bar.changeItem(at: 0, to: 3)
print("1. avg: '\(bar.getAvg())'")
bar.changeItem(at: 2, to: 20)
print("2. avg: '\(bar.getAvg())'")
bar.changeItem(at: 2, to: 30)
print("3. avg: '\(bar.getAvg())'")
bar.changeItem(at: 2, to: 20)
每次更改项目数组时都会设置 var avg
的值。我了解这是预期的行为。
但是,只有当项目数组发生变化并且调用了 "Request" 之类的东西时,是否有任何方法可以更新变量 avg
。
如果仅更改了项目,则不应更新变量 avg
,如果仅调用 "Request",但未更改任何项目,则不应更新变量。
我不知道该怎么做。
您有没有想过使用组合框架或其他解决方案来做到这一点?
编辑 - 23.Jan.2020:
我可以做类似的事情:
import Combine
struct Item: Equatable {
var foo: Int
init(_ iFoo: Int = 0){
self.foo = iFoo
}
}
class TestObject {
@Published var items = [Item(1), Item(2), Item(3), Item(4)]
private var newAverage: Double? {
didSet{
print("didSet: items changed --> newAverage: '\(String(describing: self.newAverage))'")
}
}
private var average:Double = 0.0{
didSet{
print("didSet: average: '\(self.average)'")
}
}
private var cancellable: AnyCancellable?
private var isItemChangedPublisher: AnyPublisher<[Item], Never>{
self.$items
.eraseToAnyPublisher()
}
init(){
cancellable = self.isItemChangedPublisher
.removeDuplicates()
.map{Double([=11=].map{[=11=].foo}.reduce(0, +))/Double([=11=].count)}
.sink{self.newAverage = [=11=]}
}
func changeItem(at index: Int, to value: Int){
if self.items.count < index{
self.items.append(Item(value))
}else{
self.items[index].foo = value
}
}
func getAverage() -> Double{
if self.newAverage != nil{
self.average = self.newAverage!
self.newAverage = nil
}
return self.average
}
}
var bar = TestObject()
bar.changeItem(at: 2, to: 20)
bar.changeItem(at: 0, to: 20)
print("1. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("2. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("3. avg: '\(bar.getAverage())'")
bar.changeItem(at: 3, to: 20)
/*
prints:
didSet: items changed --> newAverage: 'Optional(2.5)'
didSet: items changed --> newAverage: 'Optional(6.75)'
didSet: items changed --> newAverage: 'Optional(11.5)'
didSet: average: '11.5'
didSet: items changed --> newAverage: 'nil'
1. avg: '11.5'
didSet: items changed --> newAverage: 'Optional(16.0)'
didSet: average: '16.0'
didSet: items changed --> newAverage: 'nil'
2. avg: '16.0'
3. avg: '16.0'
didSet: items changed --> newAverage: 'Optional(20.0)'
*/
但是,我仍在寻找仅结合的解决方案(没有带有 newAverage
变量的脏解决方案)。
我也尝试了一个自定义DispatchQueue的解决方案(这只是一次尝试,不是一个好的解决方案或想法):
import Combine
import SwiftUI
struct Item: Equatable {
var foo: Int
init(_ iFoo: Int = 0){
self.foo = iFoo
}
}
struct MyQueue {
// let queue = DispatchQueue(label: "myQueue", attributes: .concurrent, target: .global())
let queue = DispatchQueue(label: "myQueue")
init(){
self.queue.suspend()
}
func releaseData(){
self.queue.resume()
self.queue.suspend()
}
}
class TestObject {
@Published var items = [Item(1), Item(2), Item(3), Item(4)]
private var average:Double = 0.0{
didSet{
print("didSet: average: '\(self.average)'")
}
}
private var cancellable: AnyCancellable?
let myQueue = MyQueue()
private var isItemChangedPublisher: AnyPublisher<[Item], Never>{
self.$items
.eraseToAnyPublisher()
}
init(){
cancellable = self.isItemChangedPublisher
.removeDuplicates()
.map{ items in
Double(items.map{ [=12=].foo }.reduce(0, +))/Double(items.count)}
.buffer(size: 1, prefetch: .keepFull, whenFull: .dropOldest) //The Buffer changes nothing
.receive(on: self.myQueue.queue)
.assign(to: \.average, on: self)
}
func changeItem(at index: Int, to value: Int){
if self.items.count < index{
self.items.append(Item(value))
}else{
self.items[index].foo = value
}
}
func getAverage() -> Double{
self.myQueue.releaseData()
return self.average
}
}
var bar = TestObject()
bar.changeItem(at: 2, to: 20)
bar.changeItem(at: 0, to: 20)
print("1. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("2. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("3. avg: '\(bar.getAverage())'")
bar.changeItem(at: 3, to: 20)
/*
Prints:
didSet: average: '2.5'
1. avg: '2.5'
didSet: average: '6.75'
didSet: average: '11.5'
2. avg: '11.5'
didSet: average: '16.0'
3. avg: '16.0'
But im looking for:
didSet: average: '11.5' (because 2.5 and 6.5 are dropped)
1. avg: '11.5'
didSet: average: '16.0'
2. avg: '16.0'
3. avg: '16.0'
*/
但这也不管用...
让事情变得简单!
import Combine
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum Operator {
case count
case sum
case average
}
class TestObject {
@Published var items = [1, 2, 3, 4]
@Published var result: Double = .nan
private var count: Double {
Double(items.count)
}
private var average: Double {
guard items.count > 0 else {
return .nan
}
return sum / count
}
private var sum: Double {
return Double(items.reduce(0, +))
}
func request( _ operation: Operator) {
switch operation {
case .count:
result = count
case .sum:
result = sum
case .average:
result = average
}
}
}
let test = TestObject()
let pub = test.$result.sink { (res) in
print("Result:", res)
}
test.items[0] = 10
test.items[1] = 20
test.items[2] = 30
test.request(.sum)
test.request(.average)
test.request(.count)
test.items.append(contentsOf: [1, 2, 3])
test.request(.average)
仅打印初始值并根据要求打印...
Result: nan
Result: 64.0
Result: 16.0
Result: 4.0
Result: 10.0
如果看不到result的初始值,可以采用代码,但是用SwiftUI会比较难
import Combine
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum Operator {
case count
case sum
case average
}
class TestObject {
@Published var items = [1, 2, 3, 4]
//@Published var result: Double = .nan
let result = PassthroughSubject<Double,Never>()
private var count: Double {
Double(items.count)
}
private var average: Double {
guard items.count > 0 else {
return .nan
}
return sum / count
}
private var sum: Double {
return Double(items.reduce(0, +))
}
func request( _ operation: Operator) {
switch operation {
case .count:
//result = count
result.send(count)
case .sum:
//result = sum
result.send(sum)
case .average:
//result = average
result.send(average)
}
}
}
let test = TestObject()
/*
let pub = test.$result.sink { (res) in
print("Result:", res)
}*/
let pub = test.result.sink { (res) in
print("Result:", res)
}
test.items[0] = 10
test.items[1] = 20
test.items[2] = 30
test.request(.sum)
test.request(.average)
test.request(.count)
test.items.append(contentsOf: [1, 2, 3])
test.request(.average)
结果值只会根据要求发布(没有可用的初始值)
Result: 64.0
Result: 16.0
Result: 4.0
Result: 10.0
更新:
使用 .removeDuplicates()
根本无助于减少计算量。它作为发布者的 "filter"。
你想要的很简单,你必须存储每个 "operation" 的结果并将其发布到单独的 "operation"
更改部分
private var _last: Double = .nan
private var count: Double {
Double(items.count)
}
private var average: Double {
guard items.count > 0 else {
return .nan
}
_last = sum / count
return _last
}
private var sum: Double {
_last = Double(items.reduce(0, +))
return _last
}
func request( _ operation: Operator) {
switch operation {
case .count:
//result = count
result.send(count)
case .sum:
//result = sum
result.send(sum)
case .average:
//result = average
result.send(average)
case .last:
result.send(_last)
}
}
现在你又多了一个"operation request"
test.request(.last)
只发布最后的结果,不做任何计算
并且在尝试之前不要忘记更新 :-)
enum Operator {
case count
case sum
case average
case last
}
我有以下问题:
struct Item {
var foo: Int
init(_ iFoo: Int = 0){
self.foo = iFoo
}
}
class TestObject {
@Published var items = [Item(1), Item(2), Item(3), Item(4)]
private var avg:Double = 0.0{
didSet{
print("didSet: avg: '\(self.avg)'")
}
}
private var cancellableSet: Set<AnyCancellable> = []
private var isItemChangedPublisher: AnyPublisher<[Item], Never>{
self.$items
.eraseToAnyPublisher()
}
init(){
self.isItemChangedPublisher
.map{ items in
var sum = 0
for item in items{
sum += item.foo
}
return Double(sum)/Double(items.count)}
.assign(to: \.avg, on: self)
.store(in: &cancellableSet)
}
func changeItem(at index: Int, to value: Int){
if self.items.count < index{
self.items.append(Item(value))
}else{
self.items[index].foo = value
}
}
func getAvg() -> Double{
//Request: Items changed --> Change Value of avg here
//Set Value of avg only if items has changed AND "Request" is called
// - don't set the new Value if Items has not changed and "Request" is called
// - don't set the new Value if Items has changed, but "Request" is not called
return self.avg
}
}
var bar = TestObject()
bar.changeItem(at: 2, to: 20)
bar.changeItem(at: 0, to: 3)
print("1. avg: '\(bar.getAvg())'")
bar.changeItem(at: 2, to: 20)
print("2. avg: '\(bar.getAvg())'")
bar.changeItem(at: 2, to: 30)
print("3. avg: '\(bar.getAvg())'")
bar.changeItem(at: 2, to: 20)
每次更改项目数组时都会设置 var avg
的值。我了解这是预期的行为。
但是,只有当项目数组发生变化并且调用了 "Request" 之类的东西时,是否有任何方法可以更新变量 avg
。
如果仅更改了项目,则不应更新变量 avg
,如果仅调用 "Request",但未更改任何项目,则不应更新变量。
我不知道该怎么做。
您有没有想过使用组合框架或其他解决方案来做到这一点?
编辑 - 23.Jan.2020:
我可以做类似的事情:
import Combine
struct Item: Equatable {
var foo: Int
init(_ iFoo: Int = 0){
self.foo = iFoo
}
}
class TestObject {
@Published var items = [Item(1), Item(2), Item(3), Item(4)]
private var newAverage: Double? {
didSet{
print("didSet: items changed --> newAverage: '\(String(describing: self.newAverage))'")
}
}
private var average:Double = 0.0{
didSet{
print("didSet: average: '\(self.average)'")
}
}
private var cancellable: AnyCancellable?
private var isItemChangedPublisher: AnyPublisher<[Item], Never>{
self.$items
.eraseToAnyPublisher()
}
init(){
cancellable = self.isItemChangedPublisher
.removeDuplicates()
.map{Double([=11=].map{[=11=].foo}.reduce(0, +))/Double([=11=].count)}
.sink{self.newAverage = [=11=]}
}
func changeItem(at index: Int, to value: Int){
if self.items.count < index{
self.items.append(Item(value))
}else{
self.items[index].foo = value
}
}
func getAverage() -> Double{
if self.newAverage != nil{
self.average = self.newAverage!
self.newAverage = nil
}
return self.average
}
}
var bar = TestObject()
bar.changeItem(at: 2, to: 20)
bar.changeItem(at: 0, to: 20)
print("1. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("2. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("3. avg: '\(bar.getAverage())'")
bar.changeItem(at: 3, to: 20)
/*
prints:
didSet: items changed --> newAverage: 'Optional(2.5)'
didSet: items changed --> newAverage: 'Optional(6.75)'
didSet: items changed --> newAverage: 'Optional(11.5)'
didSet: average: '11.5'
didSet: items changed --> newAverage: 'nil'
1. avg: '11.5'
didSet: items changed --> newAverage: 'Optional(16.0)'
didSet: average: '16.0'
didSet: items changed --> newAverage: 'nil'
2. avg: '16.0'
3. avg: '16.0'
didSet: items changed --> newAverage: 'Optional(20.0)'
*/
但是,我仍在寻找仅结合的解决方案(没有带有 newAverage
变量的脏解决方案)。
我也尝试了一个自定义DispatchQueue的解决方案(这只是一次尝试,不是一个好的解决方案或想法):
import Combine
import SwiftUI
struct Item: Equatable {
var foo: Int
init(_ iFoo: Int = 0){
self.foo = iFoo
}
}
struct MyQueue {
// let queue = DispatchQueue(label: "myQueue", attributes: .concurrent, target: .global())
let queue = DispatchQueue(label: "myQueue")
init(){
self.queue.suspend()
}
func releaseData(){
self.queue.resume()
self.queue.suspend()
}
}
class TestObject {
@Published var items = [Item(1), Item(2), Item(3), Item(4)]
private var average:Double = 0.0{
didSet{
print("didSet: average: '\(self.average)'")
}
}
private var cancellable: AnyCancellable?
let myQueue = MyQueue()
private var isItemChangedPublisher: AnyPublisher<[Item], Never>{
self.$items
.eraseToAnyPublisher()
}
init(){
cancellable = self.isItemChangedPublisher
.removeDuplicates()
.map{ items in
Double(items.map{ [=12=].foo }.reduce(0, +))/Double(items.count)}
.buffer(size: 1, prefetch: .keepFull, whenFull: .dropOldest) //The Buffer changes nothing
.receive(on: self.myQueue.queue)
.assign(to: \.average, on: self)
}
func changeItem(at index: Int, to value: Int){
if self.items.count < index{
self.items.append(Item(value))
}else{
self.items[index].foo = value
}
}
func getAverage() -> Double{
self.myQueue.releaseData()
return self.average
}
}
var bar = TestObject()
bar.changeItem(at: 2, to: 20)
bar.changeItem(at: 0, to: 20)
print("1. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("2. avg: '\(bar.getAverage())'")
bar.changeItem(at: 1, to: 20)
print("3. avg: '\(bar.getAverage())'")
bar.changeItem(at: 3, to: 20)
/*
Prints:
didSet: average: '2.5'
1. avg: '2.5'
didSet: average: '6.75'
didSet: average: '11.5'
2. avg: '11.5'
didSet: average: '16.0'
3. avg: '16.0'
But im looking for:
didSet: average: '11.5' (because 2.5 and 6.5 are dropped)
1. avg: '11.5'
didSet: average: '16.0'
2. avg: '16.0'
3. avg: '16.0'
*/
但这也不管用...
让事情变得简单!
import Combine
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum Operator {
case count
case sum
case average
}
class TestObject {
@Published var items = [1, 2, 3, 4]
@Published var result: Double = .nan
private var count: Double {
Double(items.count)
}
private var average: Double {
guard items.count > 0 else {
return .nan
}
return sum / count
}
private var sum: Double {
return Double(items.reduce(0, +))
}
func request( _ operation: Operator) {
switch operation {
case .count:
result = count
case .sum:
result = sum
case .average:
result = average
}
}
}
let test = TestObject()
let pub = test.$result.sink { (res) in
print("Result:", res)
}
test.items[0] = 10
test.items[1] = 20
test.items[2] = 30
test.request(.sum)
test.request(.average)
test.request(.count)
test.items.append(contentsOf: [1, 2, 3])
test.request(.average)
仅打印初始值并根据要求打印...
Result: nan
Result: 64.0
Result: 16.0
Result: 4.0
Result: 10.0
如果看不到result的初始值,可以采用代码,但是用SwiftUI会比较难
import Combine
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum Operator {
case count
case sum
case average
}
class TestObject {
@Published var items = [1, 2, 3, 4]
//@Published var result: Double = .nan
let result = PassthroughSubject<Double,Never>()
private var count: Double {
Double(items.count)
}
private var average: Double {
guard items.count > 0 else {
return .nan
}
return sum / count
}
private var sum: Double {
return Double(items.reduce(0, +))
}
func request( _ operation: Operator) {
switch operation {
case .count:
//result = count
result.send(count)
case .sum:
//result = sum
result.send(sum)
case .average:
//result = average
result.send(average)
}
}
}
let test = TestObject()
/*
let pub = test.$result.sink { (res) in
print("Result:", res)
}*/
let pub = test.result.sink { (res) in
print("Result:", res)
}
test.items[0] = 10
test.items[1] = 20
test.items[2] = 30
test.request(.sum)
test.request(.average)
test.request(.count)
test.items.append(contentsOf: [1, 2, 3])
test.request(.average)
结果值只会根据要求发布(没有可用的初始值)
Result: 64.0
Result: 16.0
Result: 4.0
Result: 10.0
更新:
使用 .removeDuplicates()
根本无助于减少计算量。它作为发布者的 "filter"。
你想要的很简单,你必须存储每个 "operation" 的结果并将其发布到单独的 "operation"
更改部分
private var _last: Double = .nan
private var count: Double {
Double(items.count)
}
private var average: Double {
guard items.count > 0 else {
return .nan
}
_last = sum / count
return _last
}
private var sum: Double {
_last = Double(items.reduce(0, +))
return _last
}
func request( _ operation: Operator) {
switch operation {
case .count:
//result = count
result.send(count)
case .sum:
//result = sum
result.send(sum)
case .average:
//result = average
result.send(average)
case .last:
result.send(_last)
}
}
现在你又多了一个"operation request"
test.request(.last)
只发布最后的结果,不做任何计算
并且在尝试之前不要忘记更新 :-)
enum Operator {
case count
case sum
case average
case last
}