Tutoriel Nuke pour iOS: mise en route

Online Coding Courses for Kids

Mettre à jour la note: Ehab Yosry Amer a mis à jour ce tutoriel pour iOS 13, Xcode 11 et, Swift 5. Yono Mittlefehldt a écrit l’original.

Chaque année, la dernière caméra iPhone s’améliore de plus en plus. Mais savez-vous qui a les meilleurs appareils photo? Non, pas l’agence de mannequins qui fait vos portraits. Ses La NASA!

Et quel est le meilleur sujet à photographier? Encore une fois, à moins que vous ne travailliez pour la NASA, la réponse n’est malheureusement pas vous. Ses ESPACE! Mais vous êtes de près. :]

Ne serait-il pas agréable d’avoir une application qui affiche les belles photos de la NASA pour que vous puissiez les regarder sur votre téléphone quand vous le souhaitez? Le seul problème est que certaines de ces photos de l’espace sont énormes – comme 60 Mo énormes. Et pour couronner le tout, ils n’ont pas de vignettes disponibles.

Vous redoutez déjà d’avoir à écrire les tonnes de code standard associé à OperationQueue. Si seulement il y avait un meilleur moyen.

Bonnes nouvelles! Il existe un meilleur moyen sous la forme d’une bibliothèque tierce appelée Nuke.

Dans ce didacticiel Nuke, vous apprendrez à:

  • Intégrez Nuke à votre projet à l’aide du gestionnaire de packages Swift.
  • Chargez des images distantes tout en conservant la réactivité de l’application.
  • Redimensionnez les photos à la volée pour garder le contrôle de l’utilisation de la mémoire.
  • Intégrer l’extension Combine de Nuke appelée ImagePublisher.
  • Utilisez Combine dans votre projet pour charger différentes tailles d’image consécutivement.

Prêt à explorer l’espace dans le confort de votre iPhone? Il est temps de commencer!

Commencer

Clique le Télécharger les matériaux en haut ou en bas de ce didacticiel pour télécharger les ressources dont vous aurez besoin. Ouvrez le projet de démarrage, puis générez et exécutez. Jetez un œil à l’application à votre guise.

le Loin Photos prend des photos sur le site Web de la NASA et les affiche dans un UICollectionViewgalerie basée. Appuyez sur l’une des belles photos pour l’afficher en plein écran.

Malheureusement, il y a un problème majeur avec cette application. Il est lent et ne répond pas. Cela est dû au fait que toutes les demandes d’images du réseau sont effectuées sur le thread principal, elles interfèrent donc avec votre capacité à faire défiler. De plus, il n’y a pas de mise en cache, les images sont donc récupérées à chaque fois qu’elles sont chargées.

Monstre jaune ennuyé aux yeux fermés

Vous allez utiliser Nuke pour réparer cette application et la faire fonctionner correctement.

L'ancienne planète Pluton

Configurer Nuke

Vous avez quelques options pour intégrer Nuke à votre projet, mais pour ce didacticiel, vous utiliserez Swift Package Manager ou SwiftPM. C’est un moyen simple d’installer des packages dans votre projet qu’Apple a introduits dans Xcode 11.

Pour en savoir plus sur SwiftPM, consultez le didacticiel Swift Package Manager pour iOS.

Installation avec Swift Package Manager

Pour ajouter un package, accédez à Fichier ▸ Paquets Swift ▸ Ajouter une dépendance de paquet.

Ajouter un package avec SwiftPM

Cela ouvre une boîte de dialogue pour choisir le package que vous souhaitez installer. Entrez l’URL https://github.com/kean/Nuke.git dans le champ de texte et cliquez sur Prochain.

URL Nuke dans SwiftPM Dialogue

Sélectionner Version pour la règle, et avec Jusqu’au prochain majeur dans le menu déroulant et entrez 9.1.0 dans la boite. Cela spécifie que la version sera un minimum de 9.1.0 et jusqu’à un maximum, mais non compris, 10.0.0. Puis sélectionnez Prochain et attendez que Xcode vérifie le package.

Paramètres de version Nuke dans SwiftPM

Assurez-vous que la cible Loin Photos est sélectionné et cliquez sur terminer.

