Pulse SDK Integration Tutorial for iOS: Network Logger

Learn how to set up network logger for your app using Pulse SDK. Pulse framework provides you a UI to display the logs in your debug app and also persist the logs that can be exported anytime. By Mark Struzinski.

5 (1) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Set up ImageDownloader

In the Xcode Project Navigator, open MovieSearch ▸ Source ▸ Network ▸ Service. Right-click the Service group and select New File ….

Then select Swift File and click Next. Name the new file ImageDownloader.swift and click Create.

Remove import Foundation and replace with this code:

// 1
import UIKit
import PulseCore

// 2
class ImageDownloader: NSObject {
  private let imageBaseURLString = "https://image.tmdb.org"

  let urlSession = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: nil)
  // 3
  let logger = NetworkLogger()

  // 4
  var imageDownloadCompletion: ((Result<UIImage, NetworkError>) -> Void)?

  // 5
  func downloadImage(for imageType: ImageType, at path: String) {
    guard let url = try? url(for: imageType, at: path) else {
      return
    }

    let task = urlSession.dataTask(with: url)
    task.delegate = self
    task.resume()
  }

  // 6
  private func url(for imageType: ImageType, at path: String) throws -> URL {
    let imagePathParam = imageType.pathParameter()
    guard let baseURL = URL(string: imageBaseURLString),
      var urlComponents = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else {
        throw NetworkError.invalidURL
      }

    urlComponents.path = "/t/p/\(imagePathParam)\(path)"

    let queryItems: [URLQueryItem] = [
      URLQueryItem(name: "api_key", value: APIKey.value)
    ]

    urlComponents.queryItems = queryItems

    guard let url = urlComponents.url else {
      throw NetworkError.invalidURL
    }

    return url
  }
}

This code is an implementation of a simple download utility for images. As a quick summary, this code:

  1. Imports UIKit instead of Foundation because you’ll work with UIImage. It also imports PulseCore for logging.
  2. Makes the implementation a class. You’ll implement the same delegation pattern as in NetworkService, so ImageDownloader needs to be a class that inherits from NSObject.
  3. Creates a NetworkLogger instance just like you did before.
  4. Uses a property to hold a completion handler like you did before.
  5. Creates a function to trigger image downloads.
  6. Generates an image URL from the image type, detail or list, and the poster path retrieved from the API response for each movie.

Next, implement Pulse logging in an extension in ImageDownloader.swift. Under the closing brace of ImageDownloader, add:

extension ImageDownloader: URLSessionTaskDelegate, URLSessionDataDelegate {
  func urlSession(
    _ session: URLSession, 
    dataTask: URLSessionDataTask, 
    didReceive response: URLResponse, 
    completionHandler: @escaping (URLSession.ResponseDisposition) -> Void
  ) {
    logger.logDataTask(dataTask, didReceive: response)

    if let response = response as? HTTPURLResponse,
      response.statusCode != 200 {
      imageDownloadCompletion?(.failure(.invalidResponseType))
    }

    completionHandler(.allow)
  }

  func urlSession(
    _ session: URLSession, 
    task: URLSessionTask, 
    didCompleteWithError error: Error?
    ) {
    logger.logTask(task, didCompleteWithError: error)
    imageDownloadCompletion?(.failure(NetworkError.invalidResponseType))
  }

  func urlSession(
    _ session: URLSession, 
    task: URLSessionTask, 
    didFinishCollecting metrics: URLSessionTaskMetrics
    ) {
    logger.logTask(task, didFinishCollecting: metrics)
  }

  func urlSession(
    _ session: URLSession, 
    dataTask: URLSessionDataTask, 
    didReceive data: Data
    ) {
    logger.logDataTask(dataTask, didReceive: data)
    guard let image = UIImage(data: data) else {
      imageDownloadCompletion?(.failure(.invalidParse))
      return
    }
    imageDownloadCompletion?(.success(image))
  }
}

Much of this is like the NetworkService extension. Note the transformation of Data to UIImage in the urlSession(_:dataTask:didReceive:) function. Aside from that key difference, all the patterns and log statements are like NetworkService.

Update MovieDetailViewModel

Now, you’ll switch to ImageDownloader for all image downloads. Open MovieDetailViewModel.swift. At the top of the class declaration, replace the networkService property with an ImageDownloader:

private let imageDownloader = ImageDownloader()

Next, under fetchImage(for:), add this helper function:

private func processImageResponse(result: Result<UIImage, NetworkError>) {
  // 1
  guard let image = try? result.get() else {
    return
  }

  // 2
  DispatchQueue.main.async {
    self.posterImage = image
  }
}

This helper:

  1. Checks whether the network request returned an image. Otherwise, it returns early.
  2. Sets the posterImage on the main queue, triggering a UI update.

Now, replace fetchImage(for:) with:

func fetchImage(for movie: Movie, imageType: ImageType) {
  guard let posterPath = movie.posterPath else {
    return
  }

  imageDownloader.imageDownloadCompletion = processImageResponse(result:)
  imageDownloader.downloadImage(for: .list, at: posterPath)
}

This code switches the download image implementation to the new class. It follows the delegation pattern required to use Pulse logging.

Next, open NetworkService.swift and remove the downloadImage(for:at:completion:) and url(for:at:) functions because you no longer need them. That completes your swap of the image download implementation.

Build and run to check your work. Perform a search and you’ll see image request results showing in your logs alongside the search results:

Image results

Tap an image request. Then tap again into Response Body and you’ll see the image as part of the response:

Image results response body

And that’s it! You updated your network implementation to add Pulse logging.

Pulse gives you some excellent functionality for in-app debugging out of the box. You’ll find the visually-oriented interface, ability to review results in-app and ability to share Pulse files helpful when debugging tricky network conditions.

Where to Go From Here?

Download the completed project by clicking Download Materials at the top or bottom of the tutorial.

Pulse has many more features you can explore. Pulse Pro adds the ability to livestream logs.

If you have specific needs, you can develop your own viewer front-end that works with the Pulse data and is implemented via a Core Data based API, so iOS and macOS developers should feel at home.

Finally, if you want to explore more about network proxy tools, check out our tutorials on Charles and Proxyman, which offer a different take on viewing and debugging network conditions.

If you have any comments or questions, please join the discussion below.