"Extra argument" 使用 Combine 框架中的 CombineLatest 时出错

"Extra argument" error when using CombineLatest from the Combine framework

这个问题可以在下面的 playground 中看到。有四个已发布的值将被异步更新(一个图像和三个字符串)。当所有四个都已初始化或随后更改时, UI 将需要更新。当我尝试使用 CombineLatest4 捕获此数据流时,编译器立即反对第四个参数并显示消息 Extra argument in call。 (注意:以下代码实际上没有做任何事情,因为它只有一个发布者,但足以在 Playground 中产生错误消息。

import Combine
import UIKit

struct CustomerUpdates
{
    @Published var photo: UIImage!
    @Published var firstName: String!
    @Published var lastName: String!
    @Published var id: String!

    typealias customerTuple =
        (   photo: UIImage,
            firstName: String,
            lastName: String,
            id: String )
    var validatedCustomer: AnyPublisher< customerTuple, Never >
    {
        return Publishers.CombineLatest4( $photo,
                                          $firstName,
                                          $lastName,
                                          $id )
        {
            photo, firstName, lastName, id in
            if      photo == nil
                ||  firstName == nil
                ||  lastName == nil
                ||  id == nil
            {
                return nil
            }
            return ( photo!, firstName!, lastName!, id! )
        }
        .compactMap
        .return( on: RunLoop.main )
    }
}

我的问题是,为什么编译器标记第四个参数("id")? Apple 的 CombineLatest4 通用结构文档说:

A publisher that receives and combines the latest elements from four publishers.

问题是 CombineLatest 没有关闭,它只是在任何上游发出新值时发出从其所有上游发出的最新值。您的闭包应该提供给 compactMap,这是一个运算符,它采用闭包 returning 一个 Optional 并且仅在 return 值不是 nil.

struct CustomerUpdates {
    @Published var photo: UIImage!
    @Published var firstName: String!
    @Published var lastName: String!
    @Published var id: String!

    typealias CustomerTuple = (photo: UIImage, firstName: String, lastName: String, id: String)
    var validatedCustomer: AnyPublisher<CustomerTuple, Never> {

        return Publishers.CombineLatest4($photo, $firstName, $lastName, $id)
            .compactMap { photo, firstName, lastName, id in
                guard let photo = photo, let firstName = firstName, let lastName = lastName, let id = id else { return nil }
                return ( photo, firstName, lastName, id)
            }
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
    }
}

其他几个与您的问题无关的问题:没有这样的运算符 return(on:),您需要 receive(on:) 并且您想将 DispatchQueue.main 而不是 RunLoop.main 传递给能够直接将更新发布到您的 UI.

类型名称在 Swift 中应为 UpperCamelCase,因此请使用 CustomerTuple 而不是 customerTuple

此外,您的属性应该是 Optional (var photo: UIImage?),而不是隐式解包可选 (var photo: UIImage!).

CombineLatest 类型(及其更大的变体,包括 CombineLatest4)不采用转换闭包。但是 PublishercombineLatest operators 可以。所以如果你愿意,你可以这样说:

    return $photo.combineLatest($firstName, $lastName, $id) {
        guard
            let photo = [=10=],
            let firstName = ,
            let lastName = ,
            let id = 
            else { return nil }
        return ([=10=], , , )
    }
    .compactMap { [=10=] }
    .receive(on: RunLoop.main)