Call completion Handler function using "Result" - swift

How to use call getEmployee function while using "Result"?
struct Employee {
let name: String
let designation: String
}
func getEmployee(name: String, completion: #escaping(Result<Employee?, Error>) -> Void) {
}

First you need to make your structure conform to Codable
struct Employee: Codable {
let name, designation: String
}
Than you need to fetch your data from your server and call completion if decoding was successful with your employee .success(employee) or if it fails pass failure with your error .failure(error):
func getEmployee(name: String, completion: #escaping(Result<Employee, Error>) -> Void) {
URLSession.shared.dataTask(with: URL(string: "http://www.example.com/getEmployee.api?name=\(name)")!) { data, response, error in
guard let data = data, error == nil else {
if let error = error { completion(.failure(error)) }
return
}
do {
let employee = try JSONDecoder().decode(Employee.self, from: data)
completion(.success(employee))
} catch {
completion(.failure(error))
}
}
}
Usage:
getEmployee(name: "Ella") { result in
switch result {
case let .success(employee):
print("employee:", employee)
case let .failure(error):
print("error:", error)
default: break
}
}

Related

Swift Playground "Expressions are not allowed at the top level"

I know it is impossible to call function from here in real app project. But in playground it was possible. What can I do now?
You can try using class
like this:
struct Dummy: Decodable {
let userId: Int
let id: Int
let title: String?
let body: String?
}
final class APIHandler{
static let shared = APIHandler()
private init () {}
func get<T: Decodable>(_ type: T.Type, completion:#escaping (Result<T,Error>)->Void) {
guard let url = URL(string:"https://jsonplaceholder.typicode.com/posts" ) else {return}
let task = URLSession.shared
task.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
}
if let data = data {
do {
let json = try JSONDecoder().decode(T.self, from: data)
completion(.success(json))
} catch let error {
print("Error with Data : \(error)")
completion(.failure(error))
}
}
}.resume()
}
}
and test it:
APIHandler.shared.get([Dummy].self) { result in
switch result {
case .success(let res):
print("response: ", res)
case .failure(let error):
print(error)
}
}

Call function with completion handler

I created the function below but I'm not sure how to call it, it complains saying:
Type 'T.Type' cannot conform to 'Decodable'
Here's how I'd like to call it:
let result = getApiData(modelToDecode: MyModel, url: "abc")
This is what I've tried:
func getApiData<T : Decodable>(modelToDecode: T.Type, url: String) -> Any? {
// I get an error below
fetchDataAndDecode(url: String, modelToDecode: T.Type) { result in
}
// temp placeholder
return nil
}
func fetchDataAndDecode<T : Decodable>(url: String, modelToDecode: T.Type, completionHandler: #escaping (Result<T.Type, NetworkError>) -> Void) {
guard let url = URL(string: url) else {
completionHandler(.failure(NetworkError.badURL))
return
}
AF.request(url, method: .get).validate().responseData { response in
guard let data = response.data else {
completionHandler(.failure(NetworkError.apiFailed))
return
}
do {
// Decode the data
let decodedData = try JSONDecoder().decode(modelToDecode.self, from: data)
DispatchQueue.main.async {
completionHandler(.success(decodedData as! T.Type))
}
} catch(let error) {
print("🛑 Error on afRequest(): \(error)")
}
}
}
How can I call it inside the class properly?
Result should be Result<T, NetworkError>. No need to add modelToDecode to your method declaration. You can explicitly set the resulting type in your async call. Btw you should the completion handler as well if you fail to decode your data:
enum NetworkError: Error {
case badURL, apiFailed, corruptedData
}
Your method should look like this:
func fetchDataAndDecode<T: Decodable>(url: String, completionHandler: #escaping (Result<T, NetworkError>) -> Void) {
guard let url = URL(string: url) else {
completionHandler(.failure(.badURL))
return
}
AF.request(url, method: .get).validate().responseData { response in
guard let data = response.data else {
completionHandler(.failure(.apiFailed))
return
}
do {
let decodedData = try JSONDecoder().decode(T.self, from: data)
DispatchQueue.main.async {
completionHandler(.success(decodedData))
}
} catch {
completionHandler(.failure(.corruptedData))
}
}
}
And when calling it you need to explicitly set the resulting type:
fetchDataAndDecode(url: "yourURL") { (result: Result<WhatEver, NetworkError>) in
// switch the result here
}

MVC Networking Swift

