缓存 Firebase 视频网址时 URLSession 移动缓慢 [英] URLSession moving slow when caching a firebase video url

查看:61
本文介绍了缓存 Firebase 视频网址时 URLSession 移动缓慢的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最初问了这个问题,得到了答案,@LeoDabus 在评论中说:

I initially asked this question, got the answer, and in the comments @LeoDabus said:

NSData(contentsOf: url) 并不是要与非本地资源一起使用网址

NSData(contentsOf: url) it is not mean to use with non local resources urls

他建议我使用 URLSession ,我这样做了,但响应很慢.我想知道我做错了什么.如果这有什么不同,视频大小为 2mb.

He suggested I use URLSession which I did, but the response is very slow. I'm wondering am I doing something wrong. The video is 2mb if that makes any difference.

在会话的 completionHandler 中,我尝试更新主队列上返回的数据,但在执行此操作时出现滚动故障.使用 DispatchQueue.global().async 没有滚动故障,但似乎需要更长的返回时间

Inside the the session's completionHandler I tried updating the returned data on the main queue but there was a scrolling glitch while doing that. Using DispatchQueue.global().async there is no scrolling glitch but it seems like it takes longer return

// all of this occurs inside my data model

var cachedURL: URL?

let videoUrl = dict["videoUrl"] as? String ?? "" // eg. "https://firebasestorage.googleapis.com/v0/b/myApp.appspot.com/o/abcd%277920FHqFBkl7D6j%2F-MC65EFG_qT0KZbdtFhU%2F48127-8C29-4666-96C9-E95BE178B268.mp4?alt=media&token=bf85dcd1-8cee-428e-87bc-91800b7316de"
guard let url = URL(string: videoUrl) else { return }

useURLSessionToCacheVideo(url)


func useURLSessionToCacheVideo(_ url: URL) {
    
    let lastPathComponent = url.lastPathComponent
    let cachesDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
    let file = cachesDir.appendingPathComponent(lastPathComponent)
    
    if FileManager.default.fileExists(atPath: file.path) {

        self.cachedURL = file
        print("url already exists in cache")
        return
    }
    
    URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
        
        if let error = error { return }
        
        if let response = response as? HTTPURLResponse {
            guard response.statusCode == 200 else {
                return
            }
        }
        
        guard let data = data else {
            return
        }
        
        DispatchQueue.global().async { // main queue caused a hiccup while scrolling a cv
            do {
                try data.write(to: file, options: .atomic)
                DispatchQueue.main.async { [weak self] in
                    self?.cachedURL = file
                }
            } catch {
                print("couldn't cache video file")
            }
        }
        
    }).resume()
}

推荐答案

您应该从会话的后台线程写入文件:

You should write the file from the session's background thread:

func useURLSessionToCacheVideo(_ url: URL) {
    let lastPathComponent = url.lastPathComponent

    let fileURL = try! FileManager.default
        .url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        .appendingPathComponent(lastPathComponent)

    if FileManager.default.fileExists(atPath: fileURL.path) {
        self.cachedURL = fileURL
        print("url already exists in cache")
        return
    }

    URLSession.shared.dataTask(with: url) { data, response, error in
        guard
            error == nil,
            let httpResponse = response as? HTTPURLResponse,
            200 ..< 300 ~= httpResponse.statusCode,
            let data = data
        else {
            return
        }

        do {
            try data.write(to: fileURL, options: .atomic)
            DispatchQueue.main.async { [weak self] in
                self?.cachedURL = fileURL
            }
        } catch {
            print("couldn't cache video file")
        }
    }.resume()
}

这也接受任何 2xx HTTP 响应代码.

This also accepts any 2xx HTTP response code.

话虽如此,我建议使用下载任务,它可以减少峰值内存使用量并将数据写入文件:

That having been said, I’d suggest using a download task, which reduces the peak memory usage and writes the data to the file as you go along:

func useURLSessionToCacheVideo(_ url: URL) {
    let lastPathComponent = url.lastPathComponent

    let fileURL = try! FileManager.default
        .url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        .appendingPathComponent(lastPathComponent)

    if FileManager.default.fileExists(atPath: fileURL.path) {
        self.cachedURL = fileURL
        print("url already exists in cache")
        return
    }

    URLSession.shared.downloadTask(with: url) { location, response, error in
        guard
            error == nil,
            let httpResponse = response as? HTTPURLResponse,
            200 ..< 300 ~= httpResponse.statusCode,
            let location = location
        else {
            return
        }

        do {
            try FileManager.default.moveItem(at: location, to: fileURL)
            DispatchQueue.main.async { [weak self] in
                self?.cachedURL = fileURL
            }
        } catch {
            print("couldn't cache video file")
        }
    }.resume()
}


就我个人而言,与其让这个例程更新 cachedURL 本身,不如使用完成处理程序模式:


Personally, rather than having this routine update cachedURL itself, I'd use a completion handler pattern:

enum CacheError: Error {
    case failure(URL?, URLResponse?)
}

func useURLSessionToCacheVideo(_ url: URL, completion: @escaping (Result<URL, Error>) -> Void) {
    let lastPathComponent = url.lastPathComponent

    let fileURL = try! FileManager.default
        .url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        .appendingPathComponent(lastPathComponent)

    if FileManager.default.fileExists(atPath: fileURL.path) {
        completion(.success(fileURL))
        return
    }

    URLSession.shared.downloadTask(with: url) { location, response, error in
        if let error = error {
            DispatchQueue.main.async {
                completion(.failure(error))
            }
            return
        }

        guard
            let httpResponse = response as? HTTPURLResponse,
            200 ..< 300 ~= httpResponse.statusCode,
            let temporaryLocation = location
        else {
            DispatchQueue.main.async {
                completion(.failure(CacheError.failure(location, response)))
            }
            return
        }

        do {
            try FileManager.default.moveItem(at: temporaryLocation, to: fileURL)
            DispatchQueue.main.async {
                completion(.success(fileURL))
            }
        } catch {
            DispatchQueue.main.async {
                completion(.failure(error))
            }
        }
    }.resume()
}

然后这样称呼它:

useURLSessionToCacheVideo(url) { result in
    switch result {
    case .failure(let error):
        print(error)

    case .success(let cachedURL):
        self.cachedURL = cachedURL
    }
}

这样,调用者负责更新 cachedURL,它现在知道它何时完成(如果您想更新 UI 以反映下载的成功或失败),以及您的网络层不与调用者的模型结构纠缠在一起.

That way, the caller is responsible for updating cachedURL, it now knows when it's done (in case you want to update the UI to reflect the success or failure of the download), and your network layer isn't entangled with the model structure of the caller.

这篇关于缓存 Firebase 视频网址时 URLSession 移动缓慢的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