I came up with to other ideas, because I still wonder if the whole thing can be solved with combine:
First one:
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)'
*/
But, I'm still looking for a solution with combine only (without the dirty solution with the newAverage variable).
I also tried a solution with a custom DispatchQueue (it is just an attempt, not necessarily a good solution):
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'
*/
but that doesn't work either...
add removeDuplicates was suggested at Stackoverflow: Change Variable only if other Variables have changed and Update is requested