Skip to content

Instantly share code, notes, and snippets.

@profh
Created October 31, 2023 11:23
Show Gist options
  • Select an option

  • Save profh/d6543b9bf35914555b1c1e821f4c9e7d to your computer and use it in GitHub Desktop.

Select an option

Save profh/d6543b9bf35914555b1c1e821f4c9e7d to your computer and use it in GitHub Desktop.

Revisions

  1. profh created this gist Oct 31, 2023.
    101 changes: 101 additions & 0 deletions combine_demo.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,101 @@
    // h/t to @JohnSundell (https://www.swiftbysundell.com/)
    import UIKit
    import Combine
    import PlaygroundSupport

    // This playground will execute indefinetly in order to give our
    // async operations enough time to execute.
    PlaygroundPage.current.needsIndefiniteExecution = true


    // --- Creating a publisher for performing a network request ---
    // Note: set a retry automatically (3x) if request fails

    let url = URL(string: "https://api.github.com/repos/67443-Mobile-Apps/RepoBrowser2022")!
    // for class exercise, change the url to:
    // let url = URL(string: "https://api.github.com/search/repositories?q=language:swift&sort=stars&order=desc")!
    let publisher = URLSession.shared
    .dataTaskPublisher(for: url)
    .retry(3)


    // --- Bringing back our old friend, Repository ---

    struct Repository: Codable, Identifiable {
    let id: Int
    let name: String
    let htmlURL: String
    let itemDescription: String?

    enum CodingKeys: String, CodingKey {
    case id
    case name
    case htmlURL = "html_url"
    case itemDescription = "description"
    }
    }


    // --- Subscribing to our publisher using sink() ---

    let cancellableSubscriber = publisher.sink(
    receiveCompletion: { completion in
    switch completion {
    case .failure(let error):
    print(error)
    case .finished:
    print("Success")
    }
    },
    receiveValue: { value in
    let decoder = JSONDecoder()

    do {
    // Since each value passed into our closure will be a tuple
    // containing the downloaded data, as well as the network
    // response itself, we're accessing the 'data' property here:
    let repo = try decoder.decode(Repository.self, from: value.data)
    print(repo)
    } catch {
    print(error)
    }
    }
    )

    // --- Constructing a reactive chain of operators ---

    let repoPublisher = publisher
    .map(\.data)
    .decode(
    type: Repository.self,
    decoder: JSONDecoder()
    )
    .receive(on: DispatchQueue.main)


    // --- Updating our UI based on our reactive chain ---

    // Two labels that we want to render our data using:
    let nameLabel = UILabel()
    let urlLabel = UILabel()
    let errorLabel = UILabel()

    let cancellableRepoSubscriber = repoPublisher.sink(
    receiveCompletion: { completion in
    switch completion {
    case .failure(let error):
    // Rendering a description of the error that was encountered:
    errorLabel.text = error.localizedDescription
    print("ErrorMsg: \(error.localizedDescription)")
    case .finished:
    break
    }
    },
    receiveValue: { repo in
    // Rendering the downloaded repository's name and url:
    nameLabel.text = repo.name
    urlLabel.text = repo.htmlURL
    print("Name: \(repo.name)")
    print("URL: \(repo.htmlURL)")
    }
    )