I am using an api which allows me 600 requests per hour.
My app is not finished so i test sometimes the JSON Requests. I guess about 10 requests per hour.
But the provider says I did 600.
How is that possible?
Is there a way to count the requests from my side?
I am doing the requests with my widget (Widgetkit). Is it possible that it does a realtime download request for every second?
My actual JSON Request:
class NetworkManager: ObservableObject {
#Published var posts = [Post]()
#Published var clubName = "..."
#Published var teamId = "30"
#Published var gastMannschaft = "..."
let testStr = UserDefaults(suiteName: "gro")!.string(forKey: "test")
init() {
fetchData() // fetch data must be called at least once
}
func fetchData() {
let teamId = testStr ?? "47"
if let url = URL(string: "..." + teamId) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = gettingInfo {
do {
let results = try decoder.decode(Results.self, from: safeData)
DispatchQueue.main.async {
self.clubName = results.data[0].away_name
if #available(iOS 14.0, *) {
WidgetCenter.shared.reloadAllTimelines()
} else {
// Fallback on earlier versions
}
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
}
There might be a possibility that you are making some redundant requests which you might not be aware of.
To track your requests you could log them using for example Charles Proxy. This would reveal detailed information about requests you have made over the time you are performing them over the proxy.
Related
I have a List that is updated with a Fetch class, an ObservableObject. It has an init function. This is that Fetch class.
#Published private(set) var items: [ItemsResult] = []
init() {
let url = URL(string: "[redacted]")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let itemsData = data {
let decodedData = try JSONDecoder().decode([ItemsResult].self, from: itemsData)
DispatchQueue.global(qos: .utility).async {
DispatchQueue.main.async {
print("running task")
self.items = decodedData
}
}
print(self.items)
} else {
print("No data")
}
} catch {
print("Error: \(error)")
}
}.resume()
}
When the app is build, it correctly displays the data returned by the API and it matches the database. However when I tap / click on one in order to delete it, or use the textarea I've added to add a new item it doesn't update.
struct TickrApp: View {
#EnvironmentObject var fetch: Fetch
var body: some View {
NavigationView {
Form {
Section {
VStack(alignment: .center) {
Text("Welcome to Tickr")
}
}
Section {
List(fetch.items) { item in
CheckView(checked: item.done, title: item.content.replacingOccurrences(of:"_", with: " "))
}
}
AddItemView()
}.navigationBarTitle(Text("Tickr"))
}
}
}
The database is being updated as shown when I log the decodedData they respond, however in each I just call Fetch(). Requests are made the same in all three cases.
One of the calls, for text input.
func toggle() {
checked = !checked
let url = URL(string: "")!
var req = URLRequest(url: url)
req.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: req) { data, response, error in
guard let _ = data,
let response = response as? HTTPURLResponse,
error == nil else {
print("error", error ?? "Unknown error")
return
}
guard (200 ... 299) ~= response.statusCode else {
print("statusCode should be 2xx, but is \(response.statusCode)")
print("response = \(response)")
return
}
}
task.resume()
Fetch()
In order to update the list visually I need to completely quit the app / rerun it in order to have the new and/or deleted items show correctly. No errors show about background publishing changes or anything.
It appears that you're trying to call Fetch() to refresh your data. There are two things that are going to be a problem with this:
You're calling it outside of the dataTask completion handler. That means that it may get called before the write finishes
Calling Fetch() just creates a new instance of Fetch, when what you really want to do is update the results on your existing instance.
I'm assuming that your first code snipped is from Fetch. I'd change it to look more like this:
class Fetch: ObservableObject {
#Published private(set) var items: [ItemsResult] = []
init() {
performFetch()
}
func performFetch() {
//your existing fetch code that was in `init`
}
}
Then, in your AddItemView and CheckView, make sure you have this line:
#EnvironmentObject var fetch: Fetch
This will ensure you're using the same instance of Fetch so your list will reflect the same collection of results.
Once you're done with an operation like toggle(), call self.fetch.performFetch() to update your results. So, your last code snippet would turn into something like this:
let task = URLSession.shared.dataTask(with: req) { data, response, error in
//guard statements to check for errors
self.fetch.performFetch() //perform your refresh on the *existing* `Fetch` instance
}
A bigger refactor would involve moving your async code (like toggle) to a view model, instead of doing any of it in a View code. Also, look into using the URLSession Publishers using Combine, since you're using SwiftUI: https://developer.apple.com/documentation/foundation/urlsession/processing_url_session_data_task_results_with_combine
I have an object with some text data received from a remote server using a POST request. Each time the page is opened, the application makes a request to the remote server.
How do I do caching?
Here is my code without caching:
import Foundation
struct NewsFeed: Codable {
var status: String = ""
var totalResults: Int = 0
var posts: [PostItem]
}
struct PostItem: Codable {
var id: Int
var title: String
var link: String
var date: String
var category: String
var thumbnail: String
var excerpt: String
var content: String
}
class NewsDecoder: ObservableObject {
#Published var newsFeed: NewsFeed?
#Published var hasContent: Bool = false
init() {
self.getNews()
}
func getNews() {
let urlString = "http://example.com/feed/json_news/all.json"
let url = URL(string: urlString)
guard url != nil else {
return
}
let dataTask = URLSession.shared.dataTask(with: url!) { (data, response, error) in
DispatchQueue.main.async {
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
self.newsFeed = try decoder.decode(NewsFeed.self, from: data!)
self.hasContent = true
} catch {
print("Error: \(error)")
}
}
}
}
dataTask.resume()
}
}
Well, within the HTTP protocol layer, you do not explicitly cache the response. If the backend wishes the client to cache the payload from a POST response which you are later be able to fetch with a GET request, the backend would need to setup the response accordingly by specifying a Content-Location response header and an associated cache control header, according to RFC 7231 4.3.3 POST.
The payload returned has the location specified in the returned URL, and the URL has the same value as the POST's effective request URL. Please read here for more information about this specifically.
If that has been done by the backend and if you have configured your URLSession to use a URLCache on the client, the URLLoading mechanism would cache the payload under the URL returned in the "Content-Location" response header.
If you then issue a GET request with the given URL previously returned in the Content-Location response header, the caching mechanism would take effect.
However, there is no guarantee that POST caching is implemented on the client. When using URLCache it's worth to carefully investigate its behaviour and check if it is working as expected.
I am having problems finding out why my dataTask returns an empty result.
While going through My NetworkingManager class it appeared that it never enters the URLSession.shared.dataTask. Does anyone know why?
Her is my NetworkingManager which is being used in the ContentView of the app:
class NetworkingManager: ObservableObject {
var didChange = PassthroughSubject<NetworkingManager, Never>()
var showList = ShowResultsAPI(results: []) {
didSet {
didChange.send(self)
}
}
init() {
guard let url = URL(string: "www.json.url") else {
return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else {
return }
let showList = try! JSONDecoder().decode(ShowResultsAPI.self, from: data)
DispatchQueue.main.async {
self.showList = showList
}
}.resume()
}
}
In my opinion your coding looks correct.
Keep in mind that the request is asynch. When your debugging the URLSession.shared.dataTask you will recognize that at first the debugger is skipping the dataTask. When the URLSession receives a response it will enter the URLSession.shared.dataTask again.
I would recommend to set a breakpoint in the line with your guard statement. Then debug the process again and see if the debugger enters the process.
It would also be interesting to observe the response and the error in the completion handler to see if there are errors occuring.
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200,
let data = data
else {
print("ResponseProblem")
print(response)
return
}
add return value in guard : return "example"
change your code to this :
}
}.resume()
sleep(2)
}
Adding a .sleep(2) at the end of the init() helped to process the JSON.
Edited:
It needed an asynchronous task which implies having a sleep or as #vadian suggested a better suited delay() from combine.
I retrieved values from Json, i print retrieved values order changed(position changed) every time. please check my below code once:
override func viewDidLoad() {
super.viewDidLoad()
var myIds = [104016, 104010, 104014, 104018, 104000, 104038, 104015, 104011, 104015, 104010, 104010, 104010, 104003, 104003, 104011]
for arr in 0 ..< myIds.count-1 {
let url = URL(string: "http://.........\(myIds[arr])")
print("myIds[\(arr)]:\(myIds[arr])")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print ("ERROR")
}
else
{
if let content = data
{
do
{
//Array
let myJson = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
var i = myJson["Job_title_name"]!
var ist = ""
// print("i=\(i)")
ist = String(describing: i!)
print("myIds[\(arr)]=\(ist)")
}
catch
{
}
}
}
}
task.resume()
}
}
**output:**
myIds[0]:104016
myIds[1]:104010
myIds[2]:104014
myIds[3]:104018
myIds[4]:104000
myIds[5]:104038
myIds[6]:104015
myIds[7]:104011
myIds[8]:104015
myIds[9]:104010
myIds[10]:104010
myIds[11]:104010
myIds[12]:104003
myIds[13]:104003
myIds[0]=WebLogic Admin
myIds[3]=OracleDevloper
myIds[2]=Node Js Developer
myIds[1]=Angular Developer
myIds[6]=HTML&CSS Developer
myIds[5]=Senior Manager
myIds[7]=CRM Developer
myIds[4]=Windows Manager
myIds[8]=HTML&CSS Developer
myIds[9]=Angular Developer
myIds[11]=Angular Developer
myIds[10]=Angular Developer
myIds[12]=java developer
myIds[13]=java developer
This output is changed every time, when stop and run the app. but i'm unable to find the problem. please check my code once.
Required output:
myIds[0]:104016
myIds[1]:104010
myIds[2]:104014
myIds[3]:104018
myIds[4]:104000
myIds[5]:104038
myIds[6]:104015
myIds[7]:104011
myIds[8]:104015
myIds[9]:104010
myIds[10]:104010
myIds[11]:104010
myIds[12]:104003
myIds[13]:104003
myIds[0]=WebLogic Admin
myIds[1]=Angular Developer
myIds[2]=Node Js Developer
myIds[3]=OracleDevloper
myIds[4]=Windows Manager
myIds[5]=Senior Manager
myIds[6]=HTML&CSS Developer
myIds[7]=CRM Developer
myIds[8]=HTML&CSS Developer
myIds[9]=Angular Developer
myIds[10]=Angular Developer
myIds[11]=Angular Developer
myIds[12]=java developer
myIds[13]=java developer
i want the above output. In the above code whats my mistake.
URLSession.shared.dataTask() usually call API in parallel execution so no matter if calling request order will same as response order.
If you want to call request and get response in same order then you should create NSOperation queue and make dependency on it.
Network requests run asynchronously, so this is the expected behaviour. The requests don't finish in the same order as they were started.
If you need your requests to run sequentially, you can run them using DispatchGroups, but this will lead to slower performance due to the fact that your asynchronous requests are executed sequentially instead of in parallel. A better solution is to store the results in a data structure, where you can identify the objects based on a unique identifier other than their index. For your current problem, the best solution is to store the results in a dictionary, where the key is the id and the value is the value from the network request.
Concurrent solution using a Dictionary to store the output values:
override func viewDidLoad() {
super.viewDidLoad()
var myIds = [104016, 104010, 104014, 104018, 104000, 104038, 104015, 104011, 104015, 104010, 104010, 104010, 104003, 104003, 104011]
var jobTitles = [Int:String]()
let group = DispatchGroup()
for arr in 0 ..< myIds.count-1 {
let url = URL(string: "http://.........\(myIds[arr])")
print("myIds[\(arr)]:\(myIds[arr])")
group.enter()
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil{
print ("ERROR")
} else {
if let content = data{
do {
guard let myJson = try JSONSerialization.jsonObject(with: content, options: []) as? [String:Any] else {return}
guard let jobTitle = myJson["Job_title_name"] as? String else {return}
jobTitles[myIds[arr]] = jobTitle
group.leave()
} catch {
}
}
}
}
task.resume()
}
}
}
group.notify(queue: DispatchQueue.main, execute: {
print(jobTitles)
})
Some general advice: don't force unwrap values from a server response and don't use String(describing:) to create a String. Use optional binding or default values to safely unwrap the optionals and either cast your values to String if they are String values or just use String(value) for values from which Strings can be directly initialized (such as Int).
I am making an application which makes a lot of requests from an API. So I don't want to copy and past the code over and over. I was wondering how I can reuse my code in a some more efficient way? Maybe with extensions?
This is my code know:
func apiRequest() {
let config = URLSessionConfiguration.default
let username = "****"
let password = "****"
let loginString = String(format: "%#:%#", username, password)
let userPasswordData = loginString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData?.base64EncodedString()
let authString = "Basic " + (base64EncodedCredential)!
print(authString)
config.httpAdditionalHeaders = ["Authorization" : authString]
let session = URLSession(configuration: config)
var running = false
let urlProjects = NSURL(string: "https://start.jamespro.nl/v4/api/json/projects/?limit=10")
let task = session.dataTask(with: urlProjects! as URL) {
( data, response, error) in
if let taskHeader = response as? HTTPURLResponse {
print(taskHeader.statusCode)
}
if error != nil {
print("There is an error!!!")
print(error)
} else {
if let content = data {
do {
let dictionary = try JSONSerialization.jsonObject(with: content) as! [String:Any]
print(dictionary)
if let items = dictionary["items"] as? [[String:Any]] {
for item in items {
if let description = item["Description"] as? String {
self.projectNaam.append(description)
}
if let id = item["Id"] as? String {
self.projectId.append(id)
}
if let companyId = item["CompanyId"] as? String {
self.companyId.append(companyId)
}
}
}
self.apiRequestCompani()
}
catch {
print("Error: Could not get any data")
}
}
}
running = false
}
running = true
task.resume()
while running {
print("waiting...")
sleep(1)
}
}
Yes, you can use Extensions to create a BaseViewController and extend that where you want to use your code over and over again. Then you should abstract all dynamic data over input parameters to that method.
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
func getApiRequest (Parameters) {
//API Request
}
And then in your view controller you just extend BaseViewController
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Call method in baseviewcontroller
getApiRequest(parameters)
//Call method in self
self.getApiRequest(parameters)
}
override func getApiRequest(Parameters) {
//IF you need to override default configuration
}
So I don't want to copy and past the code over and over.
Absolutely right, no one aiming to get duplicated code; That's the issue of massive view controller. This issue appears since the view controller layer in your application handles most of the responsibilities, such as: getting data from the network, how data should be represented, deliver the formatted data to the view layer, etc...
There are many approaches for solving such an issue (using an appropriate architectural pattern for your application), for simplicity, I would recommend to apply the MVC-N (or MVCNetworking) approach into your app, it is almost the same usual MVC, with a separated files (managers), represent a new layer for handling -for instance- the integration with the external APIs.
Applying the MVN-N should not be that complex, nevertheless it needs to be described well (which might be too abroad to be descried in the answer), I would suggest to check the above mentioned apple example, also watching this video should be useful.