Загрузка изображений в UITableViewCell: Data vs URLSession?

У меня есть UITableView, и мне нужно, чтобы каждая из его ячеек загружала изображение с предоставленного URL-адреса и отображала его. Это выглядит довольно распространенным сценарием, и я действительно нашел несколько сообщений и вопросов, связанных с этим, но мне все еще не ясно, какой подход должен быть лучшим.

Первый, у меня есть один, использующий Data(contentsOf:). Это код, который у меня есть в моем UITableViewCell:

class MyCell: UITableViewCell {

@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
@IBOutlet weak var imageView: UIImageView!

var model: Model? {
    willSet {
        activityIndicator.startAnimating()
        configureImage(showImage: false, showActivity: true)
    }
    didSet {
        guard let imageSmallUrlStr = model?.imageSmallUrl, let imageUrl = URL(string: imageSmallUrlStr) else {
            activityIndicator.stopAnimating()
            configureImage(showImage: false, showActivity: false)
            return
        }

        DispatchQueue.global(qos: .userInitiated).async {
            var image: UIImage?
            let imageData = try? Data(contentsOf: imageUrl)
            if let imageData = imageData {
                image = UIImage(data: imageData)
            }

            DispatchQueue.main.async {
                self.imageView.image = image
                self.configureImage(showImage: true, showActivity: false)
            }
        }
}

override func awakeFromNib() {
    super.awakeFromNib()
    configureImage(showImage: false, showActivity: false)
}

override func prepareForReuse() {
    super.prepareForReuse()
    model = nil
}

// Other methods
}

Этот подход выглядит довольно простым и быстрым, по крайней мере, когда я тестирую его на симуляторе. Тем не менее, у меня есть некоторые вопросы по этому поводу:

  1. Действительно ли уместно загружать изображение с URL-адреса HTTP с помощью Data(contentsOf:)? Это работает, но, возможно, это больше подходит для получения файлов, которые у вас есть, или других вещей, и это не лучшее решение для работы в сети.
  2. Я предполагаю, что выполнение этого действия в глобальной асинхронной очереди — правильное решение, верно?
  3. Возможно ли при таком подходе отменить загрузку этого изображения, если URL-адрес изображения обновлен, и мне нужно загрузить другое? Кроме того, следует ли отменить загрузку, когда ячейка удаляется из очереди из таблицы, или это делается автоматически?
  4. Кроме того, я не уверен в этом: поскольку несколько ячеек будут отображаться в таблице одновременно, эти ячейки должны загружаться и показывать свое изображение одновременно. Также может случиться так, что после завершения загрузки изображения соответствующая ячейка будет удалена из очереди из таблицы, поскольку пользователь прокрутил ее. Гарантирует ли этот подход, что несколько ячеек одновременно загружают изображение и что каждое загруженное изображение правильно устанавливается в соответствующую ячейку?

Теперь у меня есть другой подход, использующий URLSession:

class MyCell: UITableViewCell {

@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
@IBOutlet weak var imageView: UIImageView!

var imageTask: URLSessionDownloadTask?

var model: Model? {
    willSet {
        activityIndicator.startAnimating()
        configureImage(showImage: false, showActivity: true)
    }
    didSet {
        imageTask?.cancel()

        guard let imageSmallUrlStr = model?.imageSmallUrl, let imageUrl = URL(string: imageSmallUrlStr) else {
            activityIndicator.stopAnimating()
            configureImage(showImage: false, showActivity: false)
            return
        }

        imageTask = NetworkManager.sharedInstance.getImageInBackground(imageUrl: imageUrl, completion: { [weak self] (image, error) in
            DispatchQueue.main.async {
                guard error == nil else {
                    self?.activityIndicator.stopAnimating()
                    self?.configureImage(showImage: false, showActivity: false)
                    return
                }

                self?.imageView.image = image
                self?.activityIndicator.stopAnimating()
                self?.configureImage(showImage: true, showActivity: false)
            }
        })
    }
}

// Other methods

}

При таком подходе я могу вручную отменить задачу, но когда я запускаю приложение на симуляторе, оно выглядит медленнее. На самом деле, я получаю несколько ошибок «отменено» при попытке загрузить многие изображения. Но таким образом я мог бы обрабатывать фоновые загрузки.

Вопросы об этом подходе:

