Swift - Reusable URL Request with Delegates - swift

Hi I'm new to Swift and I am trying to create a reusable generic Download Manager for URL Request that can be reused throughout my project in different View Controllers or reused within the same VC for a different URL Request calls. The problem that I have is how do I pass the Data Type from the Request into the Download Manager and then return the Downloaded Data back to the VC with the corresponding Data Type. I am able to pass the Data Type in a call to downloadRequest but I can't figure out how to pass the Data Type back to the VC via a delegate DownloadManagerDelegate. Any help would be greatly appreciate it!
Generic Download Manager:
protocol DownloadManagerDelegate {
func didUpdateData<T: Codable>(modelType: T.Type, downloadedData: T.Type)
}
struct DownloadManager {
var delegate: DownloadManagerDelegate?
func downloadRequest<T: Codable>(modelType: T.Type, parameters: [String: Any]) {
guard let url = URL(string: "https://www.someAPI...") else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
guard let httpBodyWithParameters = try? JSONSerialization.data(withJSONObject: parameters, options: []) else
{
print("error")
return
}
request.httpBody = httpBodyWithParameters
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if error != nil {
print("error")
return
}
if let safeData = data {
if let downloadedData = parseDownloadedData(data: safeData) {
self.delegate?.didUpdateData(modelType: modelType, downloadedData: downloadedData)
}
}
}.resume()
func parseDownloadedData(data: Data) -> T?{
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(T.self, from: data)
return decodedData
} catch {
print(error)
return nil
}
}
}
Delegate in my VC:
override func viewDidLoad() {
super.viewDidLoad()
downloadManager.delegate = self
}
func didUpdateData(modelType: modelType,downloadedData:downloadedData){
DispatchQueue.main.async {
print(downloadedData)
}
}
To call download downloadRequest:
downloadManager.downloadrequest(modeType: Type1.self, parameters: parameters)
The Data Model is defined as a struct:
struct DataModel1: Codable {
let ItemID: String
}
Then in the same VC I call the same function downloadManager that will call a different API which should return data for a different Model Type (defined as Struct)
downloadManager.downloadRequest(modeType: Type2.self, parameters: parameters)
The Data Model is defined as a struct:
struct DataModel2: Codable {
let EmployeeeID: String
}

In the Swift times Protocol/Delegate smells a bit objective-c-ish.
I recommend a completion handler with the versatile Result type.
It returns the generic type non-optional on success and any error on failure.
The force unwrapping of data is safe because if error is nil then data has a value
struct DownloadManager {
func downloadRequest<T: Decodable>(modelType: T.Type, parameters: [String: Any], completion : #escaping (Result<T, Error>) -> Void) {
guard let url = URL(string: "https://www.someAPI...") else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
let httpBodyWithParameters = try JSONSerialization.data(withJSONObject: parameters)
request.httpBody = httpBodyWithParameters
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let error = error {
completion(.failure(error))
} else {
completion( Result { try JSONDecoder().decode(T.self, from: data!)})
}
}.resume()
} catch {
completion(.failure(error))
}
}
}
And use it
downloadManager.downloadrequest(modeType: Type1.self, parameters: parameters) { result in
switch result {
case .success(let data): print(data)
case .failure(let error): print(error)
}
}

Related

Mutating Struct property with asynchronous function