I have this Networking class that i declared in the Model .
class Networking {
func response (url : String ) {
guard let url = URL(string: url) else {return}
URLSession.shared.dataTask(with: url, completionHandler: urlPathCompletionHandler(data:response:error:)).resume()
}
func urlPathCompletionHandler (data : Data? , response: URLResponse? , error: Error? ) {
guard let data = data else {return }
do {
let jsondecoder = JSONDecoder()
}catch {
print("Error \(error)")
}
}
}
In the controller . I have an array of users i declared and i want the controller to call from the Model Networking class instead of doing the networking inside the controller. This is part of my controller:
var users = [Users]()
var networking : Networking()
#IBOutlet weak var tableview : UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
}
func getFromModel() {
var vm = networking.response()
}
I want a way of calling the networking class and return an array of users that i can set to the users array above and use it to populate the table view . If i wanted to do that inside the controller it would easy but i am not sure how i can return an array of users from the Model Networking class .
You need to modify your Network class like this:
class Networking {
func response<T: Codable>(url: String, completion: ((T) -> Void)?) {
guard let url = URL(string: url) else {return}
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
self.urlPathCompletionHandler(data: data, response: response, error: error, completion: completion)
}).resume()
}
func urlPathCompletionHandler<T: Codable>(data : Data? , response: URLResponse? , error: Error?, completion: ((T) -> Void)?) {
guard let data = data else { return }
do {
let jsondecoder = JSONDecoder()
// Pseudo Code to decode users
completion?(decodedObject)
} catch {
print("Error \(error)")
}
}
}
And call it like this:
func getFromModel() {
networking.response(url: <#T##String#>) { (users: [User]) in
self.users = users
}
}
OK, there are a few thoughts:
Your response method is performing an asynchronous network request, so you need to give it a completion handler parameter. So, I might suggest something like:
class Networking {
enum NetworkingError: Error {
case invalidURL
case failed(Data?, URLResponse?)
}
private let parsingQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".parsing")
// response method to handle network stuff
func responseData(_ string: String, completion: #escaping (Result<Data, Error>) -> Void) {
guard let url = URL(string: string) else {
completion(.failure(NetworkingError.invalidURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
DispatchQueue.main.async {
if let error = error {
completion(.failure(error))
return
}
guard
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
completion(.failure(NetworkingError.failed(data, response)))
return
}
completion(.success(responseData))
}
}.resume()
}
// response method to handle the JSON parsing
func response<T: Decodable>(of type: T.Type, from string: String, completion: #escaping (Result<T, Error>) -> Void) {
responseData(string) { result in
switch result {
case .failure(let error):
completion(.failure(error))
case .success(let data):
self.parsingQueue.async {
do {
let responseObject = try JSONDecoder().decode(T.self, from: data)
DispatchQueue.main.async {
completion(.success(responseObject))
}
} catch let parseError {
DispatchQueue.main.async {
completion(.failure(parseError))
}
}
}
}
}
}
}
This obviously assumes that you have some Codable types. For example, it’s common for an API to have some common structure in its responses:
struct ResponseObject<T: Decodable>: Decodable {
let code: Int
let message: String?
let data: T?
}
And maybe the User is like so:
struct User: Decodable {
let id: String
let name: String
}
Then getFromModel (perhaps better called getFromRepository or something like that) could parse it with:
networking.response(of: ResponseObject<[User]>.self, from: urlString) { result in
switch result {
case .failure(let error):
print(error)
case .success(let responseObject):
let users = responseObject.data
// do something with users
}
}
For what it’s worth, if you didn’t want to write your own networking code, you could use Alamofire, and then getFromModel would do:
AF.request(urlString).responseDecodable(of: ResponseObject<[User]>.self) { response in
switch response.result {
case .failure(let error):
print(error)
case .success(let responseObject):
let users = responseObject.data
}
}
Now, clearly the model types are likely to be different in your example, but you didn’t share what your JSON looked like, so I had to guess, but hopefully the above illustrates the general idea. Make a generic-based network API and give it a completion handler for its asynchronous responses.

How can I return all the response from API to my Swift app