  1. Как я могу убедиться, что загруженное изображение отображается в соответствующей ячейке? Та же проблема, что описана ранее: в этот момент ячейка могла быть удалена из очереди из таблицы.

Этот вопрос связан с обоими подходами:

  1. Я не хочу сохранять изображения в файлы, но я не хочу загружать снова, если ячейка снова отображается, а я уже это сделал. Каким должен быть лучший способ их кэширования?

person AppsDev    schedule 01.05.2017    source источник
comment
могу ли я предложить лучшую объективную библиотеку C?   -  person KKRocks    schedule 01.05.2017
comment
@KKRocks спасибо, но я полностью разрабатываю в Swift 3 и предпочитаю не использовать здесь никаких сторонних разработчиков.   -  person AppsDev    schedule 01.05.2017
comment
вы можете использовать для этого библиотеку KingFisher. Это то же самое, что и SDWwebImage в Objective-C.   -  person Jitendra Modi    schedule 01.05.2017
comment
@JeckyModi спасибо за предложение, но по нескольким причинам я предпочитаю обрабатывать этот сценарий без использования какой-либо внешней библиотеки.   -  person AppsDev    schedule 01.05.2017
comment
Так что подождите, я могу дать одно предложение сделать это без библиотеки.   -  person Jitendra Modi    schedule 01.05.2017
comment
По вашему вопросу вы сохранили все загружаемые изображения в кеше, и когда этот URL-адрес изображения снова используется для загрузки, вы должны взять это изображение из кеша. Таким образом, простым способом вы управляете одним массивом кэшированных изображений для него. Я предлагаю вам использовать третью сторону, потому что она автоматически управляет кэшированием, поэтому изображение загружается только один раз.   -  person Jitendra Modi    schedule 01.05.2017


Ответы (1)


Вы никогда не должны использовать метод contentOfURL NSData для получения нелокальных данных по двум причинам:

  • В документации прямо сказано, что это не поддерживается.
  • Разрешение и извлечение имени происходит синхронно, блокируя текущий поток.

В вашем случае, делая это в параллельной очереди, последний не так плох, как мог бы быть в противном случае, но если вы получаете достаточно ресурсов, я подозреваю, что это приведет к значительным накладным расходам.

Одна из причин, по которой ваш подход NSURLSession кажется медленнее, заключается в том, что я думаю, что вы, вероятно, получаете все ресурсы одновременно (очень сильно ударяя по серверу) с вашим другим подходом. Изменение ограничения параллелизма в NSURLSession может немного повысить его производительность, но делайте это только в том случае, если вы знаете, что сервер может с этим справиться.

Чтобы ответить на другие вопросы:

  • Вы можете привязать изображение к конкретной ячейке любым количеством способов, но наиболее распространенный подход заключается в том, чтобы иметь синглтон, который управляет всеми запросами, и в этом синглтоне:

    • keep a dictionary that maps a data task to a unique identifier. Set that as the cell's accessibility identifier, and use the built-in lookup methods to find the cell with that accessibility identifier. If it no longer exists, throw away the response.
    • держите словарь, который сопоставляет задачу данных с блоком. Когда запрос завершится, запустите блок. Пусть блок сохраняет сильную ссылку на ячейку.

    В любом случае сохраните URL-адрес в свойстве ячейки и убедитесь, что он соответствует исходному URL-адресу запроса перед установкой изображения, чтобы при повторном использовании ячейки существующее изображение не было перезаписано неправильным изображением из устаревший запрос.

  • NSURLCache — ваш друг. Создайте на диске кэш разумного размера, и он должен позаботиться о том, чтобы избежать ненужных сетевых запросов (при условии, что ваш сервер правильно поддерживает кэширование).
person dgatwood    schedule 01.05.2017