Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/ssh/2.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 使用Flatmap和多个订阅服务器时,合并多次调用的Future块_Ios_Combine - Fatal编程技术网

Ios 使用Flatmap和多个订阅服务器时,合并多次调用的Future块

Ios 使用Flatmap和多个订阅服务器时,合并多次调用的Future块,ios,combine,Ios,Combine,我已经在我的应用程序中成功地使用了异步网络请求。我决定是时候看看我是否能迁移到美国了。然而,我发现,当我将两个using与两个订阅者组合时,我的第二个代码块执行了两次。下面是一些将直接在操场上运行的示例代码: 进口联合收割机 进口基金会 扩展发布程序{ func ShowActivityIndicator同时显示消息:字符串->任何可取消{ let cancelable=sinkreceiveCompletion:{inSwift.printHide活动指示器},receiveValue:{in

我已经在我的应用程序中成功地使用了异步网络请求。我决定是时候看看我是否能迁移到美国了。然而,我发现,当我将两个using与两个订阅者组合时,我的第二个代码块执行了两次。下面是一些将直接在操场上运行的示例代码:

进口联合收割机 进口基金会 扩展发布程序{ func ShowActivityIndicator同时显示消息:字符串->任何可取消{ let cancelable=sinkreceiveCompletion:{inSwift.printHide活动指示器},receiveValue:{inIn} Swift.printBusy:\消息 可取消退货 } } 枚举服务器错误:错误{ 案例验证失败 案例无关联 案例超时 } func authenticateusername:String,密码:String->Future{ 未来{ 正在调用服务器进行身份验证 DispatchQueue.main.async{ 成功 } } } func downloadUserInfousername:String->Future{ 未来{ 打印下载用户信息 DispatchQueue.main.async{ promise.successdecoded用户数据 } } } func authenticateAndDownloadUserInfousername:String,password:String->some Publisher{ 返回authenticateusername:用户名,密码:password.flatMap{isAuthenticated->Future in 卫兵被其他人认证了{ 返回未来{$0.failure.authenticationFailed} } 返回downloadUserInfousername:username } } 让future=authenticateAndDownloadUserInfousername:stack,password:overflow 让Cancelable2=future.showActivityIndicator在下载时发送消息:请等待下载 让Cancelable1=future.sinkreceiveCompletion:{completion in 交换完成{ 案例。完成: 打印完成,没有错误。 案例。failurelet错误: printreceived错误:'\error' } }{输出在 printreceived userInfo:“\output” } 该代码模拟进行两次网络调用,并将它们作为一个成功或失败的单元进行平面映射。 结果是:

正在调用服务器进行身份验证 忙:请稍候下载 下载用户信息
下载用户信息使用let future创建实际发布服务器时,请附加.share操作符,以便您的两个订阅者订阅单个拆分管道

编辑:正如我在评论中所说的,我会在你的管道中做一些其他的改变。这里有一个建议重写。其中一些变化是风格/装饰性的,以说明我如何编写组合代码;你可以接受它,也可以离开它。但是其他的事情都是很正常的。你需要延迟包装你的未来,以防止过早的网络,即在订阅发生之前。您需要存储管道,否则它将在网络开始之前消失。我还将.handleEvents替换为您的第二个订阅者,不过如果您将上述解决方案与.share一起使用,您仍然可以使用第二个订阅者。这是一个完整的例子;您可以直接复制并粘贴到项目中

class ViewController: UIViewController {
    enum ServerError: Error {
        case authenticationFailed
        case noConnection
        case timeout
    }
    var storage = Set<AnyCancellable>()
    func authenticate(username: String, password: String) -> AnyPublisher<Bool, ServerError> {
        Deferred {
            Future { promise in
                print("Calling server to authenticate")
                DispatchQueue.main.async {
                    promise(.success(true))
                }
            }
        }.eraseToAnyPublisher()
    }
    func downloadUserInfo(username: String) -> AnyPublisher<String, ServerError> {
        Deferred {
            Future { promise in
                print("Downloading user info")
                DispatchQueue.main.async {
                    promise(.success("decoded user data"))
                }
            }
        }.eraseToAnyPublisher()
    }
    func authenticateAndDownloadUserInfo(username: String, password: String) -> AnyPublisher<String, ServerError> {
        let authenticate = self.authenticate(username: username, password: password)
        let pipeline = authenticate.flatMap { isAuthenticated -> AnyPublisher<String, ServerError> in
            if isAuthenticated {
                return self.downloadUserInfo(username: username)
            } else {
                return Fail<String, ServerError>(error: .authenticationFailed).eraseToAnyPublisher()
            }
        }
        return pipeline.eraseToAnyPublisher()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        authenticateAndDownloadUserInfo(username: "stack", password: "overflow")
            .handleEvents(
                receiveSubscription: { _ in print("start the spinner!") },
                receiveCompletion: { _ in print("stop the spinner!") }
        ).sink(receiveCompletion: {
            switch $0 {
            case .finished:
                print("Completed without errors.")
            case .failure(let error):
                print("received error: '\(error)'")
            }
        }) {
            print("received userInfo: '\($0)'")
        }.store(in: &self.storage)
    }
}

当您使用let future创建实际的发布服务器时,如果您添加.share,以便您的两个订阅者订阅单个管道,会怎么样?顺便说一句,我根本不会使用两个订阅者。我会在水槽前在管道里插一个手动通风操作员。您不需要第二个订户的开销来驱动微调器。@matt adding.share解决了这个问题。谢谢。对不起,还有一件事:你需要为你的每一个未来都写一封信。否则他们会过早开火。好吧,我会回答的。但我更愿意批评整个管道!:感谢您提供的。共享解决方案。我仍然有点困惑,为什么在FlatMap之后需要共享,而不是在我直接呼叫某个期货时,不管有多少订户。是不是因为未来在FlatMap之后变成了出版商。有什么想法吗?事实上,谜团是为什么你不能让呼叫服务器进行两次身份验证。我希望通过双重订阅复制整个管道。这个发布者是一个结构体;它具有值语义,两次订阅它会生成一个双管道,即两个独立的管道。share将发布服务器包装在一个类中,以便获得引用语义,从而可以将流拆分为两个订阅服务器。更多信息,请参见我的。好的,建议重写。这只是一个有用的建议,但由于您正处于从另一个框架迁移的过程中,您可能会发现它很有启发性。非常感谢。非常有用。我的理解是Future具有引用语义;普通Future的操作只执行一次,与订阅服务器的数量无关。因此,对我来说,当两个未来的管道被平面映射在一起时,结果管道似乎仍然有点令人惊讶 至少在第二个将来,它的行为就好像它具有值语义一样。是的,Future是一个类,但是。flatMap不是,AnyPublisher不是,所以它确实有点让人困惑在您的原始代码中,您与某个发布者一起将此类型平铺,这样天知道它是什么;这是我避免使用一些的一个原因。正如你所看到的,我将所有内容都输入到,并将所有内容展平到,AnyPublisher,因此我对每个连接处的类型都有很好的了解。
start the spinner!
Calling server to authenticate
Downloading user info
received userInfo: 'decoded user data'
stop the spinner!
Completed without errors.