I have the following method (named: stories
) from Combine's book by Ray Wenderlich that fetches stories from Hacker News Public API as follows:
Model Object:
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-----"
}
}
API Struct:
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()
}
}
Consumer Code:
let api = API()
var subscriptions = Set<AnyCancellable>()
api.stories()
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
.store(in: &subscriptions)
The method works perfectly without putting in the print("storyIDs are \(storyIDs)")
statement, once this print statement is placed, a weird compiler error fires at the line: stories + [story]
which says:
'[Any]' is not convertible to 'Array<Story>'
I don't know what does this misleading error mean in such case ?