I have the following Struct that I want to initialize, and then use its method query() to mutate its result property.
Query() sends and fetches JSON data, then decodes it to a String. When I declare query() as a mutating function, I receive the error "Escaping closure captures mutating 'self' parameter" in my URLSession.
What do I need to change?
The call:
var translation = Translate(string: "hello", base: "en", target: "de", result: "")
translation.query()
let translated = translation.result
The struct:
struct Translate {
let string: String, base: String, target: String
var result: String
mutating func query() {
let body: [String: String] = ["q": self.string, "source": self.base, "target": self.target]
let bodyData = try? JSONSerialization.data(withJSONObject: body)
guard let url = URL(string: "https://libretranslate.com/translate") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = bodyData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
DispatchQueue.main.async {
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
if responseJSON["translatedText"] != nil {
self.result = responseJSON["translatedText"] as! String
}
}
}
return
}
.resume()
}
}
Xcode error:
There are many issues in the code.
The most significant issue is that the URLRequest is asynchronous. Even if no error occurred result will be always empty.
You have to add a completion handler – it fixes the errors you got by the way – and it's highly recommended to handle all errors.
Instead of JSONSerialization the code uses JSONDe/Encoder
struct Translation : Decodable { let translatedText : String }
struct Translate {
let string: String, base: String, target: String
func query(completion: #escaping (Result<String,Error>) -> Void) {
let body: [String: String] = ["q": self.string, "source": self.base, "target": self.target]
do {
let bodyData = try JSONEncoder().encode(body)
let url = URL(string: "https://libretranslate.com/translate")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = bodyData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(Translation.self, from: data!).translatedText} )
}
.resume()
} catch {
completion(.failure(error))
}
}
}
let translation = Translate(string: "hello", base: "en", target: "de")
translation.query() { result in
DispatchQueue.main.async {
switch result {
case .success(let translated): print(translated)
case .failure(let error): print(error)
}
}
}
Both exclamation marks (!) are safe.

Swift Template Function

I am wondering how I can potentially remove some duplicate methods in Swift 5.
func CreateProjectPossibleAccount(possibleAccount: ProjectPossibleAccount) -> Data {
var dataToReturn: Data?
self.dispatchGroup.enter()
do {
var request = getUrlRequestFor(RequestType.post)
request.httpBody = try JSONEncoder().encode(possibleAccount)
URLSession.shared.dataTask(with: request) {
(data, response, err) in
guard let data = data else {return}
dataToReturn = data
self.dispatchGroup.leave()
}.resume()
} catch {
print(error.localizedDescription)
}
self.dispatchGroup.wait()
return dataToReturn!
}
func CreateMaterialIssueItem(item: NewMaterialIssueItem) -> Data {
var dataToReturn: Data?
self.dispatchGroup.enter()
do {
var request = getUrlRequestFor(RequestType.post)
request.httpBody = try JSONEncoder().encode(item)
URLSession.shared.dataTask(with: request) {
(data, response, err) in
guard let data = data else {return}
dataToReturn = data
self.dispatchGroup.leave()
}.resume()
} catch {
print(error.localizedDescription)
}
self.dispatchGroup.wait()
return dataToReturn!
}
I have several functions like this where the only real difference is the custom type that I create. Is there a way to just name this function Post(item: T) and pass in all the unique types?
You can use generics to avoid code duplication. In the following example I'm assuming that ProjectPossibleAccount and NewMaterialIssueItem implement the Decodable protocol and also that RequestType is a enum. The dispatchGroup could also become a param of the method.
func request<T: Encodable>(item: T, requestType: RequestType) -> Data {
var dataToReturn: Data?
self.dispatchGroup.enter()
do {
var request = getUrlRequestFor(requestType)
request.httpBody = try JSONEncoder().encode(item)
URLSession.shared.dataTask(with: request) {
(data, response, err) in
guard let data = data else {return}
dataToReturn = data
self.dispatchGroup.leave()
}.resume()
} catch {
print(error.localizedDescription)
}
self.dispatchGroup.wait()
return dataToReturn!
}
and you can call the method like this:
let possibleAccount: ProjectPossibleAccount = ProjectPossibleAccount()
let data = request(item: possibleAccount, requestType: .post)
Another approach for the previous example ( and because it is a simple one) is simply specifying the item param data type as a Encodable, like this:
func request(item: Encodable, requestType: RequestType) -> Data

Generic parameter 'T' could not be inferred: in Common Webservice method

