Development record of developer who study hard everyday.

, ,

How to call the api in SwiftUI and Combine ios project.

 How to call the api in SwiftUI and Combine ios project.


1. ContentView

import SwiftUI


struct ContentView: View {
    //We need @ObservedObject to observe ViewModel class

    @ObservedObject

    var viewModel = ViewModel()

    

    var body: some View {

        VStack {
            //List is rerendered whenever ViewModel's characters property changed

            List(viewModel.characters) { character in

                CharacterRow(character: character)

            }

        }

        .padding()

        .onAppear {
            //this closure is executed before View appears

            viewModel.fetchCharacters()

        }

    }

}


2. CharacterRow

import SwiftUI


struct CharacterRow: View {

    

    var character: Character

    

    var body: some View {

        HStack {

            AsyncImage(url: character.image) { image in

                image

                    .resizable()

                    .clipShape(RoundedRectangle(cornerRadius: /*@START_MENU_TOKEN@*/25.0/*@END_MENU_TOKEN@*/, style: .continuous))

                    .frame(width: 100, height: 100)

            } placeholder: {

                ProgressView()

            }

            Text(character.name)

        }

    }

}



3. Character struct and CharacterResponse struct

import Foundation


struct CharacterResponse: Codable {

    let results: [Character]

}


struct Character: Codable, Identifiable {

    let id: Int

    let name: String

    let image: URL


}

I receive api response using these structs


4. ViewModel

import Foundation

import Combine


class ViewModel : ObservableObject {
    //@Published make the view re-render whenever characters changes

    @Published var characters: [Character] = []

    var subscriptions = Set<AnyCancellable>()

    

    func fetchCharacters() {

        guard let url = URL(string: "https://rickandmortyapi.com/api/character") else {return}

        

        URLSession.shared.dataTaskPublisher(for: url)

            .map {

                $0.data

            }.decode(type: CharacterResponse.self, decoder: JSONDecoder())

            .receive(on: DispatchQueue.main)

            .sink(receiveCompletion: { completion in

                switch completion {

                case .failure(let error):

                    print(error.localizedDescription)

                case .finished:

                    print("Data Fetched")

                }

                

            }, receiveValue: { decodedData in

                print("receiveValue, count: \(decodedData.results.count)")

                self.characters = decodedData.results

            })

            .store(in: &subscriptions)

    }

}

I call the api and store character information to characters property.

Because characters is type of @Published, ListView is rerendered.









Share:
Read More
, ,

How to subscribe one publisher simultaneously in Swift Combine

 How to subscribe one publisher simultaneously in Swift Combine


1. Limitation of Publishers when several subscribers exists

import UIKit

import Combine


var cancellables = Set<AnyCancellable>()


//This is Publishers.Zip which combine two publishers

func valuePublisher() -> Publishers.Zip<Publishers.Sequence<[Int], Never>, Publishers.Autoconnect<Timer.TimerPublisher>> {

    

    let timerPublisher = Timer.publish(every: 1, on: .main, in: .default).autoconnect()

    

    return Publishers.Zip([1, 2, 3, 4, 5, 6, 7].publisher, timerPublisher)

}


