URLSession медленно перемещается при кешировании URL-адреса видео Firebase - PullRequest
1 голос
/ 14 июля 2020

Сначала я задал этот вопрос , получил ответ, и в комментариях @LeoDabus сказал:

NSData (contentsOf: url), это не означает использование с non URL-адреса локальных ресурсов

Он предложил мне использовать URLSession, что я и сделал, но ответ очень медленный. Интересно, я что-то делаю не так? Размер видео составляет 2 МБ, если это имеет значение.

Внутри обработчика завершения сеанса я попытался обновить возвращенные данные в основной очереди, но при этом произошел сбой при прокрутке. При использовании 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()
}

1 Ответ

1 голос
/ 14 июля 2020

Вы должны записать файл из фонового потока сеанса:

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()
}

Он также принимает любой код ответа HTTP 2xx.

При этом я бы предложил использовать задачу загрузки, которая снижает пиковое использование памяти и записывает данные в файл, как вы go:

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, теперь он знает, когда это было сделано (в случае, если вы хотите обновить пользовательский интерфейс, чтобы отразить успех или неудачу загрузки), и ваш сетевой уровень не связан со структурой модели звонящего.

...