I am trying to make generic post method for API call.In my loadNew method I want to add normal dictionary inside resource object.Resource contains normal data which will pass from controller class.And dictionary is passed as body of request. but while encoding "Generic parameter 'T' could not be inferred" showing. How do I use dictionary in it?
struct Resource<T> {
let url: URL
let request: URLRequest
let dictionary : [String:Any]
let parse: (Data) -> T?
}
final class Webservice {
// MARK:- Generic
func load<T>(resource: Resource<T>, completion: #escaping (T?) -> ()) {
URLSession.shared.dataTask(with: resource.url) { data, response, error in
if let data = data {
//completion call should happen in main thread
DispatchQueue.main.async {
completion(resource.parse(data))
}
} else {
completion(nil)
}
}.resume()
}
func loadNew<T>(resource: Resource<T>, completion: #escaping (T?) -> ()) {
var request = resource.request
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
//FIXIT: error is getting here
let jsonBody = try JSONEncoder().encode(resource.dictionary)
request.httpBody = jsonBody
}catch{}
let session = URLSession.shared
session.dataTask(with: request) { data, response, error in
if let data = data {
//completion call should happen in main thread
DispatchQueue.main.async {
completion(resource.parse(data))
}
} else {
completion(nil)
}
}.resume()
}
}
This method is called inside my Login controller.I have also tried assign it directly to request object but same error is showing
func APICall(){
guard let url = URL(string: Constants.HostName.local + Constants.API.User_Login) else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let resources = Resource<LoginReponse>(url: url, request: request, dictionary: dict){
data in
let loginModel = try? JSONDecoder().decode(LoginReponse.self, from: data)
return loginModel
}
// var response = LoginReponse()
Webservice().loadNew(resource: resources) {
result in
if let model = result {
print(model)
}
}
}
The error is a bit misleading, and may indicate you're using an older version of Xcode. In 11.4.1, the error is much more explicit:
error: value of protocol type 'Any' cannot conform to 'Encodable'; only struct/enum/class types can conform to protocols
The problem is that [String: Any] is not Encodable, because there's no way to encode "Any" (what should happen if you passed a UIViewController here? Or a CBPeripheral?)
Instead of a dictionary here, looking at your code I would expect you to pass an encodable object here. For example:
struct Resource<Value: Decodable, Parameters: Encodable> {
let url: URL
let request: URLRequest
let parameters : Parameters?
let parse: (Data) -> Value?
}
final class Webservice {
func loadNew<Value, Parameters>(resource: Resource<Value, Parameters>, completion: #escaping (Value?) -> ()) {
var request = resource.request
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
if let parameters = resource.parameters {
request.httpBody = try? JSONEncoder().encode(parameters)
}
// ...
}
That said, I'd probably turn this system around a bit. If you want to have a Request<T> (parameterized on the thing it returns, and not on the parameters it takes to generate it), that's fine. You can pack a bit more into the struct. For example:
let baseURL = URL(string: "https://example.com/api/")!
struct Resource<Value> {
let urlRequest: URLRequest
let parse: (Data) -> Result<Value, Error>
// Things you want as default for every request
static func makeStandardURLRequest(url: URL) -> URLRequest {
var request = URLRequest(url: url)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
return request
}
}
// It would be nice to have a default parser when you can, but you don't have to put that
// into Webservice. The Resource can handle it.
extension Resource where Value: Decodable {
init(urlRequest: URLRequest) {
self.init(urlRequest: urlRequest, parse: { data in
Result { try JSONDecoder().decode(Value.self, from: data) }
})
}
}
And then Resources are smart about themselves:
struct LoginParameters: Encodable {
let username: String
let password: String
}
struct LoginResult: Decodable {
let authToken: String
}
extension Resource where Value == LoginResult {
static func login(parameters: LoginParameters) -> Resource {
var urlRequest = makeStandardURLRequest(url: baseURL.appendingPathComponent("login"))
urlRequest.httpBody = try? JSONEncoder().encode(parameters)
return Resource(urlRequest: urlRequest)
}
}
Of course that may get repeated a lot, so you can hoist it out:
extension Resource where Value: Decodable {
static func makeStandardURLRequest<Parameters>(endpoint: String, parameters: Parameters) -> URLRequest
where Parameters: Encodable {
var urlRequest = makeStandardURLRequest(url: baseURL.appendingPathComponent(endpoint))
urlRequest.httpBody = try? JSONEncoder().encode(parameters)
return Resource(urlRequest: urlRequest)
}
}
And then Login looks like:
extension Resource where Value == LoginResult {
static func login(parameters: LoginParameters) -> Resource {
return makeStandardURLRequest(endpoint: "login", parameters: parameters)
}
}
The point is that you can pull duplicated code into extensions; you don't need to stick it in the Webservice, or add more generic.
With that, your load gets a bit simpler and much more flexible. It focuses just on the networking part. That means that it's easier to swap out with something else (like something for unit tests) without having to mock out a bunch of functionality.
func load<Value>(request: Resource<Value>, completion: #escaping (Result<Value, Error>) -> ()) {
let session = URLSession.shared
session.dataTask(with: request.urlRequest) { data, response, error in
DispatchQueue.main.async {
if let data = data {
//completion call should happen in main thread
completion(request.parse(data))
} else if let error = error {
completion(.failure(error))
} else {
fatalError("This really should be impossible, but you can construct an 'unexpected error' here.")
}
}
}.resume()
}
There's a lots of ways to do this; for another, see this AltConf talk.

How to execute a synchronous api call after an asynchronous api call

I have two services that are working perfectly independently one is a synchronous call to get shopping-lists and another is an asynchronous call to add shopping-lists. The problem comes when i try to get a shopping-lists just after the add-Shopping-lists call has successfully completed.
The function to get shopping-lists never returns it just hangs after i call it in the closure of the add-Shopping-lists function. What is the best way to make these two calls without promises.
Create ShoppingList
func createURLRequest(with endpoint: String, data: ShoppingList? = nil, httpMethod method: String) -> URLRequest {
guard let accessToken = UserSessionInfo.accessToken else {
fatalError("Nil access token")
}
let urlString = endpoint.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
guard let requestUrl = URLComponents(string: urlString!)?.url else {
fatalError("Nil url")
}
var request = URLRequest(url:requestUrl)
request.httpMethod = method
request.httpBody = try! data?.jsonString()?.data(using: .utf8)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
return request
}
func createShoppingList(with shoppingList: ShoppingList, completion: #escaping (Bool, Error?) -> Void) {
let serviceURL = environment + Endpoint.createList.rawValue
let request = createURLRequest(with: serviceURL, data: shoppingList, httpMethod: HttpBody.post.rawValue)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
guard let _ = data,
let response = response as? HTTPURLResponse,
(200 ..< 300) ~= response.statusCode,
error == nil else {
completion(false, error)
return
}
completion(true, nil)
})
task.resume()
}
Get shoppingLists
func fetchShoppingLists(with customerId: String) throws -> [ShoppingList]? {
var serviceResponse: [ShoppingList]?
var serviceError: Error?
let serviceURL = environment + Endpoint.getLists.rawValue + customerId
let request = createURLRequest(with: serviceURL, httpMethod: HttpBody.get.rawValue)
let semaphore = DispatchSemaphore(value: 0)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
defer { semaphore.signal() }
guard let data = data, // is there data
let response = response as? HTTPURLResponse, // is there HTTP response
(200 ..< 300) ~= response.statusCode, // is statusCode 2XX
error == nil else { // was there no error, otherwise ...
serviceError = error
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let shoppingList = try decoder.decode([ShoppingList].self, from: data)
serviceResponse = shoppingList
} catch let error {
serviceError = error
}
})
task.resume()
semaphore.wait()
if let error = serviceError {
throw error
}
return serviceResponse
}
Usage of function
func addShoppingList(customerId: String, shoppingList: ShoppingList, completion: #escaping (Bool, Error?) -> Void) {
shoppingListService.createShoppingList(with: shoppingList, completion: { (success, error) in
if success {
self.shoppingListCache.clearCache()
let serviceResponse = try? self.fetchShoppingLists(with: customerId)
if let _ = serviceResponse {
completion(true, nil)
} else {
let fetchListError = NSError().error(description: "Unable to fetch shoppingLists")
completion(false, fetchListError)
}
} else {
completion(false, error)
}
})
}
I would like to call the fetchShoppingLists which is a synchronous call and get new data then call the completion block with success.
This question is predicated on a flawed assumption, that you need this synchronous request.
You suggested that you needed this for testing. This is not true: One uses “expectations” to test asynchronous processes; we don’t suboptimize code for testing purposes.
You also suggested that you want to “stop all processes” until the request is done. Again, this is not true and offers horrible UX and subjects your app to possibly be killed by watchdog process if you do this at the wrong time while on slow network. If, in fact, the UI needs to be blocked while the request is in progress, we usually just throw up a UIActivityIndicatorView (a.k.a. a “spinner”), perhaps on top of a dimming/blurring view over the whole UI to prevent users from interacting with the visible controls, if any.
But, bottom line, I know that synchronous requests feel so intuitive and logical, but it’s invariably the wrong approach.
Anyway, I’d make fetchShoppingLists asynchronous:
func fetchShoppingLists(with customerId: String, completion: #escaping (Result<[ShoppingList], Error>) -> Void) {
var serviceResponse: [ShoppingList]?
let serviceURL = environment + Endpoint.getLists.rawValue + customerId
let request = createURLRequest(with: serviceURL, httpMethod: .get)
let session = URLSession.shared
let task = session.dataTask(with: request) { data, response, error in
guard let data = data, // is there data
let response = response as? HTTPURLResponse, // is there HTTP response
200 ..< 300 ~= response.statusCode, // is statusCode 2XX
error == nil else { // was there no error, otherwise ...
completion(.failure(error ?? ShoppingError.unknownError))
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let shoppingList = try decoder.decode([ShoppingList].self, from: data)
completion(.success(shoppingList))
} catch let jsonError {
completion(.failure(jsonError))
}
}
task.resume()
}
And then you just adopt this asynchronous pattern. Note, while I’d use the Result pattern for my completion handler, I left yours as it was to minimize integration issues:
func addShoppingList(customerId: String, shoppingList: ShoppingList, completion: #escaping (Bool, Error?) -> Void) {
shoppingListService.createShoppingList(with: shoppingList) { success, error in
if success {
self.shoppingListCache.clearCache()
self.fetchShoppingLists(with: customerId) { result in
switch result {
case .failure(let error):
completion(false, error)
case .success:
completion(true, nil)
}
}
} else {
completion(false, error)
}
}
}
Now, for example, you suggested you wanted to make fetchShoppingLists synchronous to facilitate testing. You can easily test asynchronous methods with “expectations”:
class MyAppTests: XCTestCase {
func testFetch() {
let exp = expectation(description: "Fetching ShoppingLists")
let customerId = ...
fetchShoppingLists(with: customerId) { result in
if case .failure(_) = result {
XCTFail("Fetch failed")
}
exp.fulfill()
}
waitForExpectations(timeout: 10)
}
}
FWIW, it’s debatable that you should be unit testing the server request/response at all. Often instead mock the network service, or use URLProtocol to mock it behind the scenes.
For more information about asynchronous tests, see Asynchronous Tests and Expectations.
FYI, the above uses a refactored createURLRequest, that uses the enumeration for that last parameter, not a String. The whole idea of enumerations is to make it impossible to pass invalid parameters, so let’s do the rawValue conversion here, rather than in the calling point:
enum HttpMethod: String {
case post = "POST"
case get = "GET"
}
func createURLRequest(with endpoint: String, data: ShoppingList? = nil, httpMethod method: HttpMethod) -> URLRequest {
guard let accessToken = UserSessionInfo.accessToken else {
fatalError("Nil access token")
}
guard
let urlString = endpoint.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let requestUrl = URLComponents(string: urlString)?.url
else {
fatalError("Nil url")
}
var request = URLRequest(url: requestUrl)
request.httpMethod = method.rawValue
request.httpBody = try! data?.jsonString()?.data(using: .utf8)
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
return request
}
I am sure it could be alot better, but this is my 5 minute version.
import Foundation
import UIKit
struct Todo: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
enum TodoError: String, Error {
case networkError
case invalidUrl
case noData
case other
case serializationError
}
class TodoRequest {
let todoUrl = URL(string: "https://jsonplaceholder.typicode.com/todos")
var todos: [Todo] = []
var responseError: TodoError?
func loadTodos() {
var responseData: Data?
guard let url = todoUrl else { return }
let group = DispatchGroup()
let task = URLSession.shared.dataTask(with: url) { [weak self](data, response, error) in
responseData = data
self?.responseError = error != nil ? .noData : nil
group.leave()
}
group.enter()
task.resume()
group.wait()
guard responseError == nil else { return }
guard let data = responseData else { return }
do {
todos = try JSONDecoder().decode([Todo].self, from: data)
} catch {
responseError = .serializationError
}
}
func retrieveTodo(with id: Int, completion: #escaping (_ todo: Todo? , _ error: TodoError?) -> Void) {
guard var url = todoUrl else { return }
url.appendPathComponent("\(id)")
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let todoData = data else { return completion(nil, .noData) }
do {
let todo = try JSONDecoder().decode(Todo.self, from: todoData)
completion(todo, nil)
} catch {
completion(nil, .serializationError)
}
}
task.resume()
}
}
class TodoViewController: UIViewController {
let request = TodoRequest()
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global(qos: .background).async { [weak self] in
self?.request.loadTodos()
self?.request.retrieveTodo(with: 1, completion: { [weak self](todoData, error) in
guard let strongSelf = self else { return }
if let todoError = error {
return debugPrint(todoError.localizedDescription)
}
guard let todo = todoData else {
return debugPrint("No todo")
}
debugPrint(strongSelf.request.todos)
debugPrint(todo)
})
}
}
}