Assurez-vous que la cible est sélectionnée

Votre navigateur de projet doit ressembler à ceci lorsque le package est installé.

Navigation dans le projet après l'installation du package

Cela fait, il est temps de plonger dans Nuke.

Remarque: Vous pouvez voir un numéro de patch différent – le troisième numéro de la version – puisque Nuke est toujours en développement actif.

Utilisation de Nuke: mode de base

La première chose que vous allez corriger est la terrible réactivité de l’application lors du défilement dans la galerie.

Pour commencer, ouvrez PhotoGalleryViewController.swift. Ajoutez ce qui suit en haut du fichier:

import Nuke

Ensuite, trouvez collectionView(_:cellForItemAt:). Dans cette méthode, vous devriez voir le code suivant:

if let imageData = try? Data(contentsOf: photoURLs[indexPath.row]),
  let image = UIImage(data: imageData) {
    cell.imageView.image = image
} else {
  cell.imageView.image = nil
}

Ce code récupère de manière inefficace la photo à partir de l’URL fournie et l’assigne à la vue d’image dans la cellule de vue de collection, bloquant le fil principal dans l’ensemble du processus.

Supprimez ces sept lignes et remplacez-les par les suivantes:

// 1
let url = photoURLs[indexPath.row]

// 2
Nuke.loadImage(with: url, into: cell.imageView)

Dans ce code, vous:

  1. Saisissez l’URL de la photo correcte en fonction du chemin d’index de la cellule.
  2. Utilisez Nuke pour charger l’image à partir de l’URL directement dans la vue d’image de la cellule. Nuke fait tout le gros du travail sous le capot. Il charge l’image sur un fil d’arrière-plan et l’assigne à la vue d’image.

C’est tout! Beaucoup plus facile que prévu, non? :]

Construisez et exécutez. Vous devriez remarquer une amélioration majeure. Vous pouvez maintenant faire défiler en douceur la vue de la galerie!

Défilement fluide dans la galerie après l'utilisation de Nuke

Définition des options de chargement

C’est un bon début, mais il manque encore quelque chose de très important. L’application ne donne pas à l’utilisateur de rétroaction visuelle sur l’arrivée d’images. Au lieu de cela, si les utilisateurs font défiler assez rapidement, ils ne seront accueillis que par un écran noir presque solide.

Si seulement Nuke avait un moyen d’afficher une image à la place de l’image de chargement, un espace réservé si vous voulez.

Aujourd’hui est votre jour de chance. Cela fait!

Nuke a une structure appelée ImageLoadingOptions, qui vous permet de modifier la façon dont Nuke présente les images, notamment en définissant une image d’espace réservé.

De retour collectionView(_:cellForItemAt:), remplacez le code que vous venez d’écrire par ce qui suit:

let url = photoURLs[indexPath.row]

// 1
let options = ImageLoadingOptions(
  placeholder: UIImage(named: "dark-moon"),
  transition: .fadeIn(duration: 0.5)
)
   
// 2 
Nuke.loadImage(with: url, options: options, into: cell.imageView)

Ici vous:

  1. Créé un ImageLoadingOptions struct. Vous définissez l’image d’espace réservé sur l’image nommée dark-moon et configurer la transition de l’image d’espace réservé à l’image extraite du réseau. Dans ce cas, vous définissez la transition comme un fondu en 0,5 seconde.
  2. Utilisez Nuke pour charger l’image à partir de l’URL directement dans la vue d’image de la cellule, comme auparavant, mais cette fois en utilisant les options que vous venez de configurer.

Maintenant, créez et exécutez l’application. Vous devriez voir les images d’espace réservé s’estomper pour révéler les images réelles au fur et à mesure qu’elles sont chargées.

Une image d'espace réservé est affichée jusqu'à ce que l'original soit chargé et que le fondu enchaîné

Travail fantastique! Vous avez déjà considérablement amélioré l’application avec seulement quelques lignes de code.

Surveillance de l’utilisation de la mémoire

Jusqu’à présent, comment l’application fonctionne-t-elle pour vous? Avez-vous rencontré des plantages?

