In this tutorial you will learn about making network call in iOS SwiftUI using Combine Framework & dataTaskPublisher.
Combine Framework is mainly used for managing Publisher & Subscriber. Let me explain in easy way – Subscriber who listens things which Publisher publishes. In This Tutorial AnyPublisher
act as Publisher
& Cancellable with .sink
act as Subscriber. Note: Pub & Sub are protocols
We will be using the Github GET URL "https://api.github.com/users/janeshsutharios/repos"
Code for datataskPublisher implementation
import Foundation
import Combine
// MARK: Model for JSON Data -- For Converting Network Data object into swift readable data object
struct GithubEntity: Codable {
var id: Int?
var nodeID, name, fullName: String?
}
// MARK: Custom Error enum
enum WebServiceError: Error, LocalizedError {
case unknown, customError(reason: String)
var errorDescription: String? {
switch self {
case .unknown:
return "---Unknown error----"
case .customError(let reason):
return reason
}
}
}
struct CombileNetworkHelper {
static func fetchFromWebService() -> AnyPublisher<[GithubEntity], WebServiceError> {
// 1: GET Service URL
let url = URL(string: "https://api.github.com/users/janeshsutharios/repos")!
let urlRequest = URLRequest(url: url)
// 2: Added Publisher
var dataPublisher: AnyPublisher<[GithubEntity], WebServiceError>
// 3: DataTaskPublisher to fetch stream values
dataPublisher = URLSession.DataTaskPublisher(request: urlRequest, session: .shared)
// 4: tryMap for Creating a closure to map elements with Publisher
.tryMap { data, response in
guard let httpResponse = response as? HTTPURLResponse, 200..<300 ~= httpResponse.statusCode else {
throw WebServiceError.unknown
}
return data
}
// 5: Convert Response to Codable mode
.decode(type: [GithubEntity].self, decoder: JSONDecoder())
// 6: After Recieve data jump to Main Thread so it will be thread safe for UI Activities
.receive(on: RunLoop.main)
// 7: mapError is used to map error of custom type with closure
.mapError { error in
if let error = error as? WebServiceError {
return error
} else {
return WebServiceError.customError(reason: error.localizedDescription)
}
}
// 8:eraseToAnyPublisher is used to expose an instance of AnyPublisher to the downstream subscriber, rather than this publisher’s actual type
.eraseToAnyPublisher()
return dataPublisher
}
}
So firstly we created Codable model for Git URL & we have created custom error block as well
Code Explanation:
We have DataTaskPublisher with shared session.tryMap
is used for Creating a closure to map elements with Publisher, here we are transforming data..decode(..
Mapping the response
to codable type i.e. GithubEntity
.receive(on:
After Recieve data jump to Main Thread so it will be thread safe for UI Activities < Just like DisatchQueue.main.async>
.mapError i
s used to map error of custom type with closure in our case it’s enum WebServiceError
.eraseToAnyPublisher
is used to expose an instance of AnyPublisher to the downstream subscriber, rather than this publisher’s actual type So here we are mapping to [GithubEntity], WebServiceError
ContentView:
where we are invoking network call –
Here we will use .sink subscriber which listen values from the Publisher AnyPublisher<[GithubEntity], WebServiceError>
On .sink(..
we have two parameters one is completion
& another is receive value which is closure type
struct ContentView: View {
@State var apiDataSubscriber: Cancellable? = nil
func hitWebService() {
apiDataSubscriber = CombileNetworkHelper.fetchFromWebService().sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(let error):
print(error.localizedDescription)
}
}, receiveValue: { response in
print("\n Response Recieved-->", response)
})
}
var body: some View {
Button(
action: { hitWebService() },
label: { Text("Click Me").font(.subheadline) }
)
}
}
Now launch the application & click the button the response will get printed. 🙂
The Xcode project file can be download from here
Hope the article was easy to understand.. Thanks.
That’s all 🙂