Знакомьтесь, фреймворк Nuke!

21 марта 2016

Эффективная загрузка и кэширование изображений уже давно являются проблемой для iOS разработчиков.

В Objective-C есть множество популярных библиотек, решающих эту задачу, такие как SDWebImage, AFNetworking, DFImageManager и многие другие. На появление библиотек и фреймворков сопоставимых с Swift потребовалось время, и один из новейших - фреймворк Nuke!

Nuke — это фреймворк на Swift с открытым исходным кодом для загрузки, обработки, кэширования, отображения и предварительного "разогрева" изображений. Написана она Александром Гребенюком (из Москвы!), опытным разработчиком, который также написал DFImageManager.

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

Поехали!

Установить Nuke легко - он поддерживает как CocoaPods так и Carthage.

CocoaPods

Если вы используете CocoaPods, то просто добавьте следующие строки в ваш Podfile:


pod "Nuke"
pod "Nuke-Alamofire-Plugin" # optional
pod "Nuke-AnimatedImage-Plugin" # optional

Carthage

Если вы используете Carthage, то просто добавьте следующие строки в ваш Cartfile:


github "kean/Nuke"
github "kean/Nuke-Alamofire-Plugin" # optional

В настоящее время Carthage не поддерживает Nuke-AnimatedImage-Plugin.

Что может Nuke?

Nuke поможет вам в следующем:

  1. Загружать изображения.
  2. Изменять размер изображений.
  3. Кэшировать изображения.
  4. Обрабатывать изображения.
  5. Десериализовывать и декодировать изображения.
  6. "Разогревать" изображения.

Давайте разберем пошагово:

1) Загрузка изображений

Основная цель Nuke - сделать загрузку и использование изображений в вашем приложении быстрыми, легкими и эффективными.

Например, следующий фрагмент кода извлекает для вас изображение:


Nuke.taskWithURL(url) { response in
    switch response {
    case let .Success(image, responseInfo):
        //do something with your image
    case let .Failure(error):
        //handle the error case
    }
}.resume()

Вы просто передаете экземпляр NSURL и затем вызываете resume. Nuke будет загружать изображение в фоновом режиме и вызовет ответное замыкание, когда загрузка будет закончена.

response представляет собой перечисление (или энум), который описывает полученный результат выполнения задания либо как успешный, либо как неудачный. responseInfo предоставляет вызывающему полезную информацию, такую как fastResponse, которое сообщает вам, извлечено ли изображение из сети или из кэша.

Заметка

 

Внимательные читатели заметят сходство между этим и NSURLSessionTask. В случае с Nuke вы используете ImageTask вместо NSURLSessionTask

Хотя ImageTask не выходит из NSURLSessionTask, но ImageTask предлагает аналогичные API методы, такие как resume, suspend и cancel для обработки сетевых вызовов.

2) Изменение размера изображений

До этого момента у Nuke не было отличия от NSURLSession или других сетевых библиотек. Но в Nuke вы также можете передавать некоторые специфические параметры изображений вашему заданию по загрузке, путем передачи ImageRequest вместо NSURL, как в приведенном ниже примере:


var imageRequest = ImageRequest(URL: imageURL)
request.targetSize = CGSize(width: 250, height: 250)
request.contentMode = .AspectFit
 
Nuke.taskWithRequest(request) { response in
   // Handle the response
    switch response {
    case let .Success(image, responseInfo):
        let image = image
    case let .Failure(error):
        let error = error
    }
}.resume()

Это позволяет установить размеры элементам, например изменять изображение под заданный размер (target size) с помощью contentMode (AspectFillили AspectFit). Nuke будет загружать и изменять размер изображения в фоновом режиме.

Заметка

 

Изменение размера изображения может быть полезно, когда вам нужно масштабировать изображение для его соответствовия определенному полю вашего view, например аватарке размером 100×100. Нецелесообразно передавать изображение через UIView, ведь оно значительно больше, чем вам нужно.

Но разве вы не могли бы просто передать UIImageView большое по размеру изображение и позволить размеру поменяться? Да, так можно сделать, и UIImageView изменит размер изображения в зависимости от свойств contentMode. Тем не менее, лучше изменить размер изображения самостоятельно, прежде чем передавать его в UIImageView, ведь это позволит держать использование памяти на минимальном уровне. Вот где пригождается Nuke.

3) Кэширование изображений

Если вы используете Nuke, то изображения, которые вы загружаете, будут кэшироваться автоматически. Чтобы понять, как это работает, давайте прочтем цитату о механизме кэширования из руководства Nuke (caching mechanism guide):

"Многие сторонние библиотеки пытаются изобрести кэширование. Nuke не делает этого, он опирается на кэш HTTP, как определено в спецификации HTTP, а отличная реализация кэширования предоставляется Foundation. Если вы пытаетесь реализовать свою собственную методологию кэширования HTTP, вы, вероятно, делаете это неправильно".

Вместо того, чтобы изобретать кэширование, Nuke опирается на два встроенных механизма кэширования, предлагаемых Apple:

  • механизм кэширования NSURLSession с помощью NSURLCache
  • NSCache

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

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


let cachedResponse = NSURLCache.sharedURLCache().cachedResponseForRequest(imageRequest)

4) Обработка изображений

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

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


let blurringImageFilter : ImageProcessing = MyFilters.blurringFilter()
 
var imageRequest = ImageRequest(URL: imageURL)
imageRequest.processor = blurringImageFilter
 
Nuke.taskWithRequest(request) { response in
    switch response {
    // Handle the response
    case let .Success(image, responseInfo):
        let image = image
    case let .Failure(error):
        let error = error
    }
}.resume()

Ключевым элементом в коде выше является Nuke протокол обработки ImageProcessing. Он определяет несколько методов, которые вам нужно соблюдать. Наиболее важным является этот:


func processImage(image: UIImage) -> UIImage?

Вы передаете этому методу экземпляр UIImage и он возвращает опциональный UIImage. Если операция по наложению фильтра проходит успешно, вы получите новое изображение с уже наложенным фильтром. Если, по какой-либо причине, Nuke не может применить фильтр, то он возвращает nil. Это довольно просто!

5) Десериализация и декодировка изображения

После того, как вы запросили изображение в сети, и до того, как iOS отобразит его на экране, должно произойти две вещи: десериализация изображения и его декодировка.

Десериализация изображения

Сначала происходит десериализация , что приводит к переводу блока двоичных данных в файл с изображением.

UIImage может провести десериализацию, но это "дорогая" по затратам операция, и как правило, проходит она в основном потоке и может повлиять на ваш пользовательский интерфейс. Nuke помогает поддерживать "плавный" пользовательский интерфейс, проводя десериализацию в фоновом режиме. Это происходит в классе ImageDecoder следующим образом:


return UIImage(data: data, scale: UIScreen.mainScreen().scale)

Заметка

 

В официальной документации Apple ничего не говорится о том, что init?(data data: NSData, scale scale: CGFloat) потокобезопасен. Тем не менее, с эмпирической точки зрения, и с точки зрения большинства разработчиков init?(data data: NSData, scale scale: CGFloat) работает за пределами основного потока.

Декодировка изображения

Следующим шагом после десериализации является декодирование изображения. Изображение всегда находится в определенном закодированном формате изображения, что, как правило, выражается в расширении файла, таком как .JPG или .PNG. Процесс декодировки изображения - это перевод кодированного файла с использованием стандартов изображения в 2-D цветовую сетку для отображения изображения на экране.

Хорошая новость заключается в том, что UIImage и UIImageView проводят декодировку изображения для вас автоматически. Плохая новость заключается в том, что, как и десериализация, декодировка обычно происходит в основном потоке, что опять-таки может снизить производительность пользовательского интерфейса.

Nuke же проводит декодировку изображения не в главном потоке. Класс Nuke ImageDecompressor проводит раннюю декомпрессию в контекст Core Graphics в фоновом режиме. Очень круто! :]

6) Разогрев изображений

Наиболее интересной особенностью Nuke, вероятно, является возможность предварительного "разогрева" изображений.

Если вы являетесь экспертом в подогреве пищи, но не знаете, как "подогреть" изображение, то мы вам объясним, что это значит: вы делаете запрос изображения задолго до того, как оно вам понадобится, загружаете его и храните его в сети кэша приложения.

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

Представьте, что у вас экран, отображающий статью с изображением в самом конце. Вы понимаете, что пользователь, вероятно, дойдет до конца и посмотрит изображение. С Nuke вы можете предварительно "разогреть" изображение еще в тот момент, когда пользователь только открывает статью. Как только он прокрутит страницу вниз, то изображение загрузится так быстро, что создастся впечатление, что изображение всегда там и было.

Вот как вы "разогреваете" изображение с Nuke:


