繁体   English   中英

如何在存储来自 function 回调的数据之前等待 API 请求完成?

[英]How to wait for an API request to finish before storing data from function callback?

在我的一个个人项目中,我创建了一个 API 调用程序来从 Spotify API 检索用户保存的曲目。 我正在使用的 Spotify 端点有一个限制(每个请求最多 50 个轨道)以及一个偏移量(请求中第一个轨道的起始索引),这就是为什么我决定使用 FOR 循环来获取一系列轨道页面(每 50 个磁道)和 append 将它们放到一个全局数组中。 数据是从主线程加载的,在请求数据时,我会显示一个带有微调器视图的子视图 controller。 数据请求完成后,我删除微调器视图,并转换到另一个视图 controller(将数据作为属性传递)。

我尝试了很多东西,但是在 API 请求之后,轨道数组总是空的。 我感觉这与我的请求的同步性有关,或者我可能没有正确处理它。 理想情况下,我想等到来自我的 API 的请求完成,然后 append 将结果发送到数组。 你对我如何解决这个问题有什么建议吗? 任何帮助深表感谢!

func createSpinnerView() {

    let loadViewController = LoadViewController.instantiateFromAppStoryboard(appStoryboard: .OrganizeScreen)
    add(asChildViewController: loadViewController)

    DispatchQueue.main.async { [weak self] in
        if (self?.dropdownButton.dropdownLabel.text == "My saved music") {
            self?.fetchSavedMusic() { tracksArray in
                self?.tracksArray = tracksArray
            }
        }
        ...
        self?.remove(asChildViewController: loadViewController)
        self?.navigateToFilterScreen(tracksArray: self!.tracksArray)
    }
}

private func fetchSavedMusic(completion: @escaping ([Tracks]) -> ()) {
    let limit = 50
    var offset = 0
    var total = 200
    for _ in stride(from: 0, to: total, by: limit) {
        getSavedTracks(limit: limit, offset: offset) { tracks in
            //total = tracks.total
            self.tracksArray.append(tracks)
        }
        print(offset, limit)
        offset = offset + 50
    }
    completion(tracksArray)
}

private func getSavedTracks(limit: Int, offset: Int, completion: @escaping (Tracks) -> ()) {
    APICaller.shared.getUsersSavedTracks(limit: limit, offset: offset) { (result) in
        switch result {
        case .success(let model):
            completion(model)
            print("success")
        case .failure(let error):
            print("Error retrieving saved tracks: \(error.localizedDescription)")
            print(error)
        }
    }
}

private func navigateToFilterScreen(tracksArray: [Tracks]) {
    let vc = FilterViewController.instantiateFromAppStoryboard(appStoryboard: .OrganizeScreen)
    vc.paginatedTracks = tracksArray
    show(vc, sender: self)
}

首先,您需要在加载所有数据时调用完成。 在您的情况下,您在任何getSavedTracks返回之前调用completion(tracksArray)

对于这一部分,我建议您通过遍历所有页面来递归地积累曲目。 有多种更好的工具可以做到这一点,但我将给出一个原始示例:

class TracksModel {
    
    static func fetchAllPages(completion: @escaping ((_ tracks: [Track]?) -> Void)) {
        var offset: Int = 0
        let limit: Int = 50
        var allTracks: [Track] = []
        
        func appendPage() {
            fetchSavedMusicPage(offset: offset, limit: limit) { tracks in
                guard let tracks = tracks else {
                    completion(allTracks) // Most likely an error should be handled here
                    return
                }
                if tracks.count < limit {
                    // This was the last page because we got less than limit (50) tracks
                    completion(allTracks+tracks)
                } else {
                    // Expecting another page to be loaded
                    offset += limit // Next page
                    allTracks += tracks
                    appendPage() // Recursively call for next page
                }
            }
        }

        appendPage() // Load first page
        
    }
    
    private static func fetchSavedMusicPage(offset: Int, limit: Int, completion: @escaping ((_ tracks: [Track]?) -> Void)) {
        APICaller.shared.getUsersSavedTracks(limit: limit, offset: offset) { result in
            switch result {
            case .success(let model):
                completion(model)
            case .failure(let error):
                print(error)
                completion(nil) // Error also needs to call a completion
            }
        }
    }
    
}

我希望评论能澄清一些事情。 但关键是我嵌套了一个appendPage function ,它被递归调用,直到服务器停止发送数据。 最后要么发生错误,要么最后一页返回的曲目少于提供的限制。 当然,转发一个错误会更好,但为了简单起见,我没有包括它。

无论如何,您现在可以在任何地方TracksModel.fetchAllPages { }并接收所有曲目。

当您加载并显示您的数据 ( createSpinnerView ) 时,您还需要等待接收到数据才能继续。 例如:

func createSpinnerView() {

    let loadViewController = LoadViewController.instantiateFromAppStoryboard(appStoryboard: .OrganizeScreen)
    add(asChildViewController: loadViewController)

    TracksModel.fetchAllPages { tracks in
        DispatchQueue.main.async {
            self.tracksArray = tracks
            self.remove(asChildViewController: loadViewController)
            self.navigateToFilterScreen(tracksArray: tracks)
        }
    }
    
}

一些组件可能已被删除,但我希望你明白这一点。 该方法应该已经在主线程上调用。 但是您不确定 API 调用返回到哪个线程。 所以你需要在完成闭包中使用DispatchQueue.main.async ,而不是在它之外。 并且还调用在这个闭包中导航,因为这是事情真正完成的时候。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM