ios - 缓存 Firebase 视频网址时,URLSession 移动缓慢
问题描述
我最初问了这个问题,得到了答案,并在@LeoDabus 的评论中说:
NSData(contentsOf: url) 这并不意味着与非本地资源 url 一起使用
他建议我使用URLSession
我做过的,但反应很慢。我想知道我做错了什么。如果这有什么不同的话,视频是 2mb。
在会话的完成处理程序中,我尝试更新主队列上的返回数据,但在执行此操作时出现滚动故障。使用DispatchQueue.global().async
没有滚动故障,但似乎需要更长的返回时间
// 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()
}
解决方案
您应该从会话的后台线程写入文件:
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 响应代码。
话虽如此,我建议使用下载任务,它可以减少峰值内存使用量并将数据写入文件中:
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
自行更新,而是使用完成处理程序模式:
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 以反映下载的成功或失败),并且您的网络层不会与模型结构纠缠在一起呼叫者。
推荐阅读
- sql - 如何将时间列添加到 Vertica 中的时间戳列?(或另一个分贝)
- kotlin - 在 Kotlin 中添加条件的 for 循环的优雅方式是什么?
- java - 将 OneToMany 双向关系更改为 ManyToMany 双向
- sql - Groupby 多个日期时间段
- cuda - 如何将线程块分配给 NVIDIA GPU 上的多处理器?
- ms-access - 一条记录的 Microsoft Access 更新查询
- fastapi - Pydantic 类型语法解释
- php - 如何仅获取枚举名称而不是 Laravel 中的值
- java - 如何在txt文件中逐行捕获所有标题和字段信息并存储在结构或数组中
- c++ - 为什么 C++ 映射中没有“存在”函数?