let articleImagesRequests = article.imagesRequests() // get the article's images requests
Nuke.startPreheatingImages(articleImagesRequests)    // start the oven

Остановить процесс так же легко:


Nuke.stopPreheatingImages(articleImagesRequests)

Самое замечательное в этом то, что явные запросы изображений имеют более высокий приоритет, чем "разогревание" запросов. Гребенюк написал подробное объяснение этой функции в Nuke repo’s wiki.

"Разогрев" и UICollectionView

Nuke также расширяет функциональную возможность "разогрева" для безупречной работы с UICollectionView, с помощью ImagePreheatingControllerForCollectionView и протокола ImagePreheatingControllerDelegate, определяющие единственный метод:


func preheatingController(controller: ImagePreheatingController,
    didUpdateWithAddedIndexPaths addedIndexPaths: [NSIndexPath],
    removedIndexPaths: [NSIndexPath])

Этот метод делегата передает два массива путей:

  • addedIndexPaths: путь для "разогрева".
  • removedIndexPaths: путь для удаления из "разогрева".

Но как ImagePreheatingControllerForCollectionView знает, какие пути запускают и останавливают разогрев? В основном за этим наблюдает свойство contentOffSet элемента UIScrollView в UICollectionView и выясняет, какие ячейки попадают и выходят из view port, принимая во внимание куда направлена прокрутка и как расположено устройство.

Если вы хотите использовать эту функцию с UITableView, вы можете использовать ImagePreheatingControllerForCollectionView суперкласс ImagePreheatingController . Этот класс наблюдает непосредственно за UIScrollView, который является подклассом UITableView. Тем не менее, маленькая птичка принесла новость, что скоро будет полная поддержка UITableView! :]

Чтобы посмотреть небольшую демонстрацию разогрева в действии, найдите пример в хранилище Nuke здесь и класс PreheatingDemoViewController.swift.

Когда вам нужно использовать Nuke?

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

  • Поддерживается ли она?
  • Может ли она делать то, что должна?

На первый вопрос вы можете ответить, проверив частоту обращений и как своевременно обрабатываются новые вопросы. Я думаю, что Гребенюк отлично справляется на обоих фронтах! Вы можете проверить историю обращений и список вопросов в репозитории и убедиться сами.

Ответ на второй вопрос зависит от вашего проекта. Самый простой способ - это начать использовать библиотеку и понять, делает ли она именно то, что должна. Вы можете сделать это с помощью Инструментов и посмотреть, что будет происходить при различных сценариях. В этом случае Allocations, Time Profiler и Leaks станут вашими лучшими показателями. Это даст вам понимание, справляется ли Nuke с вашей конкретной задачей.

Мой опыт работы с Nuke показал, что он работал, как и было обещано. В качестве стандартного сборщика изображений и кэшировщика, Nuke безупречен.

Альтернативы Nuke

Есть и другие интересные библиотеки, сопоставимые с Nuke, одна из них Kingfisher. Хотя Kingfisher вполне себе вариант, но предлагаемый Nuke разогрев изображений - это то, в чем я действительно нуждался, поэтому я выбрал именно его.

С другой стороны, если вы уже используете что-то вроде Alamofire, вы могли бы, вероятно, просто использовать его библиотеку AlamofireImage для обработки изображений. В данной случае использование Nuke только из-за разогрева, неоправданно.

Заметка

 

Nuke идет с дополнительным плагином, который обеспечивает интеграцию с Alamofire. Это делает возможность Alamofire.Manager соответствовать протоколу Nuke ImageDataLoading, и это означает, что вы можете использовать любой загрузчик изображений на ваш выбор, Nuke или Alamofire.

Чем это полезно? Если вы в вашем проекте уже используете Alamofire и решили использовать Nuke, плагин передаст сетевые задачи загрузки изображений на Alamofire, сохраняя при этом возможность Nuke кэшировать, обрабатывать и декодировать. Мы берем самое лучшее от каждого!

Если вам нужна еще более мощная обработка изображений, чем может предоставить Nuke, то вы можете скомбинировать Alamofire и популярную FastImageCache для решения вашей задачи.

Что дальше?

Дальше, вы можете продолжить изучать наши туториалы по мере их появления, а также, параллельно читать перевод официальной книги по языку программирования Swift. И, для более подробного изучения языка, вы можете пройти наши курсы!

Урок подготовил: Акулов Иван

Источник урока: Источник

Содержание