Si vous avez exécuté ce projet sur un appareil, il y a de bonnes chances que vous ayez rencontré un accident ou deux. Pourquoi? Parce que cette application est un porc de mémoire.

porc de mémoire

Utilisation d’instruments

Pour voir à quel point il est grave, exécutez à nouveau l’application, puis procédez comme suit:

  1. Sélectionnez le Navigateur de débogage dans le Navigateur panneau.
  2. Puis sélectionnez Mémoire sous la liste des jauges de débogage.
  3. Cliquez sur Profil dans les instruments.
  4. Enfin, cliquez sur Redémarrer dans la boîte de dialogue qui apparaît.

Tutoriel Nuke - Mémoire 1-3

Tutoriel Nuke - Mémoire 4

Avec le profileur en cours d’exécution, faites défiler la galerie vers le bas, en accordant une attention particulière au Octets persistants colonne de la ligne étiquetée VM: données raster CG. Plus d’un gigaoctet de données est conservé!

Tutoriel Nuke - Utilisation de la mémoire

La source du problème est que, même si les images téléchargées semblent petites à l’écran, elles sont toujours des images de taille normale et stockées complètement en mémoire. Ce n’est pas bon.

Malheureusement, la NASA ne fournit pas de taille de vignette pour ses images.

Que faire? Peut-être que Nuke peut vous aider?

En effet, c’est possible!

Nuking avancé

Nuke dispose de nombreuses fonctionnalités que vous pouvez utiliser pour optimiser votre mémoire et améliorer l’expérience utilisateur et les temps de chargement de votre application.

Chargement avec les demandes

Jusqu’à présent, tu es passé loadImage une URL. Mais Nuke propose également une variante de cette méthode qui accepte un ImageRequest.

ImageRequest peut définir un ensemble d’images processeurs à appliquer après le téléchargement de l’image. Ici, vous allez créer un processeur de redimensionnement et le joindre à la demande.

Dans PhotoGalleryViewController.swift juste après la définition du photoURLs variable d’instance, ajoutez ces deux propriétés calculées:

// 1
var pixelSize: CGFloat {
  return cellSize * UIScreen.main.scale
}

// 2
var resizedImageProcessors: [ImageProcessing] {
  let imageSize = CGSize(width: pixelSize, height: pixelSize)
  return [ImageProcessors.Resize(size: imageSize, contentMode: .aspectFill)]
}

Voici ce que fait votre nouveau code:

  1. pixelSize est la taille en pixels de la cellule. Certains iPhones ont une résolution 2x (2 pixels par point) et d’autres une résolution 3x (3 pixels par point). Vous voulez que vos images soient nettes et non pixélisées sur vos écrans haute résolution. Ce multiplicateur est également connu sous le nom de échelle.
  2. resizedImageProcessors est une configuration Nuke qui définit les opérations que vous souhaitez effectuer sur les images. Pour l’instant, vous ne voulez redimensionner les images que pour les adapter à vos cellules et utiliser un remplissage d’aspect comme mode de contenu.

Retournant vers collectionView(_:cellForItemAt:), remplacez l’appel à Nuke.loadImage(with:options:into:) avec ce qui suit:

// 1
let request = ImageRequest(
  url: url,
  processors: resizedImageProcessors)

// 2
Nuke.loadImage(with: request, options: options, into: cell.imageView)

Avec ce code, vous:

  1. Créé un ImageRequest pour l’URL d’image souhaitée, et utilisez le processeur d’image que vous avez défini précédemment pour appliquer un redimensionnement sur l’image après son téléchargement.
  2. Demandez à Nuke de charger l’image en fonction de cette demande, en utilisant les options que vous avez définies précédemment, et de l’afficher dans la vue d’image de la cellule.

Maintenant, créez et exécutez à nouveau, et ouvrez le profileur de mémoire de la même manière que vous l’avez fait auparavant.

Un graphique et un tableau montrant l'utilisation réduite de la mémoire

Hou la la! le VM: données raster CG est maintenant sous 300 Mo! C’est un nombre beaucoup plus raisonnable! :]

Optimisation du code

Actuellement, pour chaque cellule de vue de collection, vous recréez la même ImageLoadingOptions. Ce n’est pas très efficace.

