How to extract data from networking class into variable in same class so that it can be accessed from other class in ios - 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”
})

Related

How to print the api response to the console? Xcode 14

I am learning about API's and decided to practice using them by writing a simple function to call an api and print the response. The issues I am having is that the response is not printing to the console. I am also new to Swift but watched a couple of tutorials, which lead me to write this basic skeleton code.
import Foundation
struct Posts: Codable {
let userId: Int
let id: Int
let title: String
let body: String
}
func fetch() {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
return
}
do {
let posts = try JSONDecoder().decode(Posts.self, from: data)
print(posts) //Doesn't print the response
}
catch {
print(error)
}
}
task.resume()
}
fetch()
func fetch() {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
return
}
do {
let posts = try JSONDecoder().decode([Posts].self, from: data)
print(posts)
}
catch {
print(error)
}
}
task.resume()
}

How can i make my Networking class generic in Swift?

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

How to parse nested JSON using custom completion block with JSONDecoder?

i have nested json object, and need to parse and take fill out the collectionViewCell with imageUrls in the json
i need to iterate overe list.recommendBannerImages and save it in external var so that i can use it into my collectionViewCells
Firstly you write a function for this and lets assume that this function name fetchData and your data model should be conform Codable protocol.
func fetchData(url: String, completion: #escaping(Result<[DataModel], Error>)->()){
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url){(data, response, error) in
if error != nil{
completion(.failure(error!))
print(error!)
} else{
guard let _ = response as? HTTPURLResponse, let jsonData = data else { return }
let yourDataModel = try? JSONDecoder().decode([DataModel].self, from: jsonData)
guard let dataModels = mainPageContentData else { return } //dataModels is array of DataModel
completion(.success(dataModels)) // And we send this array to completion blocks
}
}.resume()
}
And you should use this function like this
fetchData { (result) in //Trailing closure syntax
switch result {
case .failure(let error):
print(error)
case .success(let datas):
for data in datas{
print(data)
}
}
}

How can I unit test a network request using a local json file?

I'm trying to figure out the best way to unit test a network request. My initial thought was to create a local file with the JSON response for testing purposes but that doesn't seem to be working. See my code below.
I wanna test that I can get a non-nil array back from the completion handler in the function below.
class APIClient {
let downloader = JSONDownloader() // just a class that creates a new data task
// what I want to test
func getArticles(from url: URL?, completion: #escaping([Article]?, Error?) -> ()) {
guard let url = url else { return }
let request = URLRequest(url: url)
let task = downloader.createTask(with: request) { json, error in
DispatchQueue.main.async {
// parse JSON
...
completion(articles, nil)
}
}
task.resume()
}
}
I tried testing as shown below to no avail.
func testArticleResponseIsNotNil() {
let bundle = Bundle(for: APIClientTests.self)
guard let path = Bundle.path(forResource: "response-articles", ofType: "json", inDirectory: bundle.bundlePath) else {
XCTFail("Missing file: response-articles.json")
return
}
let url = URL(fileURLWithPath: path)
var articles: [Article]?
let expectation = self.expectation(description: "Articles")
let client = APIClient()
client.getArticles(from: url) { response, error in
articles = response
expectation.fulfill()
}
wait(for: [expectation], timeout: 5)
XCTAssertNotNil(articles)
}
Any ideas on how exactly I should test this function?
Edit: This is the JSONDownloader class.
class JSONDownloader {
let session: URLSession
init(configuration: URLSessionConfiguration) {
self.session = URLSession(configuration: configuration)
}
convenience init() {
self.init(configuration: .default)
}
typealias JSON = [String: AnyObject]
func createTask(with request: URLRequest, completion: #escaping(JSON?, Error?) -> ()) -> URLSessionDataTask {
let task = session.dataTask(with: request) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse else { return }
if httpResponse.statusCode == 200 {
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? JSON
completion(json, nil)
} catch { completion(nil, error) }
} else { completion(nil, error) }
} else { completion(nil, error) }
}
return task
}
}

Swift 4 adding parameters to URLRequest completion handler

I'm trying to return some data from a URLRequest in Swift 4, and to do so I've added a completion handler to my function signature, just with a Bool for the time being. This is the function:
func getJson(completionHandler: #escaping (Bool) -> ()) {
let jsonUrlString = "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808"
guard let url = URL(string: jsonUrlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data, err == nil else {
print(err!)
return
}
do {
let response = try
JSONDecoder().decode(TopStoriesResponse.self, from: data)
print(response.results)
// Pass results into arrays (title, abstract, url, image)
completionHandler(true)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
}
and I call it in viewDidLoad like so:
getJson { (success) in
print("Success")
}
Nothing is printing to the console so I'm wondering if I am using the completion handler correctly? But ultimately I'd like to switch out the Bool and instead pass a few values out of the request, back into arrays that I have in my view controller.
These are the structs I'm using to grab the pieces of JSON that i want:
struct TopStoriesResponse: Decodable {
let status: String
let results: [Story]
}
struct Story: Decodable {
let title: String
let abstract: String
let url: String
}
And I'm ultimately trying to fill these arrays in my view controller with the parsed JSON so I can arrange them in a table view:
var headlines = [String]()
var abstracts = [String]()
var urls = [URL]()
EDIT: Full code in case I'm going wrong somewhere else: https://pastebin.com/r402GKej
try creating the struct TopStoriesResponse and Story seperately from the ViewController and add the Networking struct to load data from the API
struct TopStoriesResponse: Decodable {
let status: String
let copyright: String
let num_results: Int
let results: [Story]
}
struct Story: Decodable {
let title: String
let abstract: String
let url: String
}
struct Networking {
static func getJson(completionHandler: #escaping (Bool) -> ()) {
let jsonUrlString = "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808"
guard let url = URL(string: jsonUrlString) else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data, error == nil else {
print(error!.localizedDescription)
return
}
do {
let response: TopStoriesResponse = try JSONDecoder().decode(TopStoriesResponse.self, from: data)
print(response.results.count)
completionHandler(true)
} catch {
print(error.localizedDescription)
completionHandler(false)
}
}.resume()
}
}
Now try calling Networking.getJson from the ViewController
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData() {
Networking.getJson { (result) in
print(result)
}
}
}

Resources