Swift "expected to decode Array<Any> but found a dictionary instead."

I am using this code to fetch data from an API and setting some variables in my view controller and everything works fine. Since I am doing this in multiple controllers I am attempting to refactor the network call into a Service class using a completion handler to execute the necessary code for settings the view's variables. The service works fine on some of the controllers, but any controller that attempts to decode an array of objects, I get the error
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
Working
let httpURL = "https://myurl.com/api"
guard let url = URL(string: httpURL) else { return }
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// Setting other headers here
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
do {
let consults = try JSONDecoder().decode([Consultation].self, from:data)
DispatchQueue.main.async {
// Settings values here
self.consultationTableView.reloadData()
}
} catch let jsonErr {
print(jsonErr)
}
}
task.resume()
}
Not Working
struct Service {
static let sharedInstance = Service()
func fetchPage(url: String, completion: #escaping (Data) -> ()){
let httpURL = "https://myurl.com/api"
guard let url = URL(string: httpURL) else { return }
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// Other headers here
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
completion(data)
}
task.resume()
}
}
ViewController
let httpURL = "https://myUrl.com/api"
Service.sharedInstance.fetchPage(url: httpURL) { (data) in
do {
let consults = try
---> Error Happens>> JSONDecoder().decode([Consultation].self, from:data) }
} catch let jsonErr {
print(jsonErr)
}
}
Any idea why when using the service class, the error shows up?