仅当其他变量已更改并请求更新时才更改变量

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
}