在 flatMap 运算符闭包中放置打印语句后组合奇怪的编译错误



我有以下方法(命名为:stories(来自Ray Wenderlich的Combine的书,它从Hacker News Public API获取故事,如下所示:

模型对象:

public struct Story: Codable {
public let id: Int
public let title: String
public let by: String
public let time: TimeInterval
public let url: String
}
extension Story: Comparable {
public static func < (lhs: Story, rhs: Story) -> Bool {
return lhs.time > rhs.time
}
}
extension Story: CustomDebugStringConvertible {
public var debugDescription: String {
return "n(title)nby (by)n(url)n-----"
}
}

接口结构:

struct API {
enum Error: LocalizedError {
case addressUnreachable(URL)
case invalidResponse
var errorDescription: String? {
switch self {
case .invalidResponse: return "The server responded with garbage."
case .addressUnreachable(let url): return "(url.absoluteString) is unreachable."
}
}
}
enum EndPoint {
static let baseURL = URL(string: "https://hacker-news.firebaseio.com/v0/")!
case stories
case story(Int)
var url: URL {
switch self {
case .stories:
return EndPoint.baseURL.appendingPathComponent("newstories.json")
case .story(let id):
return EndPoint.baseURL.appendingPathComponent("item/(id).json")
}
}
}

var maxStories = 10

private let decoder = JSONDecoder()
private let apiQueue = DispatchQueue(label: "API", qos: .default, attributes: .concurrent)
func story(id: Int) -> AnyPublisher<Story, Error> {
URLSession.shared.dataTaskPublisher(for: EndPoint.story(id).url)
.receive(on: apiQueue)
.map(.data)
.decode(type: Story.self, decoder: decoder)
.catch{ _ in Empty<Story, Error>() }
.eraseToAnyPublisher()
}
func mergedStories(ids storyIDs: [Int]) -> AnyPublisher<Story, Error> {
let storyIDs = Array(storyIDs.prefix(maxStories))
precondition(!storyIDs.isEmpty)
let initialPublisher = story(id: storyIDs[0])
let remainder = Array(storyIDs.dropFirst())
return remainder.reduce(initialPublisher) { combined, id in //Swift's reduce method
combined
.merge(with: story(id: id))
.eraseToAnyPublisher()
}
}
func stories() -> AnyPublisher<[Story], Error> {
URLSession.shared
.dataTaskPublisher(for: EndPoint.stories.url)
.map(.data)
.decode(type: [Int].self, decoder: decoder)
.mapError { error -> API.Error in
switch error {
case is URLError:
return Error.addressUnreachable(EndPoint.stories.url)
default:
return Error.invalidResponse
}
}
.filter { !$0.isEmpty }
.flatMap { storyIDs in
print("StoryIDs are (storyIDs)") //the print statement that causes the error
return self.mergedStories(ids: storyIDs)
}
.scan([]) { stories, story -> [Story] in
stories + [story] //<--- Error fires here
}
.map { $0.sorted() }
.eraseToAnyPublisher()
}
}

消费者代码:

let api = API()
var subscriptions = Set<AnyCancellable>()
api.stories()
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
.store(in: &subscriptions)

该方法无需输入print("storyIDs are (storyIDs)")语句即可完美运行,一旦放置了此 print 语句,就会在以下行触发一个奇怪的编译器错误:stories + [story]它说:

'[Any]' is not convertible to 'Array<Story>'

我不知道在这种情况下这种误导性错误意味着什么?

多语句闭包不参与类型推断,因此将 flatMap 的闭包设为多语句闭包,您以某种方式导致它错误地推断scan的类型参数。您可以通过在闭包中写出它们来提供它所需的类型:

.flatMap { storyIDs -> AnyPublisher<Story, API.Error> in
print("StoryIDs are (storyIDs)")
return self.mergedStories(ids: storyIDs)
}

如果只想打印收到的值,也可以调用.print()

更新:

我对此进行了更多的尝试,我发现如果您将scan之前的所有内容放入一个let常量中,并在该常量上调用scan,则错误会移动到其他地方:

let pub = URLSession.shared
.dataTaskPublisher(for: EndPoint.stories.url)
.map(.data)
.decode(type: [Int].self, decoder: decoder) //<--- Error fires here now!
.mapError { error -> API.Error in
switch error {
case is URLError:
return Error.addressUnreachable(EndPoint.stories.url)
default:
return Error.invalidResponse
}
}
.filter { !$0.isEmpty }
.flatMap { storyIDs in
print("StoryIDs are (storyIDs)")
return self.mergedStories(ids: storyIDs)
}
return pub.scan([]) { stories, story -> [Story] in
stories + [story]
}
.map { $0.sorted() }
.eraseToAnyPublisher()

实例方法 'decode(type:decoder:(' 要求类型 'URLSession.DataTaskPublisher.Output' (aka '(data: Data, response: URLResponse('( 和 'JSONDecoder.Input' (又名 'Data'( 是等效

这一次,它导致decode的类型参数被错误地推断出来。scan的原始错误消失了,因为现在编译器知道pub具有确定的类型,尽管它在确定pub类型之前(错误地(在其他地方发现了错误。

按照这个模式,我做了另一个临时let常量:

let pub1 = URLSession.shared
.dataTaskPublisher(for: EndPoint.stories.url)
.map(.data)
let pub2 = pub1
.decode(type: [Int].self, decoder: decoder)
.mapError { error -> API.Error in
switch error {
case is URLError:
return Error.addressUnreachable(EndPoint.stories.url)
default:
return Error.invalidResponse
}
}
.filter { !$0.isEmpty }
.flatMap { storyIDs in
print("StoryIDs are (storyIDs)")
return self.mergedStories(ids: storyIDs)
}
return pub2.scan([]) { stories, story -> [Story] in
stories + [story]
}
.map { $0.sorted() }
.eraseToAnyPublisher()

最后,编译器在storyIDs in处显示一条有用的消息,该消息导致在答案开头使用解决方案:

无法推断复杂的闭包返回类型;添加显式类型以消除歧义

它甚至告诉我们应该插入什么类型!

最新更新