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()
}
Related
Hi i am a beginner studying swift and would like to know what to use when making an api request. What is the modern and professional way?
is it using an escaping closure like so:
func getTrendingMovies(completion: #escaping (Result< [Movie], Error >) -> Void) {
guard let url = URL(string: "\(Constants.baseUrl)/trending/all/day?api_key=\.(Constants.API_KEY)") else {return}
let task = URLSession.shared.dataTask(with: URLRequest(url: url)) { data, _,
error in
guard let data = data, error == nil else {
return
}
do {
let results = try JSONDecoder().decode(TrendingMoviesResponse.self, from:
data)
completion(.success(results.results))
} catch {
completion(.failure(error))
}
}
task.resume()
}
or should i make an api request without escaping closure while using a sort of delegate like so:
func performRequest(with urlString: String){
if let url = URL(string: urlString){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
delegate?.didFailWithError(error: error!)
return
}
if let safeData = data{
// created parseJson func
if let weather = parseJSON(safeData){
delegate?.didUpdateWeather(self,weather: weather)
}
}
}
task.resume()
} else {
print("url is nil")
}
}
I agree with matt, the modern and professional way is async/await
func getTrendingMovies() async throws -> [Movie] {
let url = URL(string: "\(Constants.baseUrl)/trending/all/day?api_key=\(Constants.API_KEY)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(TrendingMoviesResponse.self, from: data).results
}
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
I have the following JSON that is formatted like this:
{
"error":false
}
I understand that is not an array because it does not include square brackets on both sides, but I cannot seem to understand how to properly get Swift to interpret this correctly.
This is the structure I am using:
struct CheckStruct: Decodable {
let error: String?
}
And the following is the function that should read the JSON:
private func JSONFunc() {
guard let url = URL(string: "https://example.com/example/example.php"),
let value = name.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "number=\(number)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.CheckRecord = try JSONDecoder().decode(Array<CheckStruct>.self,from:data)
DispatchQueue.main.async {
// Do something
}
}
catch {
print(error)
}
}.resume()
}
UPDATE:
If I were to use the results of the function to create an if else statement, how would this look?
For example if results are true do this..
else do this...
Your model should be like this:
struct CheckStruct: Codable {
let error: Bool?
}
And your function should be like this:
private func JSONFunc() {
guard let url = URL(string: "https://example.com/example/example.php"),
let value = name.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "number=\(number)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return }
do {
let myData= try JSONDecoder().decode(CheckStruct.self, from:data)
print(myData.error)
} catch {
print(error)
}
}.resume()
}
BONUS
//Create Typealias
typealias BoolHandler = ((Bool?) -> Void)
//Create Function with Completion
private func fetchData(_ completion: #escaping BoolHandler) {
guard let url = URL(string: "https://example.com/example/example.php"),
let value = name.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "number=\(number)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return }
do {
let myData= try JSONDecoder().decode(CheckStruct.self, from:data)
DispatchQueue.main.async {
completion(myData.error)
}
} catch {
DispatchQueue.main.async {
completion(nil)
}
}
}.resume()
}
//Call Method
fetchData { isSuccess in
if isSuccess {
// Do something
} else {
// Do something
}
}
I hope it will work for you.
Enjoy.
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)
}
}
}
class NetworkManager{
var articleList = [Article]()
func downloadJsonData() -> Void{
let jsonUrl = "someUrl"
guard let url = URL(string: jsonUrl) else { return }
URLSession.shared.dataTask(with: url) { data, response, err in
//check err
//check response status
guard let data = data else { return }
do{
let apiResults = try JSONDecoder().decode(ApiResults.self, from: data)
//article list remains empty
self.articleList = apiResults.articles
} catch let err{
print(err)
}
}.resume()
}
}
I have also tried to use a for loop to append to the array and that didn't work either. Any help will be appreciated.
First thing I would check is that the data returned is correct.
Is the guard block triggering the return or is the data fine?
Is the JSON able to decode the response correctly?
Are the articles in the apiResults object populated.
The next thing is you are not attempting to append the contents of apiResults.articles to your list, instead you are making your list become what ever apiResults.articles is.
Try the following and see how it runs:
class NetworkManager{
// better declaration syntax
var articleList: [Article] = []
func downloadJsonData() {
let jsonUrl = "https://newsapi.org/v2/everything?sources=nfl-news&apiKey=mykey"
guard let url = URL(string: jsonUrl) else { return }
URLSession.shared.dataTask(with: url) { data, response, err in
//check err
//check response status
guard let data = data else { return }
do{
let apiResults = try JSONDecoder().decode(ApiResults.self, from: data)
//article list remains empty
//appends contents instead of assignment
self.articleList.append(contentsOf: apiResults.articles)
} catch let err{
print(err)
}
}.resume()
}
}