我遇到以下问题:
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)
每次更改items-Array时,都会设置var avg
的值。我了解这是预期的行为。
但是,只有在items-Array已更改并且调用了诸如“ Request”之类的方法时,才有任何方法可以更新变量avg
。
如果仅更改了项目,则不应该更新变量avg
,也可以仅调用“请求”,但没有更改项目,则不应该更新变量。
我不知道该怎么做。
您是否有任何想法要使用Combine框架或其他解决方案?
编辑-2020年1月23日:
我可以做这样的事情:
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($0.map{$0.foo}.reduce(0, +))/Double($0.count)}
.sink{self.newAverage = $0}
}
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)'
*/
但是,我仍在寻找一种仅使用Combine的解决方案(没有带有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{ $0.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'
*/
但这也不起作用...
答案 0 :(得分: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
如果您在查看结果的初始值时遇到麻烦,可以采用代码,但是与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()
根本无法减少计算量。它充当发布者的“筛选器”。
您想拥有的很简单,您必须存储每个“操作”的结果并将其发布在单独的“操作”上
更改的部分
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)
}
}
现在您还有一个“操作请求”
test.request(.last)
它只会发布最后的结果,而无需任何计算
并且在尝试:-)之前不要忘记tu更新
enum Operator {
case count
case sum
case average
case last
}