Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/116.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 在Swift Combine中以优雅的方式进行组合测试,而不会降低价值和不平衡的发布者_Ios_Swift_Combine - Fatal编程技术网

Ios 在Swift Combine中以优雅的方式进行组合测试,而不会降低价值和不平衡的发布者

Ios 在Swift Combine中以优雅的方式进行组合测试,而不会降低价值和不平衡的发布者,ios,swift,combine,Ios,Swift,Combine,我有两个发布者A和B。它们是不平衡的,因为A将发出3个值,然后完成,B将只发出1个值,然后完成(A实际上可以发出一个可变数字,如果有帮助,B将保持1): B也异步运行,并且可能只在a已经发出其第二个值之后才发出一个值(见上图)。(B也可能只在任何时候发出,包括在A已经完成之后。) 我想发布A值与B值组合的元组: (1, X) (2, X) (3, X) CombineTest不适合该作业,因为它将跳过A的第一个值,并且只发出(2,X)和(3,X)zip对我不起作用,因为B只发出一个值 我正在寻

我有两个发布者A和B。它们是不平衡的,因为A将发出3个值,然后完成,B将只发出1个值,然后完成(A实际上可以发出一个可变数字,如果有帮助,B将保持1):

B也异步运行,并且可能只在a已经发出其第二个值之后才发出一个值(见上图)。(B也可能只在任何时候发出,包括在A已经完成之后。)

我想发布A值与B值组合的元组:

(1, X) (2, X) (3, X)
CombineTest
不适合该作业,因为它将跳过A的第一个值,并且只发出
(2,X)
(3,X)
<另一方面,code>zip对我不起作用,因为B只发出一个值

我正在寻找一种优雅的方式来实现这一点。谢谢


编辑并找到解决方案

有点哲学性,但我认为如果你想走
zip
combineLatest
路线,有一个基本问题。当您等待较慢的发布服务器开始发出值时,您肯定需要某种存储,以便较快的发布服务器缓冲事件

一种解决方案可能是创建一个发布服务器,该发布服务器从a收集事件,直到B发出,然后发出所有收集的事件,并继续发出a给出的内容。这实际上可以通过

let bufferedSubject1 = Publishers.Concatenate(
   prefix: Publishers.PrefixUntilOutput(upstream: subject1, other: subject2).collect().flatMap(\.publisher),
   suffix: subject1)
PrefixUntilOutput
将收集所有内容,直到B发出(
subject2
),然后切换到仅定期传递其输出

但是如果你跑

let cancel = bufferedSubject1.combineLatest(subject2)    
    .sink(receiveCompletion: { c in
        print(c)
    }, receiveValue: { v in
        print(v)
    })
您仍然缺少
(1,X)
中的第一个值——这似乎有点像竞争条件:是
bufferedSubject1
首先发出所有值,还是subject2首先向
CombineTest
提供值

我认为有趣的是,没有任何异步调用,行为似乎是未定义的。如果您运行下面的示例,有时™️ 您将发出所有值。有时您会错过
(1,X)
。因为这里没有异步调用,也没有dispatchQueue切换,所以我甚至认为这是一个bug

您可以通过在
bufferedSubject1
combinelatetest
之间提供
delay
甚至只提供
receive(在:DispatchQueue.main上)
来“脏修复”竞争条件,以便在继续管道之前,我们将控制权交还给DispatchQueue,并让subject2向
combinelatetest
发出

然而,我不认为优雅和仍然在寻找一种解决方案,它使用
zip
语义,但不必创建相同价值的无限集合(在我看来,这不适合顺序处理和无限需求)

样本:

var subject1 = PassthroughSubject<Int, Never>()
var subject2 = PassthroughSubject<String, Never>()

let bufferedSubject1 = Publishers.Concatenate(prefix: Publishers.PrefixUntilOutput(upstream: subject1, other: subject2).collect().flatMap(\.publisher),
                                      suffix: subject1)

let bufferedSubject2 = Publishers.Concatenate(prefix: Publishers.PrefixUntilOutput(upstream: subject2, other: subject1).collect().flatMap(\.publisher),
                                      suffix: subject2)

let cancel = bufferedSubject1.combineLatest(subject2)
    .sink(receiveCompletion: { c in
        print(c)
    }, receiveValue: { v in
        print(v)
    })

subject1.send(1)
subject1.send(2)
subject2.send("X")
subject2.send(completion: .finished)
subject1.send(3)
subject1.send(completion: .finished)
var subject1=PassthroughSubject()
var subject2=PassthroughSubject()
让bufferedSubject1=publisher.Concatenate(前缀:publisher.PrefixUntilOutput(上游:subject1,其他:subject2).collect().flatMap(\.publisher),
后缀:subject1)
让bufferedSubject2=publisher.Concatenate(前缀:publisher.PrefixUntilOutput(上游:subject2,其他:subject1).collect().flatMap(\.publisher),
后缀:subject2)
让cancel=bufferedSubject1.CombineTest(subject2)
.sink(接收完成:{c in
印刷品(c)
},receiveValue:{v in
印刷品(五)
})
主题1.发送(1)
主题1.发送(2)
主题2.发送(“X”)
主题2.发送(完成:。已完成)
主题1.发送(3)
subject1.send(完成:。已完成)
编辑 在结结巴巴地讲了这个问题之后,我想知道您的示例中的问题是否与
PassthroughSubject
无关:

如果下游没有任何要求,PassthroughSubject将删除值

事实上,使用:

var subject1 = Timer.publish(every: 1, on: .main, in: .default, options: nil)
    .autoconnect()
    .measureInterval(using: RunLoop.main, options: nil)
    .scan(DateInterval()) { res, interval in
        .init(start: res.start, duration: res.duration + interval.magnitude)
    }
    .map(\.duration)
    .map { Int($0) }
    .eraseToAnyPublisher()
var subject2 = PassthroughSubject<String, Never>()

let bufferedSubject1 = Publishers.Concatenate(prefix: Publishers.PrefixUntilOutput(upstream: subject1, other: subject2).collect().flatMap(\.publisher),
                                              suffix: subject1)

let cancel = bufferedSubject1.combineLatest(subject2)
    .sink(receiveCompletion: { c in
        print(c)
    }, receiveValue: { v in
        print(v)
    })

subject2.send("X")

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    subject2.send("Y")
}
这似乎是人们想要的行为


我不知道这是否是一个优雅的解决方案,但您可以尝试使用:

输出:

(X, 1)
(X, 2)
(X, 3)
(Y, 3)
(Y, 4)
(Y, 5)
(Y, 6)
编辑 在结结巴巴地讲了这个问题之后,我想知道您的示例中的问题是否与
PassthroughSubject
无关:

如果下游没有任何要求,PassthroughSubject将删除值

事实上,使用:

var subject1 = Timer.publish(every: 1, on: .main, in: .default, options: nil)
    .autoconnect()
    .measureInterval(using: RunLoop.main, options: nil)
    .scan(DateInterval()) { res, interval in
        .init(start: res.start, duration: res.duration + interval.magnitude)
    }
    .map(\.duration)
    .map { Int($0) }
    .eraseToAnyPublisher()
var subject2 = PassthroughSubject<String, Never>()

let bufferedSubject1 = Publishers.Concatenate(prefix: Publishers.PrefixUntilOutput(upstream: subject1, other: subject2).collect().flatMap(\.publisher),
                                              suffix: subject1)

let cancel = bufferedSubject1.combineLatest(subject2)
    .sink(receiveCompletion: { c in
        print(c)
    }, receiveValue: { v in
        print(v)
    })

subject2.send("X")

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    subject2.send("Y")
}
这似乎是人们想要的行为


我不知道这是否是一个优雅的解决方案,但您可以尝试使用:

输出:

(X, 1)
(X, 2)
(X, 3)
(Y, 3)
(Y, 4)
(Y, 5)
(Y, 6)
另一方面,zip对我来说不起作用,因为B只发出一个值

正确,所以修正它,使其不正确。在B处启动管道。使用flatmap将其信号转换为该信号序列的发布者,重复。用拉链把它拉上

例如:

import UIKit
import Combine

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}

class ViewController: UIViewController {
    var storage = Set<AnyCancellable>()
    let s1 = PassthroughSubject<Int,Never>()
    let s2 = PassthroughSubject<String,Never>()
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let p1 = s1
        let p2 = s2.flatMap { (val:String) -> AnyPublisher<String,Never> in
            let seq = Array(repeating: val, count: 100)
            return seq.publisher.eraseToAnyPublisher()
        }
        p1.zip(p2)
            .sink{print($0)}
            .store(in: &storage)
        
        delay(1) {
            self.s1.send(1)
        }
        delay(2) {
            self.s1.send(2)
        }
        delay(3) {
            self.s1.send(3)
        }
        delay(2.5) {
            self.s2.send("X")
        }
    }
}
另一方面,zip对我来说不起作用,因为B只发出一个值

正确,所以修正它,使其不正确。在B处启动管道。使用flatmap将其信号转换为该信号序列的发布者,重复。用拉链把它拉上

例如:

import UIKit
import Combine

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}

class ViewController: UIViewController {
    var storage = Set<AnyCancellable>()
    let s1 = PassthroughSubject<Int,Never>()
    let s2 = PassthroughSubject<String,Never>()
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let p1 = s1
        let p2 = s2.flatMap { (val:String) -> AnyPublisher<String,Never> in
            let seq = Array(repeating: val, count: 100)
            return seq.publisher.eraseToAnyPublisher()
        }
        p1.zip(p2)
            .sink{print($0)}
            .store(in: &storage)
        
