How can i make my Networking class generic in Swift? - ios

Here i am extracting data as DataModel. But i want to make this class generic and pass the model myself so that i can use it to parse data from multiple API's. Can Anyone Help?
import Foundation
struct NetworkManager {
func fetchData(url : String, completion : #escaping (DataModel?) -> ()) {
print("Neeraj here")
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
if let parsedData = self.parseData(data : safeData) {
print("got data")
completion(parsedData)
}
else {
debugPrint("failed to fetch data")
completion(nil)
}
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
func parseData(data : Data) -> DataModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(DataModel.self, from: data)
return decodedData
} catch {
print("error while parsing data \(error)")
return nil
}
}
}

With the convenient Result type you can write a quite tiny generic method, it returns the decoded type on success and any error on failure
func fetchData<T: Decodable>(urlString: String, completion: #escaping (Result<T,Error>) -> Void) {
guard let url = URL(string: urlString) else { return } // or throw an error
URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(T.self, from: data!) })
}.resume()
}
Note: Force unwrapping data! is 100% safe if no error occurs
Be aware that you have to specify the concrete type when you are going to call the method
fetchData(urlString: "https://example.com/api") { (result : Result<MyModel,Error>) in
switch result {
case .success(let model): print(model)
case .failure(let error): print(error)
}
}