Une façon de résoudre ce problème serait de créer une propriété de classe constante pour les options que vous utilisez et de la transmettre à Nuke. loadImage(with:options:into:) chaque fois.

Nuke a une meilleure façon de faire cela. Dans Nuke, vous pouvez définir ImageLoadingOptions comme valeur par défaut lorsqu’aucune autre option n’est fournie.

Dans PhotoGalleryViewController.swift, ajoutez le code suivant au bas de viewDidLoad()

// 1
let contentModes = ImageLoadingOptions.ContentModes(
  success: .scaleAspectFill, 
  failure: .scaleAspectFit, 
  placeholder: .scaleAspectFit)

ImageLoadingOptions.shared.contentModes = contentModes

// 2
ImageLoadingOptions.shared.placeholder = UIImage(named: "dark-moon")

// 3
ImageLoadingOptions.shared.failureImage = UIImage(named: "annoyed")

// 4
ImageLoadingOptions.shared.transition = .fadeIn(duration: 0.5)

Dans ce code, vous:

  1. Définir la valeur par défaut contentMode pour chaque type de résultat de chargement d’image: succès, échec et l’espace réservé.
  2. Définissez l’image d’espace réservé par défaut.
  3. Définissez l’image par défaut à afficher en cas d’erreur.
  4. Définissez la transition par défaut d’un espace réservé à une autre image.

Une fois cela fait, revenez à collectionView(_:cellForItemAt:). Ici, vous devez faire deux choses.

Commencez par supprimer les lignes de code suivantes:

let options = ImageLoadingOptions(
  placeholder: UIImage(named: "dark-moon"),
  transition: .fadeIn(duration: 0.5)
)

Vous n’en aurez plus besoin, car vous avez défini les options par défaut. Ensuite, vous devez changer l’appel en loadImage(with:options:into:) pour ressembler à ceci:

Nuke.loadImage(with: request, into: cell.imageView)

Si vous créez et exécutez le code maintenant, vous ne verrez probablement pas beaucoup de différence, mais vous vous êtes faufilé dans une nouvelle fonctionnalité tout en améliorant votre code.

Éteignez votre Wi-Fi et exécutez à nouveau l’application. Vous devriez commencer à voir un petit extraterrestre en colère et frustré apparaître pour chaque image qui ne se charge pas.

Un petit extraterrestre en colère et frustré apparaît pour chaque image qui ne se charge pas

En plus d’ajouter une image d’échec, vous devriez vous sentir satisfait de savoir que votre code est plus petit et plus propre!

Utilisation d’ImagePipeline pour charger des images

OK, vous devez résoudre un autre problème.

Actuellement, lorsque vous appuyez sur une image dans la galerie, l’image à afficher dans la vue détaillée est récupérée sur le fil principal. Cela, comme vous le savez déjà, empêche l’interface utilisateur de répondre à l’entrée.

Si votre connexion Internet est suffisamment rapide, vous ne remarquerez peut-être aucun problème pour quelques images. Cependant, faites défiler jusqu’au bas de la galerie. Découvrez l’image de la nébuleuse de l’Aigle, qui est l’image du milieu, troisième rangée à partir du bas:

Image de la nébuleuse de l'Aigle dans la galerie

L’image en taille réelle est d’environ 60 Mo! Si vous appuyez dessus, vous remarquerez le gel de votre interface utilisateur.

Pour résoudre ce problème, vous allez utiliser – attendez – Nuke. Cependant, vous n’utiliserez pas loadImage(with:into:). Au lieu de cela, vous utiliserez quelque chose de différent pour comprendre les différentes façons dont vous pouvez utiliser Nuke.

Ouvert à PhotoViewController.swift. Importez Nuke en haut du fichier.

import Nuke

Trouvez le code suivant dans viewDidLoad()

if let imageData = try? Data(contentsOf: imageURL),
  let image = UIImage(data: imageData) {
  imageView.image = image
}

C’est le même chargement d’image naïf que vous avez vu auparavant. Remplacez-le par le code suivant:

// 1
imageView.image = ImageLoadingOptions.shared.placeholder
imageView.contentMode = .scaleAspectFit

