有没有办法避免到处使用AnyPublisher/eraseToAnyPublisher?

lhcgjxsq  于 2023-11-20  发布在  Swift
关注(0)|答案(4)|浏览(215)

我只是在学习如何使用合并。我有使用Rx(RxSwift和RxJava)的经验,我注意到它们非常相似。

但是,Publisher协议没有为其OutputFailure类型使用泛型,而是使用关联类型,这一点非常不同(也有些恼人)。

这意味着我不能指定多态的Publisher类型(如Publisher<Int, Error>),并用这些类型简单地返回符合Publisher的任何类型。我需要使用AnyPublisher<Int, Error>,并且我被迫在所有地方都包含eraseToAnyPublisher()

如果这是唯一的选择,那么我会忍受的。然而,我最近也了解到SWIFT中的不透明类型,我想知道我是否可以使用它们来解决这个问题。

有没有办法让我拥有一个返回some Publisher并为OutputFailure使用特定类型的函数?

这似乎是不透明类型的完美案例,但我不知道是否有办法既使用不透明类型又指定相关类型。

我的想象是这样的:

func createPublisher() -> some Publisher where Output = Int, Failure = Error {
    return Just(1)
}
vuktfyat

vuktfyat1#

更新

未来就是现在!如果您使用Xcode14.0测试版2或更高版本,您可以像这样编写createPublisher函数,并且应该将其重新部署到所有支持Combine的系统:

func createPublisher() -> some Publisher<Int, Never> {
    return Just(1)
}

此外,您可以使用不透明的参数类型使函数采用语法较少的泛型Publisher参数,而无需求助于存在词或AnyPublisher

func use(_ p: some Publisher<Int, Never>) { }

// means exactly the same as this:

func use<P: Publisher>(_ p: P) where P.Output == Int, P.Failure == Never { }

// and is generally more efficient than either of these:

func use(_ p: any Publisher<Int, Never>) { }
func use(_ p: AnyPublisher<Int, Never>) { }

原创

截至撰写本文时,SWIFT还没有您想要的功能。Joe Groff在他的“Improving the UI of generics” document的标题为“函数返回缺少类型级抽象”一节中特别描述了缺失的内容:
然而,想要从调用方抽象出实现选择的返回类型**是很常见的。例如,一个函数可能会生成一个集合,但不想透露它到底是哪种集合的详细信息。这可能是因为实现者希望保留在将来的版本中更改集合类型的权利,或者因为实现使用组合的lazy转换并且不想在其接口中公开长的、脆弱的、令人困惑的返回类型。首先,在这种情况下,人们可能会尝试使用存在词:

func evenValues<C: Collection>(in collection: C) -> Collection where C.Element == Int {
  return collection.lazy.filter { B1a2a1b % 2 == 0 }
}

但斯威夫特今天会告诉你,Collection只能用作通用约束,这会让人自然而然地尝试这样做:

func evenValues<C: Collection, Output: Collection>(in collection: C) -> Output
  where C.Element == Int, Output.Element == Int
{  
  return collection.lazy.filter { B1a3a1b % 2 == 0 }
}

但这也不起作用,因为如上所述,Output泛型参数是由调用方选择的-该函数签名声称能够返回调用方要求的任何类集合,而不是实现使用的一种特定类型的集合。

有可能有一天,不透明的返回类型语法(some Publisher)将被扩展以支持这种使用。

今天你有三个选择。为了理解它们,让我们考虑一个具体的例子。假设您想要从URL获取一个整数文本列表,每行一个,并将每个整数作为单独的输出发布:

return dataTaskPublisher(for: url)
    .mapError { B1a4a1b as Error }
    .flatMap { data, response in
        (response as? HTTPURLResponse)?.statusCode == 200
            ? Result.success(data).publisher
            : Result.failure(URLError(.resourceUnavailable)).publisher
    }
    .compactMap { String(data: B1a4a1b, encoding: .utf8) }
    .map { data in
        data
            .split(separator: "n")
            .compactMap { Int(B1a4a1b) }
    }
    .flatMap { B1a4a1b.publisher.mapError { B1a4a1b as Error } }

选项一:拼写返回类型

您可以使用完整、复杂的返回类型。它看起来是这样的:

extension URLSession {
    func ints(from url: URL) -> Publishers.FlatMap<
        Publishers.MapError<
            Publishers.Sequence<[Int], Never>,
            Error
        >,
        Publishers.CompactMap<
            Publishers.FlatMap<
                Result<Data, Error>.Publisher,
                Publishers.MapError<
                    URLSession.DataTaskPublisher,
                    Error
                >
            >,
            [Int]
        >
    > {
        return dataTaskPublisher(for: url)
            ... blah blah blah ...
            .flatMap { B1a5a1b.publisher.mapError { B1a5a1b as Error } }
    }
}