You can add a generic type constraint (called Model) which conforms Decodable like below:
struct NetworkManager {
func fetchData<Model: Decodable>(url : String, completion : #escaping (Model?) -> ()) {
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
do {
let decodedData = try JSONDecoder().decode(Model.self, from: safeData)
completion(decodedData)
} catch {
print("error while parsing data \(error)")
}
} else {
debugPrint("failed to fetch data")
completion(nil)
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
}
Usage
struct SampleModel: Decodable {
let name: String
}
NetworkManager().fetchData(url: "") { (data: SampleModel?) in
print(data)
}

You can write a generic function to fetch data like this one :
func fetchGenericData<T: Decodable>(urlString: String, completion: #escaping (T) -> ()) {
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, resp, err) in
if let err = err {
print("Failed to fetch data:", err)
return
}
guard let data = data else { return }
do {
let obj = try JSONDecoder().decode(T.self, from: data)
completion(obj)
} catch let jsonErr {
print("Failed to decode json:", jsonErr)
}
}.resume()
}
}
I suppose that you have a data model, if you have not, you should create for your every object. Also by using a dummy URL i will make a request and fetch the JSON includes some users name and ids with JSON format.
Let`s define a data model for this:
struct StackUser: Decodable {
let id: Int
let name: String
}
fetchGenericData(urlString: "https://api.stackoverexample.com/stackusers") { (stackUsers: [StackUser]) in
stackUsers.forEach({print($0.name)})
}
Finally you will be parse the data and prints like this:
Rob
Matt
Vadian

Related

Swift - How do I show Data in Swift View from Data.swift file?

In a separate data file called data.swift I have this code
struct Response: Decodable {
var data: Data
}
struct Data: Decodable {
var search: search
}
struct search: Decodable {
var __Typename: String
var query: String
var searchResults: searchResults
}
...and so on and so forth. I then decode the data from a Rapid-Api like so
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error!)
} else {
let products = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)
if let water = products {
print("JSON: \n" + String(describing: water) + "\n")
}
}
})
How do I display the data elements in ProductList.swift it's a (view) file. The API works as expected and displays the JSON in the terminal. I am using XCODE 12.4 as I am not permitted to upgrade any further.
So, actually you want to receive the data in Model and show in view, either a label or image.
In your productSwift.List:
var response: Response?
Right now, you have to decode the data in the Model:
static func postApiCall<T: Decodable>(completion: #escaping (Result<T,Error>) -> Void) {
let url = URL(string: "Enter Your URL here")
let request = URLRequest(url: url!)
let dataTask = URLSession.shared.dataTask(with: request) { data, response , error in
guard let data = data else {
if error == nil {
completion(.failure(error as! Error))
}
return
}
do {
let decoder = JSONDecoder()
let json = try decoder.decode(T.self, from: data)
completion(.success(json))
} catch let error {
print(error.localizedDescription)
}
}
dataTask.resume()
}
}
Now in your ProductList.swift:
ServiceManage.postApiCall { (result : Result<Response,Error>) in
switch result {
case .success(let result):
print("result is \(result)")
self.response = response.data
self.yourLabel.text = response.data.search.query
case .failure(let failure):
print(failure)
}
}
and as Larme said, change your Struct "Data" name to something else.

How to extract data from networking class into variable in same class so that it can be accessed from other class in ios

How to extract data from networking class into variable in same class so that it can be accessed from other class in ios
Code for Networking Class
import Foundation
struct NetworkManager {
func fetchData(url : String) {
print("Neeraj here")
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
if let parsedData = self.parseData(data : safeData) {
print("got data")
}
}
}
else {
print("error in data task is \(String(describing: error))")
}
}
dataTask.resume()
}
func parseData(data : Data) -> DataModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(DataModel.self, from: data)
return decodedData
} catch {
print("error while parsing data \(error)")
return nil
}
}
}
Here where i am getting data, i want that data to be stored somewhere or in same class so that i can access it from class i am calling this method fetchData
You can use the closure to return the value out of the function. This practice is functional programming, almost using for async function.
func fetchData(url: String, completion: (DataModel?) -> ()) {
...
if let parsedData = self.parseData(data : safeData) {
print("got data")
completion(parsedData)
} else {
completion(nil)
}
}
And then, to use it:
NetworkManager().fetchData(url: "https://google.com", completion: { data in
// handle the “data”
})

What will be the Apple Combine Equivalent of the following two methods?

There is an equivalent for URL Session in URLSession.shared.dataTaskPublisher(for:>) and simlairily for handling' error sand decoding But how to go about handling the return type of discardableResult? There is an equivalent for URL Session in URLSession.shared.dataTaskPublisher(for:>) and simlairily for handling' error sand decoding
#discardableResult class func taskForGETRequest<ResponseType: Decodable>(url: URL, responseType: ResponseType.Type, completion: #escaping (ResponseType?, Error?) -> Void) -> URLSessionTask {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
DispatchQueue.main.async {
completion(nil, error)
}
return
}
let decoder = JSONDecoder()
do {
let responseObject = try decoder.decode(ResponseType.self, from: data)
DispatchQueue.main.async {
completion(responseObject, nil)
}
} catch {
do {
let errorResponse = try decoder.decode(TMDBResponse.self, from: data)
completion(nil,errorResponse)
} catch {
DispatchQueue.main.async {
completion(nil, error)
}
}
}
}
task.resume()
return task
}
class func taskForPOSTRequest<RequestType: Encodable, ResponseType: Decodable>(url: URL, responseType: ResponseType.Type, body: RequestType, completion: #escaping (ResponseType?, Error?) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONEncoder().encode(body)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {
completion(nil, error)
return
}
let decoder = JSONDecoder()
do {
let responseObject = try decoder.decode(ResponseType.self, from: data)
completion(responseObject, nil)
} catch {
do {
let errorResponse = try decoder.decode(TMDBResponse.self, from: data)
completion(nil,errorResponse)
} catch {
DispatchQueue.main.async {
completion(nil, error)
}
}
}
}
task.resume()
}

Parse image from web json

I have a json file that looks something like this:
{
"adTitle": "My Title",
"adURL": "https://mylink.com/",
"adImageURL": "http://mywebsite/bannerx#3x.png"
}
I get the JSON value from website: http://mywebsite.com/file.json
The problem is that the ad somehow doesn't load the adImageURL, so when I press the UIImageView, but when I press the area that then UIImageView should be, it open my adURL. This is the code I use for JSON:
var imageURL:String = "http://mywebsite/bannerx#3x.png"
var adURL:String = "https://mylink.com/"
func loadAdvertisement() {
// Set up the URL request
let todoEndpoint: String = "http://mywebsite.com/file.json"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
// print("error calling GET on /todos/1")
// print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard (try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: AnyObject]) != nil else {
print("error trying to convert data to JSON")
return
}
let json = try JSONSerialization.jsonObject(with: responseData, options:.allowFragments) as! [String:AnyObject]
if (json != nil) {
self.imageURL = (json["adImageURL"] as? String)!
self.adURL = (json["adURL"] as? String)!
print(self.imageURL)
print(self.adURL)
DispatchQueue.main.async { () -> Void in
self.loadAdImage(self.imageURL)
}
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
// let jsonURL = URL(string: "http://mywebsite.com/file.json")
// self.getDataFromUrl(jsonURL!, completion: (data:Data?, response:URLResponse?, error:Error?)) -> Void
}
func loadAdImage(_ url:String) {
getDataFromUrl(URL(string: url)!) { (data, response, error) in
DispatchQueue.main.async { () -> Void in
guard let data = data, error == nil else { return }
print(response?.suggestedFilename ?? "")
print("Download Finished")
self.advertImageView.image = UIImage(data: data)
}
}
}
func getDataFromUrl(_ url:URL, completion: #escaping ((_ data: Data?, _ response: URLResponse?, _ error: NSError? ) -> Void)) {
URLSession.shared.dataTask(with: url) { (data:Data?, response:URLResponse?, error:Error?) in
completion(data, response, error as NSError?)
}.resume()
}
In the event LOG, is prints out both of the print("error trying to convert data to JSON") commands. I have used this code before in my project, and it worked just fine, but I have no idea why it wont work anymore.
Add the message to catch and check what actually error you are getting like this way:
do {
let json = try JSONSerialization.jsonObject(with: responseData, options:.allowFragments) as! [String:AnyObject]
} catch let message {
print("error trying to convert data to JSON" + "\(message)")
return
}

Swift 4 - Invalid conversion from throwing function

I have connect.swift with code:
public struct Connect {
let adresSerwera = "http://test.nazwa.pl/"
typealias Odpowiedz = (Data?, Error?) -> Void
func getJsonFromServer(parametry: String, wynikRequesta: #escaping Odpowiedz) {
guard let Url = URL(string: self.adresSerwera + "kartyEndpoint.qbpage" + parametry) else { return }
URLSession.shared.dataTask(with: Url) { (data, response, error) in
if error == nil {
guard let data = data else {
print("Error 100: \(error)")
wynikRequesta(nil, error)
return
}
print("R>" + self.adresSerwera + "kartyEndpoint.qbpage?" + parametry)
do {
//let json = try JSONDecoder().decode(forecast.self, from: data)
wynikRequesta(data, nil)
dump(data)
print("\(data)")
} catch let err {
print("Error 101: ", err)
wynikRequesta(nil, err)
}
} else{
print("Error 102: Problem with download data")
}
}.resume()
}
func sprawdzDaneLogowania(login: String?, haslo: String?, callback: #escaping Odpowiedz) {
getJsonFromServer(parametry: "?action=LOGOWANIE&login=\(login!)&password=\(haslo!)", wynikRequesta: callback)
}
}
and code to download data:
#IBAction func btnLoginPressed(_ sender: Any) {
if self.textFieldLogin.text?.isEmpty ?? true || self.textFieldPassword.text?.isEmpty ?? true {
print("Uzupełnij wszystkie pola!!")
} else {
print("Pola uzupełnione")
cms.sprawdzDaneLogowania(login: self.textFieldLogin.text, haslo: self.textFieldLogin.text, callback: { (data, blad) in
if blad == nil{
if let dane = data {
let str = String(data: dane, encoding: .utf8)
let downloadedData = RankingGrupowyObject(JSONString: str!)
let decoder = JSONDecoder()
let zalogowanyUser = try decoder.decode(LoginUser.self, from: data)
} else {
print("Error 103: \(data)")
}
} else {
print("Error 104: \(blad)")
}
})
}
}
for lines:
cms.Check the Logs (login: self.textFieldLogin.text, password: self.textFieldLogin.text, callback: {(date, error) in
I get an error message:
Invalid conversion from throwing function of '(_, _) throws -> ()' is
a non-throwing function type 'Connect. Answer' (aka '(Optional ,
Optional ) -> ()')
What have I done wrong? How can you fix this error?
By using the CheckLogging function I would like to create a target object
let zalogowanyUser = try decoder.decode(LoginUser.self, from: data)
This part is able to throw, meaning that you should either do/catch there:
do {
let zalogowanyUser = try decoder.decode(LoginUser.self, from: data)
}
catch {
print("Error in decoder")
}
Or let the error propagate onto upper parts. In order to do that, your method cms.sprawdzDaneLogowania could be marked as throws, or your that method's callback block could be marked as such.

Resources