// 2
ImagePipeline.shared.loadImage(
  // 3
  with: imageURL) { [weak self] response in // 4
  guard let self = self else {
    return
  }
  // 5
  switch response {
  // 6
  case .failure:
    self.imageView.image = ImageLoadingOptions.shared.failureImage
    self.imageView.contentMode = .scaleAspectFit
  // 7
  case let .success(imageResponse):
    self.imageView.image = imageResponse.image
    self.imageView.contentMode = .scaleAspectFill
  }
}

Dans ce code, vous:

  1. Définissez l’image d’espace réservé et le mode de contenu.
  2. Appel loadImage(with:) sur le ImagePipeline singleton.
  3. Transmettez l’URL de la photo appropriée.
  4. Fournissez un gestionnaire d’achèvement. Le gestionnaire a un paramètre de type enum Result.
  5. le response ne peut avoir que deux valeurs: .success avec une valeur associée de type ImageResponse, ou .failure avec une valeur associée de type Error. Une instruction switch fonctionnera donc mieux pour vérifier les deux valeurs possibles.
  6. En cas d’échec, définissez l’image sur l’image d’échec appropriée.
  7. Pour réussir, définissez l’image sur la photo téléchargée.

Là! C’est l’heure. Construisez et exécutez et appuyez à nouveau sur la photo de la nébuleuse de l’Aigle.

L'image complète de la nébuleuse de l'aigle est chargée en douceur

Fini le gel de l’interface utilisateur! Bon travail.

Mise en cache des images

Nuke a une fonctionnalité intéressante qui vous permet de mettre en cache des images de manière agressive. Qu’est-ce que ça veut dire? Eh bien, cela signifie qu’il ignorera le Cache-Control directives qui peuvent être trouvées dans les en-têtes HTTP.

Mais pourquoi voudriez-vous faire cela? Parfois, vous savez que les données ne changeront pas. C’est souvent le cas pour les images trouvées sur le Web. Pas toujours, mais souvent.

Si votre application fonctionne avec des images dont vous savez qu’elles ne devraient pas changer, il est judicieux de configurer Nuke pour utiliser un cache d’images agressif et ne pas risquer de recharger les mêmes images.

Dans PhotoGalleryViewController.swift, revenir à viewDidLoad() et ajoutez ce code à la fin:

// 1
DataLoader.sharedUrlCache.diskCapacity = 0
    
let pipeline = ImagePipeline {
  // 2
  let dataCache = try? DataCache(name: "com.raywenderlich.Far-Out-Photos.datacache")
      
  // 3
  dataCache?.sizeLimit = 200 * 1024 * 1024
      
  // 4
  $0.dataCache = dataCache
}

// 5
ImagePipeline.shared = pipeline

Ici vous:

  1. Désactivez le cache disque par défaut en définissant sa capacité sur zéro. Vous ne voulez pas mettre accidentellement en cache l’image deux fois. Vous allez créer votre propre cache.
  2. Créez un nouveau cache de données.
  3. Définissez la limite de taille du cache sur 200 Mo (car il y a 1 024 octets par kilo-octet et 1 024 kilo-octets par mégaoctet).
  4. Configurer le ImagePipeline pour utiliser le nouvellement créé DataCache.
  5. Définissez ce pipeline d’images comme étant celui utilisé par défaut lorsque aucun n’est spécifié.

C’est tout!

Si vous créez et exécutez votre code maintenant, vous ne verrez aucune différence. Cependant, dans les coulisses, votre application met en cache toutes les images sur le disque et utilise le cache pour charger tout ce qu’elle peut.

Remarque: Si vous souhaitez vider le cache manuellement, c’est simple, mais pas simple. Vous devez effectuer une conversion de type en raison de la façon dont les éléments sont stockés en interne dans un ImagePipeline:

if let dataCache = ImagePipeline.shared
  .configuration.dataCache as? DataCache {
    dataCache.removeAll()
}

Combiner avec Combine

Nuke prend en charge l’intégration avec d’autres frameworks via son extensions. Dans la partie suivante, vous apprendrez à combiner à l’aide de Nuke avec le framework Combine. :]