I'am learning swift and I see an example here https://matteomanferdini.com/network-requests-rest-apis-ios-swift/ and Im trying to change the code for something that work for me.
this is how the original code looks
struct Wrapper<T: Decodable>: Decodable {
let items: [T]?
}
protocol NetworkRequest: AnyObject {
associatedtype ModelType
func decode(_ data: Data) -> ModelType?
func load(withCompletion completion: #escaping (ModelType?) -> Void)
}
extension NetworkRequest {
fileprivate func load(_ url: URLRequest, withCompletion completion: #escaping (ModelType?) -> Void) {
let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
let task = session.dataTask(with: url, completionHandler: { [weak self] (data: Data?, response: URLResponse?, error: Error?) -> Void in
if let error = error {
print("Error: \(error)")
}
guard let data = data else {
completion(nil)
return
}
completion(self?.decode(data))
})
task.resume()
}
}
class APIRequest<Resource: APIResource> {
let resource: Resource
init(resource: Resource) {
self.resource = resource
}
}
extension APIRequest: NetworkRequest {
func decode(_ data: Data) -> [Resource.ModelType]? {
let wrapper = try? JSONDecoder().decode(Wrapper<Resource.ModelType>.self, from: data)
return wrapper?.items
}
func load(withCompletion completion: #escaping ([Resource.ModelType]?) -> Void) {
load(resource.request, withCompletion: completion)
}
}
but what I need to change the structure Wrapper to
struct Wrapper<T: Decodable>: Decodable {
let items: [T]?
let response: Bool?
let message: String?
}
and return items, response and message not only items
In this case you don't need the protocol at all because you want to get the root object.
This is sufficient
struct Wrapper<T: Decodable>: Decodable {
let items: [T]
let response: Bool
let message: String
}
class NetworkRequest {
func load<T : Decodable>(_ request: URLRequest, withCompletion completion: #escaping (Result<Wrapper<T>,Error>) -> Void) {
let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
let task = session.dataTask(with: request) { data, _, error in
if let error = error {
completion(.failure(error))
} else {
completion( Result {try JSONDecoder().decode(Wrapper<T>.self, from: data!)})
}
}
task.resume()
}
}
The completion handler returns a Result object, on success the wrapper object and on failure all errors.
In the wrapper struct declare all properties non-optional to get error messages and change only those to optional which really can be nil.
I change the code like this
class NetworkRequest<Resource: APIResource> {
let resource: Resource
init(resource: Resource) {
self.resource = resource
}
func load(withCompletion completion: #escaping (Result<Wrapper<Resource.ModelType>,Error>) -> Void) {
let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
let task = session.dataTask(with: self.resource.request) { data, _, error in
if let error = error {
completion(.failure(error))
} else {
completion( Result {try JSONDecoder().decode(Wrapper<Resource.ModelType>.self, from: data!)})
}
}
task.resume()
}
}
struct LoginResource: APIResource {
typealias ModelType = Token
let methodPath = "/users/login/"
let method = "post"
var params: [String: Any]?
init(username: String, password: String) {
self.params = ["username":username, "password": password]
}
}
In my view:
func login() {
if user == "" || password == "" {
self.title_alert = "Info"
message_alert = "Test Alert"
show_alert = true
return
}
let loginRequest = NetworkRequest(resource: LoginResource(username:user,password:password))
loginRequest.load { result in
switch result {
case .failure(let error):
print(error)
case .success(let data):
print(data)
}
}
}
I don't know if this is the best way but works Thank you #vadian

Using decoded data from an API into an algorithm

I successfully fetched and decoded data from an API and now have access to all the data I need to be used in the algorithm I want to write in my App.
The issue is that I don't know how to access this data after I decoded it, I can print it immediately after it's decoded but I have no idea how to use it in another function or place in my app.
Here is my Playground:
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError : Error {
case FoundNil(String)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
print(marketData.data[0].open)
print(marketData.data[1].open)
print("Average=", (marketData.data[0].open + marketData.data[1].open) / 2)
//completion(marketData, nil)
throw MyError.FoundNil("data")
}
} catch {
print(error)
}
}
task.resume()
}
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
How can I use .data[0], .data[1], ..., somewhere else?
You data will be available in your fecthData() call. Probably what you want is your items variable, where you're printing it. But make sure to call the completion in your fetchData implementation.
WARNING: Untested code.
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
enum MyError: Error {
case FoundNil(String)
case DecodingData(Data)
}
struct Level: Codable {
let time: Double
let close: Double
let high: Double
let low: Double
let open: Double
}
struct Response: Codable {
let data: [Level]
private enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
func fetchData(completion: #escaping (Response?, Error?) -> Void) {
let url = URL(string: "https://min-api.cryptocompare.com/data/histominute?fsym=BTC&tsym=USD&limit=60&aggregate=3&e=CCCAGG")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil, MyError.FoundNil("data"))
}
do {
if let marketData = try? JSONDecoder().decode(Response.self, from: data) {
completion(marketData, nil)
} else {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
} catch {
completion(nil, MyError.DecodingData(data)) // work on this duplicated call
}
}
task.resume()
}
fetchData() { items, error in
if let error == error {
switch(error) {
case .foundNil(let whatsNil):
print("Something is nil: \(whatsNil)")
case .decodingData(let data):
print("Error decoding: \(data)")
}
} else {
if let items = items {
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
print(items)
} else {
print("No items to show!")
}
}
}
I don't understand what is your real issue, because you have written everything you need here, but as far I understand , to pass data
just uncomment this line completion(marketData, nil)
and in
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
print(items)
}
items is an object of your struct Response. You can pass this anywhere in your other class , by just creating an another variable like:
var items : Response!
for example :
class SomeOtherClass : NSObject{
var items : Response!
func printSomeData()
{
print(items.data[0].open)
print(items.data[1].open)
print("Average=", (items.data[0].open + items.data[1].open) / 2)
}
}
and in fetchData method write this:
fetchData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
let otherObject = SomeOtherClass()
otherObject.items = items
otherObject.printSomeData()
}