func simpleMulticast() {

    let zipPublisher = valuePublisher()

    

    let publisher = zipPublisher

        .print()

        .map { "\($0.0)" }

        .eraseToAnyPublisher()

        

    publisher

        .map{ "subscribtion1 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    

    publisher

        .map{ "subscribtion2 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    

    //subscribe after 3 seconds

    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {

        publisher

            .map { "subscribtion3 - \($0)" }

            .sink { print($0) }

            .store(in: &cancellables)

    }

}


simpleMulticast()


The result of above code is

receive subscription: (Zip)

request unlimited

receive subscription: (Zip)

request unlimited

receive value: ((1, 2025-01-05 13:24:30 +0000))

subscribtion2 - 1

receive value: ((1, 2025-01-05 13:24:30 +0000))

subscribtion1 - 1

receive value: ((2, 2025-01-05 13:24:31 +0000))

subscribtion2 - 2

receive value: ((2, 2025-01-05 13:24:31 +0000))

subscribtion1 - 2

receive value: ((3, 2025-01-05 13:24:32 +0000))

subscribtion2 - 3

receive value: ((3, 2025-01-05 13:24:32 +0000))

subscribtion1 - 3

receive subscription: (Zip)

request unlimited

receive value: ((4, 2025-01-05 13:24:33 +0000))

subscribtion2 - 4

receive value: ((4, 2025-01-05 13:24:33 +0000))

subscribtion1 - 4

receive value: ((1, 2025-01-05 13:24:33 +0000))

subscribtion3 - 1

receive value: ((5, 2025-01-05 13:24:34 +0000))

subscribtion2 - 5

receive value: ((5, 2025-01-05 13:24:34 +0000))

subscribtion1 - 5

receive value: ((2, 2025-01-05 13:24:34 +0000))

subscribtion3 - 2

receive value: ((6, 2025-01-05 13:24:35 +0000))

subscribtion2 - 6

receive value: ((6, 2025-01-05 13:24:35 +0000))

subscribtion1 - 6

receive value: ((3, 2025-01-05 13:24:35 +0000))

subscribtion3 - 3

receive value: ((7, 2025-01-05 13:24:36 +0000))

subscribtion2 - 7

receive finished

receive value: ((7, 2025-01-05 13:24:36 +0000))

subscribtion1 - 7

receive finished

receive value: ((4, 2025-01-05 13:24:36 +0000))

subscribtion3 - 4

receive value: ((5, 2025-01-05 13:24:37 +0000))

subscribtion3 - 5

receive value: ((6, 2025-01-05 13:24:38 +0000))

subscribtion3 - 6

receive value: ((7, 2025-01-05 13:24:39 +0000))

subscribtion3 - 7

receive finished

In this result, same log is printed repeatedly whenever one subscriber receives value.

Moreover, third subscriber receives not recent value but oldest value.

This is quiet strange and inefficient.

To get over this limitation, we use multicast or share method.


2. Example of multicast method

import UIKit

import Combine


var cancellables = Set<AnyCancellable>()


//This is Publishers.Zip which combine two publishers

func valuePublisher() -> Publishers.Zip<Publishers.Sequence<[Int], Never>, Publishers.Autoconnect<Timer.TimerPublisher>> {

    

    let timerPublisher = Timer.publish(every: 1, on: .main, in: .default).autoconnect()

    

    return Publishers.Zip([1, 2, 3, 4, 5, 6, 7].publisher, timerPublisher)

}



let subject = PassthroughSubject<String, Never>()


func multicast() {

    print("multicast called")

    let zipPublisher = valuePublisher()

    

    let publisher = zipPublisher

        .print()

        .map { "\($0.0)" }

        .eraseToAnyPublisher()

    
    //multicast zipPublisher using PassthroughSubject

    let multicast = publisher.multicast(subject: subject)

    

    multicast

        .map{ "subscribtion1 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    

    multicast

        .map{ "subscribtion2 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    
    //Subscribing starts only when Publishers.Multicast is connected

    multicast.connect().store(in: &cancellables)

    

    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {

        multicast

            .map { "subscribtion3 - \($0)" }

            .sink { print($0) }

            .store(in: &cancellables)

    }

}


multicast()


The result of multicast is

multicast called

receive subscription: (Zip)

request unlimited

receive value: ((1, 2025-01-05 13:34:45 +0000))

subscribtion2 - 1

subscribtion1 - 1

receive value: ((2, 2025-01-05 13:34:46 +0000))

subscribtion2 - 2

subscribtion1 - 2

receive value: ((3, 2025-01-05 13:34:47 +0000))

subscribtion2 - 3

subscribtion1 - 3

receive value: ((4, 2025-01-05 13:34:48 +0000))

subscribtion2 - 4

subscribtion1 - 4

subscribtion3 - 4

receive value: ((5, 2025-01-05 13:34:49 +0000))

subscribtion2 - 5

subscribtion1 - 5

subscribtion3 - 5

receive value: ((6, 2025-01-05 13:34:50 +0000))

subscribtion2 - 6

subscribtion1 - 6

subscribtion3 - 6

receive value: ((7, 2025-01-05 13:34:51 +0000))

subscribtion2 - 7

subscribtion1 - 7

subscribtion3 - 7

receive finished

This is the real multi subscribing!

The third subscriber receives recent value and There are not repeated log.


3. Example of Share method

import UIKit

import Combine


var cancellables = Set<AnyCancellable>()


//This is Publishers.Zip which combine two publishers

func valuePublisher() -> Publishers.Zip<Publishers.Sequence<[Int], Never>, Publishers.Autoconnect<Timer.TimerPublisher>> {

    

    let timerPublisher = Timer.publish(every: 1, on: .main, in: .default).autoconnect()

    

    return Publishers.Zip([1, 2, 3, 4, 5, 6, 7].publisher, timerPublisher)

}


func shareMulticast() {

    let zipPublisher = valuePublisher()

    

    let publisher = zipPublisher

        .print()

        .map { "\($0.0)" }

        .eraseToAnyPublisher()

    

    let multicastSubject = publisher.share()

    

    multicastSubject

        .map{ "subscribtion1 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

    

    multicastSubject

        .map{ "subscribtion2 - \($0)" }

        .sink { print($0) }

        .store(in: &cancellables)

        

    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {

        multicastSubject

            .map { "subscribtion3 - \($0)" }

            .sink { print($0) }

            .store(in: &cancellables)

    }}


shareMulticast()


The result of share method is

multicast called

receive subscription: (Zip)

request unlimited

receive value: ((1, 2025-01-05 13:34:45 +0000))

subscribtion2 - 1

subscribtion1 - 1

receive value: ((2, 2025-01-05 13:34:46 +0000))

subscribtion2 - 2

subscribtion1 - 2

receive value: ((3, 2025-01-05 13:34:47 +0000))

subscribtion2 - 3

subscribtion1 - 3

receive value: ((4, 2025-01-05 13:34:48 +0000))

subscribtion2 - 4

subscribtion1 - 4

subscribtion3 - 4

receive value: ((5, 2025-01-05 13:34:49 +0000))

subscribtion2 - 5

subscribtion1 - 5

subscribtion3 - 5

receive value: ((6, 2025-01-05 13:34:50 +0000))

subscribtion2 - 6

subscribtion1 - 6

subscribtion3 - 6

receive value: ((7, 2025-01-05 13:34:51 +0000))

subscribtion2 - 7

subscribtion1 - 7

subscribtion3 - 7

receive finished

The result is same as multicast method example.

However, you don't need to connect Publisher.Share.

This is strength of share method.

Share:
Read More