Combine est un cadre Apple a publié avec iOS 13 pour fournir des API déclaratives via une structure éditeur-abonné. Il facilite grandement la gestion des événements asynchrones et vous permet de combiner des opérations de traitement d’événements.

Configurer ImagePublisher

Vous devez installer une extension Nuke nommée ImagePublisher. Installez-le de la même manière que vous l’avez fait précédemment pour Nuke à l’aide du Gestionnaire de packages Swift.

Ajoutez un nouveau package à partir de l’URL: https://github.com/kean/ImagePublisher.git.

Remarque: Au moment de la rédaction de ce tutoriel, la version disponible est la 0.2.1.

Ajout de l'extension ImagePublisher pour la moissonneuse-batteuse

Avant de commencer à utiliser la nouvelle extension, notez ce qui se passe actuellement dans votre application.

Tout d’abord, la galerie télécharge toutes les images et stocke une version redimensionnée des images dans son cache. Deuxièmement, lorsque vous ouvrez une photo, l’image complète commence à se télécharger et, pendant que cela se produit, vous voyez une image d’espace réservé.

Jusqu’à présent, cela a du sens, mais ce n’est pas le meilleur l’expérience que vous souhaitez offrir à vos utilisateurs. Vous affichez une image générique d’espace réservé bien qu’une image redimensionnée soit déjà stockée. Idéalement, vous souhaitez afficher cette photo à la place jusqu’à ce que l’image complète soit téléchargée.

La meilleure façon de le faire est de demander l’image redimensionnée, qui sera probablement presque instantanément disponible si elle était déjà mise en cache. Ensuite, juste après la fin de cette demande, récupérez la demande en taille réelle. En d’autres termes: vous souhaitez enchaîner les deux requêtes.

Mais d’abord, testez vos compétences aux demandes de base avec ImagePublisher.

Utilisation d’ImagePublisher

Dans PhotoViewController.swift, ajoutez les importations suivantes en haut du fichier:

import Combine
import ImagePublisher

Ajoutez ensuite les propriétés suivantes en haut de la classe.

//1
var cancellable: AnyCancellable?

//2
var resizedImageProcessors: [ImageProcessing] = []
  1. cancellable, en termes très simples, est un descripteur pour une opération qui – comme son nom l’indique – est annulable. Vous l’utiliserez pour faire référence à l’opération de demande que vous allez créer pour télécharger les images.
  2. resizedImageProcessors est comme ce que vous avez utilisé dans PhotoGalleryViewController lorsque vous avez créé la demande pour l’image redimensionnée. Vous voulez le PhotoGalleryViewController car il ouvre un nouveau PhotoViewController pour fournir les mêmes processeurs afin de pouvoir faire une demande identique. Si la demande est différente, une nouvelle photo sera téléchargée, mais vous voulez que la même apparaisse dans la galerie afin que vous puissiez la récupérer dans le cache chaque fois que possible.

À la fin du cours, ajoutez la nouvelle méthode

func loadImage(url: URL) {
  // 1
  let resizedImageRequest = ImageRequest(
    url: url,
    processors: resizedImageProcessors)

  // 2
  let resizedImagePublisher = ImagePipeline.shared
    .imagePublisher(with: resizedImageRequest)

  // 3
  cancellable = resizedImagePublisher
    .sink(
    // 4
      receiveCompletion: { [weak self] response in
        guard let self = self else { return }
        switch response {
        case .failure:
          self.imageView.image = ImageLoadingOptions.shared.failureImage
          self.imageView.contentMode = .scaleAspectFit
        case .finished:
          break
        }
      },
    // 5
      receiveValue: {
        self.imageView.image = $0.image
        self.imageView.contentMode = .scaleAspectFill
      }
  )
}