        delay(1) {
            self.s1.send(1)
        }
        delay(2) {
            self.s1.send(2)
        }
        delay(3) {
            self.s1.send(3)
        }
        delay(2.5) {
            self.s2.send("X")
        }
    }
}

好的,这是一个有趣的挑战,虽然看起来很简单,但我找不到一个简单优雅的方法

这里有一种工作方法(虽然很难做到优雅),它似乎不受使用
PrefixUntilOutput
/
串联
组合的竞争条件的影响

我们的想法是使用
combinelatetest
,但是第一个发布者一发出,另一个值就是
nil
,这样我们就不会丢失初始值。这里有一个方便的操作符,我称之为
combineLatestOptional

extension Publisher {
    func combineLatestOptional<Other: Publisher>(_ other: Other) 
        -> AnyPublisher<(Output?, Other.Output?), Failure> 
        where Other.Failure == Failure {
        
        self.map { Optional.some($0) }.prepend(nil)
            .combineLatest(
                other.map { Optional.some($0) }.prepend(nil)
            )
            .dropFirst() // drop the first (nil, nil)
            .eraseToAnyPublisher()
    }
}
最后一个操作符
combineLatestLossless
的实现方式如下:

extension Publisher {
   func combineLatestLossless<Other: Publisher>(_ other: Other) 
          -> AnyPublisher<(Output, Other.Output), Failure> 
          where Failure == Other.Failure {

      self.combineLatestOptional(other)
          .scan(State<Output, Other.Output>.initial, { state, tuple in
             switch (state, tuple.0, tuple.1) {
             case (.initial, let l?, nil):       // left emits first value
                return .left([l])                // -> collect left values

             case (.initial, nil, let r?):       // right emits first value
                return .right([r])               // -> collect right values

             case (.left(let ls), let l?, nil):  // left emits another
                return .left(ls + [l])           // -> append to left values

             case (.right(let rs), nil, let r?): // right emits another
                return .right(rs + [r])          // -> append to right values

             case (.left(let ls), _, let r?):    // right emits after left
                return .final(ls, [r])           // -> go to steady-state

             case (.right(let rs), let l?, _):   // left emits after right
                return .final([l], rs)           // -> go to steady-state

             case (.final, let l?, let r?):      // final steady-state
                return .final([l], [r])          // -> pass the values as-is
             default:
                fatalError("shouldn't happen")
             }
         })
         .flatMap { status -> AnyPublisher<(Output, Other.Output), Failure> in
            if case .final(let ls, let rs) = status {
               return ls.flatMap { l in rs.map { r in (l, r) }}
                   .publisher
                   .setFailureType(to: Failure.self)
                   .eraseToAnyPublisher()
            } else {
               return Empty().eraseToAnyPublisher()
            }
         }
         .eraseToAnyPublisher()
   }
}

好的,这是一个有趣的挑战,虽然看起来很简单,但我找不到一个简单优雅的方法

这里有一种工作方法(虽然很难做到优雅),它似乎不受使用
PrefixUntilOutput
/fileprivate enum State<L, R> { case initial // before any one publisher emitted case left([L]) // left emitted; right hasn't emitted case right([R]) // right emitted; left hasn't emitted case final([L], [R]) // final steady-state }
extension Publisher {
   func combineLatestLossless<Other: Publisher>(_ other: Other) 
          -> AnyPublisher<(Output, Other.Output), Failure> 
          where Failure == Other.Failure {

      self.combineLatestOptional(other)
          .scan(State<Output, Other.Output>.initial, { state, tuple in
             switch (state, tuple.0, tuple.1) {
             case (.initial, let l?, nil):       // left emits first value
                return .left([l])                // -> collect left values

             case (.initial, nil, let r?):       // right emits first value
                return .right([r])               // -> collect right values

             case (.left(let ls), let l?, nil):  // left emits another
                return .left(ls + [l])           // -> append to left values

             case (.right(let rs), nil, let r?): // right emits another
                return .right(rs + [r])          // -> append to right values

             case (.left(let ls), _, let r?):    // right emits after left
                return .final(ls, [r])           // -> go to steady-state

             case (.right(let rs), let l?, _):   // left emits after right
                return .final([l], rs)           // -> go to steady-state

             case (.final, let l?, let r?):      // final steady-state
                return .final([l], [r])          // -> pass the values as-is
             default:
                fatalError("shouldn't happen")
             }
         })
         .flatMap { status -> AnyPublisher<(Output, Other.Output), Failure> in
            if case .final(let ls, let rs) = status {
               return ls.flatMap { l in rs.map { r in (l, r) }}
                   .publisher
                   .setFailureType(to: Failure.self)
                   .eraseToAnyPublisher()
            } else {
               return Empty().eraseToAnyPublisher()
            }
         }
         .eraseToAnyPublisher()
   }
}
let c = pub1.combineLatestLossless(pub2)
            .sink { print($0) }