Sending data in Swift using Combine
Today we are going to show you how to send data in Swift using Combine. But first what is Combine?
Combine is a framework that helps us write functional reactive code. We don't want to end up in a situation where half of our networking calls are written using Combine and the other half aren't.
Since I got to work more with Combine and realized there weren't many blog posts on how to send POST requests using Combine, so I decided to write a short blog post to help if you might need it. Most of the blogposts I saw out there were revolving around getting a simple request and that is a good start but we will definitely need to send some data to a server as well so I decided to write a blog post on how to do just that.
Continue reading if you what to find a straightforward approach on how to send data to your server by using Combine and Codable structs.
In order to be able to trigger an action, we will need a publisher to emit the element we want to send to the server and we want to do it in a functional manner.
Let's build the following example:
1 /// Enum of API Errors
2 enum APIError: Error {
3 /// Encoding issue when trying to send data.
4 case encodingError(String?)
5 /// No data recieved from the server.
6 case noData
7 /// The server response was invalid (unexpected format).
8 case invalidResponse
9 /// The request was rejected: 400-499
10 case badRequest(String?)
11 /// Encountered a server error.
12 case serverError(String?)
13 /// There was an error parsing the data.
14 case parseError(String?)
15 /// Unknown error.
16 case unknown
17 }
18
19 func post(tweet: Tweet) -> AnyPublisher {
20 return Just(tweet) // 1.
21 .encode(encoder: JSONEncoder()) // 2.
22 .mapError { error -> APIError in
23 // 3.
24 return APIError.encodingError(error.localizedDescription)
25 }
26 .tryMap { jsonData -> [String: Any] in
27 // 4.
28 do {
29 let json = try JSONSerialization.jsonObject(with: jsonData, options: [])
30 guard let jsonDict = json as? [String: Any] else {
31 throw APIError.encodingError("Invalid object")
32 }
33 return jsonDict
34 } catch {
35 throw APIError.encodingError(error.localizedDescription)
36 }
37 }
38 .map { jsonDict -> URLRequest in
39 // 5.
40 let request = TimelineEndpoint.create(parameters: jsonDict).urlRequest
41 return request
42 }
43 .flatMap { request in
44 // 6.
45 return client.perform(request)
46 .map(\.value)
47 }
48 .eraseToAnyPublisher() // 7.
49 }
This is it! We now have a bridge method between our imperative code and our functional one.
It's time to add this to our previously created TwitterAPI:
1 struct TwitterAPI {
2 let client: HTTPClient
3
4 func getTweetsFor(screenName: String) -> AnyPublisher {
5 let request = TimelineEndpoint.getFor(screenName: screenName).urlRequest
6 return client.perform(request)
7 .map(\.value)
8 .eraseToAnyPublisher()
9 }
10
11 func post(tweet: Tweet) -> AnyPublisher {
12 return Just(tweet)
13 .encode(encoder: JSONEncoder())
14 .mapError { error -> APIError in
15 return APIError.encodingError(error.localizedDescription)
16 }
17 .tryMap { jsonData -> [String: Any] in
18 do {
19 let json = try JSONSerialization.jsonObject(with: jsonData, options: [])
20 guard let jsonDict = json as? [String: Any] else {
21 throw APIError.encodingError("Invalid object")
22 }
23 return jsonDict
24 } catch {
25 throw APIError.encodingError(error.localizedDescription)
26 }
27 }
28 .map { jsonDict -> URLRequest in
29 let request = TimelineEndpoint.create(parameters: jsonDict).urlRequest
30 return request
31 }
32 .flatMap { request in
33 return client.perform(request)
34 .map(\.value)
35 }
36 .eraseToAnyPublisher()
37 }
38 }
So this is how you send POST requests using Combine, hope you find it useful and instructing.
More articles from Ciprian Redinciuc can be found on Userdesk or on the OceanoBe blog .
Other articles that might interest you: CI/CD for iOS projects using Semaphore CI or Repository pattern using Core Data and Swift.