1

我正在重写我的 Hacker News 阅读器,以便更多地使用 Combine。我有两个函数,它们都返回一个AnyPublisher,其中一个从服务器获取一堆 HN 故事的 id,另一个通过它的 id 获取故事。我不确定如何遍历 的结果fetchStoryIds,使用 id 运行fetchStory并最终得到一个Story使用 Combine 的对象数组。

import Combine
import Foundation

struct HackerNewsService {
    private var session = URLSession(configuration: .default)
    static private var baseURL = "https://hacker-news.firebaseio.com/v0"

    private func fetchStoryIds(feed: FeedType) -> AnyPublisher<[Int], Error> {
       let url = URL(string: "\(HackerNewsService.baseURL)/\(feed.rawValue.lowercased())stories.json")!

        return session.dataTaskPublisher(for: url)
            .retry(1)
            .map { $0.data }
            .decode(type: [Int].self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }

    private func fetchStory(id: Int) -> AnyPublisher<Story, Error> {
        let url = URL(string: "\(HackerNewsService.baseURL)/item/\(id).json")!

        return session.dataTaskPublisher(for: url)
            .map { $0.data }
            .decode(type: Story.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

在开始重写之前,我使用此代码循环遍历 id 并获取故事。

func fetchStories(feed: FeedType, completionHandler: @escaping ([Story]?, Error?) -> Void) {
        fetchStoryIds(feed: feed) { (ids, error) in
            guard error == nil else {
                completionHandler(nil, error)
                return
            }

            guard let ids = ids else {
                completionHandler(nil, error)
                return
            }

            let dispatchGroup = DispatchGroup()

            var stories = [Story]()

            for id in ids {
                dispatchGroup.enter()

                self.fetchStory(id: id) { (story, error) in
                    guard error == nil else {
                        dispatchGroup.leave()
                        return
                    }

                    guard let story = story else {
                        dispatchGroup.leave()
                        return
                    }

                    stories.append(story)

                    dispatchGroup.leave()
                }
            }

            dispatchGroup.notify(queue: .main) {
                completionHandler(stories, nil)
            }
        }
    }
}
4

1 回答 1

6

嗯.. 看起来没有一个Publishers.ZipMany接受出版商集合的,所以我合并了故事并收集了它们。理想情况下,这会以正确的顺序收集它们,但我尚未对此进行测试,并且整个 Combine 的文档仍然有些稀疏。

func fetchStories(feed: FeedType) -> AnyPublisher<[Story], Error> {
    fetchStoryIds(feed: feed)
        .flatMap { ids -> AnyPublisher<[Story], Error> in
            let stories = ids.map { self.fetchStory(id: $0) }
            return Publishers.MergeMany(stories)
                .collect()
                .eraseToAnyPublisher()
        }
        .eraseToAnyPublisher()
}

如果您对外部代码持开放态度,这是 ZipMany 的一个 gist 实现,它将保留顺序: https ://gist.github.com/mwahlig/725fe5e78e385093ba53e6f89028a41c

虽然我认为框架中会存在这样的东西。

于 2019-07-09T19:55:45.043 回答