我自己也没弄清楚退货类型。我将返回类型设置为Int,然后编译器告诉我Int不是正确的返回类型,错误消息包含正确的返回类型。这不是很好,如果您更改实现,则必须确定新的返回类型。

选项二:使用AnyPublisher

.eraseToAnyPublisher()添加到发布服务器的末尾:

extension URLSession {
    func ints(from url: URL) -> AnyPublisher<Int, Error> {
        return dataTaskPublisher(for: url)
            ... blah blah blah ...
            .flatMap { B1a6a1b.publisher.mapError { B1a6a1b as Error } }
            .eraseToAnyPublisher()
    }
}

这是常见且简单的解决方案,通常也是您想要的。如果您不喜欢拼写eraseToAnyPublisher,您可以编写自己的Publisher扩展名来使用更短的名称,如下所示:

extension Publisher {
    var typeErased: AnyPublisher<Output, Failure> { eraseToAnyPublisher() }
}

选项三:编写自己的Publisher类型

您可以用自己的类型 Package 您的出版商。您的类型的receive(subscriber:)构造“真正的”发布服务器,然后将订阅服务器传递给它,如下所示:

extension URLSession {
    func ints(from url: URL) -> IntListPublisher {
        return .init(session: self, url: url)
    }
}

struct IntListPublisher: Publisher {
    typealias Output = Int
    typealias Failure = Error

    let session: URLSession
    let url: URL

    func receive<S: Subscriber>(subscriber: S) where
        S.Failure == Self.Failure, S.Input == Self.Output
    {
        session.dataTaskPublisher(for: url)
            .flatMap { B1a8a1b.publisher.mapError { B1a8a1b as Error } }
            ... blah blah blah ...
            .subscribe(subscriber)
    }
}
rkkpypqq

rkkpypqq2#

使用不透明返回时,类型是由闭包返回的内容定义的,因此您可以只使用

func createPublisher() -> some Publisher {
    return Just(1)
}

let cancellable = createPublisher()
   .print()
   .sink(receiveCompletion: { _ in
       print(">> done")
   }) { value in
       print(">> (value)")
   }

// ... all other code here

而且它起作用了。使用Xcode11.4进行了测试。

mtb9vblg

mtb9vblg3#

我在some Publisher(恼人的限制)上运气不佳。

一种选择是使用AnyPublisher

func a() -> AnyPublisher<(a: Int, b: String), Never> {
    return Just((a: 1, b: "two")).eraseToAnyPublisher()
}

func b() -> AnyPublisher<String, Never> {
    return a().map(.b).eraseToAnyPublisher()
}

a().sink(receiveValue: {
    let x = B1a0a1b // (a: 1, b: "two)
})

b().sink(receiveValue: {
    let x = B1a0a1b // "two"
})

或者,“Apple Way”(他们在标准库中使用的)似乎是类型别名(或 Package 器结构):

enum PublisherUtils {
    typealias A = Just<(a: Int, b: String)>
    typealias B = Publishers.MapKeyPath<A, String>
    // or implement a simple wrapper struct like what Combine does
}

func a() -> PublisherUtils.A {
    return Just((a: 1, b: "two"))
}

func b() -> PublisherUtils.B {
    return a().map(.b)
}

a().sink(receiveValue: {
    let x = B1a1a1b // (a: 1, b: "two)
})

b().sink(receiveValue: {
    let x = B1a1a1b // "two"
})

这就是组合框架中Publishers命名空间的用途。

结构比类型别名更不透明。类型别名可能会导致像Cannot convert Utils.MyTypeAlias (aka 'TheLongUnderlyingTypeOf') to expected type ABC这样的错误消息,因此您最接近正确的不透明类型可能是使用结构,这实际上就是AnyPublisher

vxf3dgd4

vxf3dgd44#

一个非常简单的技巧帮助我们减少了大部分冗长,基本上是创建一个包含以下内容的文件:

// MARK: Dependencies Extension
typealias SafePublisher<T> = AnyPublisher<T, Never>
typealias IntSafePublisher = SafePublisher<Int>
typealias Int64SafePublisher = SafePublisher<Int64>
typealias Float64SafePublisher = SafePublisher<Float>

// Then for value types, something like

protocol SafePublishing {
    associatedtype SafePublisherType
}

extension SafePublishing {
    typealias SafePublisherType = SafePublisher<Self>
}

struct APublishedType: SafePublishing {
}

// And Magic

var myPublisher: APublishedType.SafePublisherType?

func takeInPublishers(myPubisherVar: APublishedType.SafePublisherType) {
    myPubisherVar
        .sink { myStructType in
            // DO SOMETHING
        }
        .cancel()
}

相关问题