Voici ce que cela fait:

  1. Créez une nouvelle demande avec l’URL d’image fournie, en utilisant les processeurs que vous avez définis précédemment.
  2. Créez un nouvel éditeur pour traiter cette demande. Un éditeur est en fait un flux de valeurs qui se termine ou échoue. Dans le cas de cet éditeur, il traitera la demande puis retournera l’image ou échouera.
  3. Exécutez cet éditeur, ou dans Combine jargon: Créez un abonné avec sink(receiveCompletion:receiveValue:) sur cet éditeur avec deux fermetures.
  4. La première fermeture fournit le résultat de l’éditeur, qu’il ait réussi à terminer son opération normalement ou qu’il ait échoué. En cas d’échec, vous affichez l’image d’échec avec un mode de contenu adapté à l’aspect. Et si cela se termine normalement, alors vous ne faites rien ici car vous recevrez la valeur dans la deuxième fermeture.
  5. Affichez la valeur que vous avez reçue normalement avec un remplissage d’aspect. Sachez que vous ne recevez aucune valeur si la première fermeture a eu une réponse d’échec.

Dans viewDidLoad(), supprimez tout le code suivant qui est responsable du chargement de l’image:

ImagePipeline.shared.loadImage(
  with: imageURL) { [weak self] response in // 4
  guard let self = self else {
    return
  }
  switch response {
    case .failure:
      self.imageView.image = ImageLoadingOptions.shared.failureImage
      self.imageView.contentMode = .scaleAspectFit
    case let .success(imageResponse):
      self.imageView.image = imageResponse.image
      self.imageView.contentMode = .scaleAspectFill
    }
}

Et remplacez-le par l’appel à la méthode nouvellement ajoutée:

loadImage(url: imageURL)

Allez maintenant à PhotoGalleryViewController.swift et trouve collectionView(_:didSelectItemAt:). Juste avant de pousser le contrôleur de vue sur la pile de navigation, ajoutez cette ligne:

photoViewController.resizedImageProcessors = resizedImageProcessors

Sur votre simulateur, désinstallez complètement l’application en appuyant longuement sur l’icône de l’application, puis choisissez Supprimer l’application. Cela supprime complètement les images mises en cache, parmi tout ce qui concerne l’application, afin que vous puissiez recommencer à zéro.

Suppression d'une application du simulateur.

Construisez et exécutez. Appuyez sur n’importe quelle image dont le chargement est terminé et voyez qu’elle apparaît instantanément sans afficher l’espace réservé de chargement. Mais l’image ne sera plus aussi nette qu’auparavant. Cela est dû au fait que vous chargez la même image redimensionnée à partir de la galerie et que Nuke vous la fournit avec la deuxième requête du cache.

Vous n’avez pas encore terminé. Vous souhaitez également charger l’image en taille réelle.

Requêtes de chaînage

Jusqu’à présent, vous avez défini un éditeur, mais vous souhaitez utiliser deux éditeurs – un pour chaque demande d’image – et enchaîner les deux pour les exécuter de manière séquentielle.

Dans PhotoViewController.swift dans loadImage(url:), juste après resizedImagePublisher est défini, ajoutez cette ligne.

let originalImagePublisher = ImagePipeline.shared.imagePublisher(with: url)

Cela crée un éditeur en utilisant directement l’URL de l’image sans aucun processeur d’image.

Ensuite, remplacez:

cancellable = resizedImagePublisher
  .sink(

avec:

cancellable = resizedImagePublisher.append(originalImagePublisher)
  .sink(

L’addition de .append(:) sur un éditeur crée un nouvel éditeur qui combine resizedImagePublisher et originalImagePublisher. Ainsi, vous pouvez le traiter de la même manière, et en interne il fera travailler et terminer chaque éditeur avant de passer au suivant.

Construisez et exécutez. Appuyez sur n’importe quelle image de la galerie et voyez-la s’ouvrir avec une image, puis devant vos yeux pour afficher une image plus nette.

Image plus nette remplaçant celle existante.

Remarque: Si vous ne voyez pas clairement la transition, réduisez la taille de l’image que vous demandez dans la galerie dans PhotoGalleryViewController.resizedImageProcessors pour réduire la qualité de la première image.

Nettoyage de l’abonné

Plus tôt, vous avez appris que dans l’abonné, vous êtes notifié une fois avec le résultat de l’éditeur s’il a terminé ou échoué, et ce n’est que s’il est terminé que vous êtes averti avec une valeur.

Cela signifie que vous répondez à deux endroits différents, bien que tout ce que vous faites est simplement d’afficher une image différente et un mode de contenu différent.

Pourquoi ne pas demander à l’éditeur de simplement fournir l’image et le mode de contenu à utiliser, de sorte que tout ce que vous avez à faire est simplement d’afficher ces valeurs. Plus précisément: faites en sorte que l’éditeur fournisse une valeur même en cas d’échec.

L’éditeur que vous utilisez renvoie un objet contenant l’image entre autres, mais sans rien sur le mode de contenu. C’est quelque chose d’unique à cette application.

La première chose à faire est donc de demander à l’éditeur de fournir un type de valeur différent, au lieu de ImageResponse, qui est un type défini dans ImagePublisher. Vous souhaitez utiliser un tuple (Image, UIView.ContentMode)

Retour à PhotoViewController. Juste avant l’appel à .sink(receiveCompletion:receiveValue:) ajoutez ce qui suit:

.map {
  ($0.image, UIView.ContentMode.scaleAspectFill)
}

map(_:) fait une transformation pour vous. Vous utilisez la valeur d’origine reçue de l’éditeur et convertissez cette valeur en tuple. Et comme il y a une valeur, cela signifie que l’éditeur a réussi et que toutes les images de la NASA devraient avoir scaleAspectFill comme leur mode de contenu.

Deuxièmement, vous souhaitez intercepter l’éditeur si une erreur se produisait et entraînerait un échec. Au lieu de cela, demandez à l’éditeur de vous donner l’image de l’échec et son aspect dans un tuple.

Après la définition de originalImagePublisher, ajouter:

guard let failedImage = ImageLoadingOptions.shared.failureImage else {
  return
}

Ensuite, juste après les accolades fermantes pour map(_:) et avant l’appel à .sink(receiveCompletion:receiveValue:), ajouter:

.catch { _ in
  Just((failedImage, .scaleAspectFit))
}

L’éditeur appelle .catch(_:) chaque fois qu’il échoue. L’appel à Just(_:) crée un éditeur qui envoie toujours la valeur donnée, puis se termine. Dans ce cas, il s’agit de l’image d’échec par défaut que vous avez spécifiée dans le global ImageLoadingOptions. le catch(_:) signifie que toute erreur des éditeurs d’images sera «interceptée», puis l’échec sera remplacé par l’image d’échec.

Enfin, remplacez tout le code que vous avez pour .sink(receiveCompletion:receiveValue:) avec:

.sink {
  self.imageView.image = $0
  self.imageView.contentMode = $1
}

Votre abonné recevra toujours une valeur car vous avez réussi à transformer l’éditeur en un éditeur qui n’échoue jamais. Et le type de résultat qu’il fournit maintenant est beaucoup plus pratique pour votre application. Donc, naturellement, le code qui vérifie s’il a réussi ou non n’est pas nécessaire, et tout ce dont vous avez besoin est de recevoir la valeur “Just”. :]

Créez et exécutez l’application. Cela fonctionnera comme avant, mais vous avez maintenant du code Combine plus simple!

Où aller en partant d’ici?

Félicitations pour être arrivé aussi loin! Vous avez beaucoup appris sur l’utilisation de Nuke pour améliorer les performances de votre application!

Téléchargez les fichiers de projet terminés en cliquant sur le Télécharger les matériaux bouton en haut ou en bas du didacticiel.

Il y a encore plus de fonctionnalités dans Nuke que vous pouvez explorer. Par exemple, Nuke prend en charge les GIF animés. Vous pouvez également approfondir le sujet de la gestion du cache, si votre application a besoin de ce niveau de contrôle.

En outre, plusieurs plug-ins peuvent étendre les fonctionnalités de Nuke, comme la prise en charge de SwiftUI , RxSwift, WebP, et Alamofire.

Et vous pouvez toujours en savoir plus sur Combine dans le didacticiel Combine: Getting Started.

Si vous avez aimé en savoir plus sur Combine, vous pourriez être intéressé par notre Combinez: programmation asynchrone avec Swift book.

J’espère que vous avez apprécié ce tutoriel Nuke et, si vous avez des questions ou des commentaires, rejoignez la discussion du forum ci-dessous!

Close Menu