after hours of search I need your help. I have setup a program for iOS showing recipes of my private cooking book. The recipes are stored in a json file on my web server. I use another application on my MAC to update the recipes in this json file and to store it on my web server. Unfortunately the updated recipes are not shown in my iOS-application. The iOS-app shows always the old data - I guess they are stored locally on my iPhone/iPad after the first installation of the app?
How can I enforce that at every launch of the iOS-app on my iPhone/iPad the actual json data from the web server are used and not the old one.
I retrieve the data via this function I call from the viewDidLoad() and before the reloadData() of the UITableView showing the titles of the recipes
func initKochbuch() {
let url = URL(string: selektiertesKochbuch)!
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: url) { (data, response, error) in
guard let data = data else {
debugPrint("Fehler beim Laden", error ?? "Unbekannter Fehler")
return
}
self.kochbuch_local.rezepte = try! JSONDecoder().decode([Rezept].self, from: data)
self.initRezeptAnsicht()
OperationQueue.main.addOperation {self.tableView.reloadData()}
}
task.resume()
}
What do I have to do in addition? Thanks for your support.
Few possibilities:
hope you are not storing the data locally(coredata/plist/any file), and loading it anywhere in your app.
Your web server might be caching the old data, please check once.
See if you have any hardcoded sample data which was used for testing purpose but not removed after actual implementation.
Hope this helps.
Related
I have an ESP32 Battery monitor system (BMS) whose status I want to view on an iPhone, there are about 100 values to be monitored with a maximum update rate of once per second. I also needs to be able to send settings to the BMS occasionally.
Blynk seemed to be the perfect app to do this but the new version 2.0 doesn't support BLE! Does anyone know a similar app that could do this?
Have you tried setting the ESP32 up as a web server and creating an API based on the values? Using this instead of bluetooth allows you to view the values from anywhere with internet connection, and is easier to implement and use across different platforms
You can do this by implementing code into the ESP32, so that when you make http requests, it returns with a value, just like any other API would. Then in Xcode where you are building your IOS app you can make http requests to the web server and retrieve values
for example, in the ESP32 you can do something like this (assuming you are using the Arduino IDE to program it):
server.on("/value", [](AsyncWebServerRequest * request))
{
String value = "value"
request->send(200,"text/plain", value)
}
This YouTube video shows you this in more detail: https://www.youtube.com/watch?v=cWZP7Y8qP6E
Then, in Xcode you can implement something along the lines of:
func loadData() async {
guard let url = URL(string: "http://IP-address-of-esp32/path-of-value", ) else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
// process data here with the 'data' variable
} catch {
print("Invalid Data")
}
}
//Be aware that this is asynchronous when calling it
This person also explains it in more detail in a YouTube video: https://www.youtube.com/watch?v=MBCX1atOvdA
Now, since you have a web server and API set up on your ESP32, and a method of retrieving the values in your IOS app, you can go on and use the values to build the app to your liking.
Good Luck!
I have a download task that work by first calling a REST API for which the server needs to generate a fairly large file which takes it several minutes to generate, as it is CPU and disk IO intensive. The client waits for the server to give a JSON response with the URL of the file it generated. The file download then starts after it gets the first result.
For the calls that generate a particularly large file, which causes the server to be very slow to respond, I am seeing duplicate requests that my code is not initiating.
Initially the someone who works on the server side told me about the duplicate requests. Then I set up a way to inspect network traffic. This was done by setting up a Mac connected to a wired network and enabling network sharing and using Proxyman to inspect the traffic from the iPhone to the API server. I see multiple instances of the same API request on the network layer but my code was never notified.
Code looks like this
#objc class OfflineMapDownloadManager : NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
#objc func download(){
let config = URLSessionConfiguration.background(withIdentifier: "OfflineMapDownloadSession")
config.timeoutIntervalForRequest = 500
config.shouldUseExtendedBackgroundIdleMode = true
config.sessionSendsLaunchEvents = true
urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil)
getMapUrlsFromServer(bounds)
}
func getMapUrlsFromServer(){
var urlString = "http://www.fake.com/DoMakeMap.php"
if let url = URL(string: urlString) {
let request = NSMutableURLRequest(url: url)
//...Real code sets up a JSON body in to params...
request.httpBody = params.data(using: .utf8 )
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.timeoutInterval = 500
urlSession?.configuration.timeoutIntervalForRequest = 500
urlSession?.configuration.timeoutIntervalForResource = 500
request.httpShouldUsePipelining = true
let backgroundTask = urlSession?.downloadTask(with: request as URLRequest)
backgroundTask?.countOfBytesClientExpectsToSend = Int64(params.lengthOfBytes(using: .utf8))
backgroundTask?.countOfBytesClientExpectsToReceive = 1000
backgroundTask?.taskDescription = "Map Url Download"
backgroundTask?.resume()
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
if (downloadTask.taskDescription == "CTM1 Url Download") {
do {
let data = try Data(contentsOf: location, options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
if let jsonResult = jsonResult as? Dictionary<String, AnyObject> {
if let ctm1Url = jsonResult["CTM1Url"] as? String {
if let filesize = jsonResult["filesize"] as? Int {
currentDownload?.ctm1Url = URL(string: ctm1Url)
currentDownload?.ctm1FileSize = Int32(filesize)
if (Int32(filesize) == 0) {
postDownloadFailed()
} else {
startCtm1FileDownload(ctm1Url,filesize)
}
}
}
}
} catch {
postDownloadFailed()
}
}
}
There is more to this download class as it will download the actual file once the first api call is done. Since the problem happens before that code would be executed, I did not include it in the sample code.
The log from Proxyman shows that the API call went out at (minutes:seconds) 46:06, 47:13, 48:21, 49:30, 50:44, 52:06, 53:45
It looks like the request gets repeated with intervals that are just over 1 minute.
There is an API field where I can put any value and it will be echoed back to me by the server. I put a timestamp there generated with CACurrentMediaTime() and log in Proxyman shows that indeed its the same API call so there is no way my code is getting called multiple times. It seems as though the iOS networking layer is re-issuing the http request because the server is taking a very long time to respond. This ends up causing problems on the server and the API fails.
Any help would be greatly appreciated.
This sounds a lot like TCP retransmission. If the client sends a TCP segment, and the server does not acknowledge receipt within a short span of time, the client assumes the segment didn't make it to the destination, and it sends the segment again. This is a significantly lower-level mechanism than URLSession.
It's possible the HTTP server application this API is using (think Apache, IIS, LigHTTPd, nginx, etc.) is configured to acknowledge with the response data to save packeting and framing overhead. If so, and if the response data takes longer than the client's TCP retransmission timeout, you will get this behavior.
Do you have a packet capture of the connection? If not, try collecting one with tcpdump and reviewing it in Wireshark. If I'm right, you will see multiple requests, and they will all have the same sequence number.
As for how to fix it if that's the problem, I'm not sure. The server should acknowledge requests as soon as they are received.
I think the problem is in using URLSessionConfiguration.background(withIdentifier:) for this api call.
Use this method to initialize a configuration object suitable for transferring data files while the app runs in the background. A session configured with this object hands control of the transfers over to the system, which handles the transfers in a separate process. In iOS, this configuration makes it possible for transfers to continue even when the app itself is suspended or terminated.
So the problem is that the system is retrying your request unnecessarily because of this wrong API usage.
Here's what I recommend -
Use default session configuration (NOT background).
Do this api call that initiates this long job, do NOT have client wait on this job, from server side return a job_id back to client as soon as this job is initiated.
Client can now poll server every X seconds using that job_id value to know about the status of the job, even can show progress on client side if needed.
When job is completed, and client polls next time, it gets the download URL for this big file.
Download the file (using default / background session configuration as you prefer).
I need to upload a big json (containing a UIImage as base64) file to a server and would like to track progress. I tried with Alamofire, but it seems as the file is uploaded first and only the response is progress-able. I can't use multipart as the API does not support that. Is it even possible to get the upload progress when only a body-json is sent?
It seems to work with upload method and json converted to data. Used some random image and mock api for testing.
let imageData = UIImagePNGRepresentation(UIImage(named: "test")!)!
let json = ["test": imageData.base64EncodedString(options: .lineLength64Characters)]
let jsonData = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
self.sessionManager.upload(jsonData!, to: "https://demo1752132.mockable.io/test", method: .post).responseJSON { dataResponse in
}.uploadProgress { progress in
print("Upload Progress: \(progress.fractionCompleted)")
}
As I tried this already the answer to this is NO. I have succeeded with files progress whereas for body-json I couldn't able to track the progress.
I will try to explain my scenario which is may be similar to you. I have to upload multiple images with text on stream. For us, the API for media is different. I mean for each image I have to upload to server and get the response. In the final request, I have to send all collected urls along with text in body of json. In this scenario, I tracked all images uploading successfully and showed it well. When coming to the final request, I couldn't able to track it.
Will be happy to know If someone finds tracking it.
I am trying to make queries to get the fuel type and consumption of a specified car (the user enters both make and model) for an iOS app written in Swift.
The app is targeted for Spain, and I have found a website that allows the user to enter make and model, and it returns the details for that car (http://coches.idae.es/portal/BaseDatos/MarcaModelo.aspx). I have seen using the tool WireShark, that the query is based on POST instead of GET. But I am not quite sure how I can make the requests within the app I am developing, or how to handle the info that is sent to me back from the sender.
Is there any way to make those requests to the given website? If so, I would really appreciate some help on the subject, I am new in iOS development and am looking forward to learning as much as possible.
Thanks :)
Many people prefer to use AFNetworking for making HTTP requests. However you don't need to do that. You said that its a POST request. Setting that up is easy even without AFNetworking using NSMutableURLRequest. I'm assuming you have a link to the API and not just to the aspx page. My Spanish is pretty weak so I can't look up the API reference for you but here's how you can make the request and receive data from the server. You will have to put the correct values and parse the responses:
let request = NSMutableURLRequest(URL: NSURL(string: "/* Paste URL here */")!)
request.HTTPMethod = "POST"
// Do this as many times are required for filling in the headers.
request.addValue("/* The value for the HTTP header */", forHTTPHeaderField: "/*The header field like Accept-Type, etc..*/")
// If you need an HTTP body too then make the JSONObj as a dictionary or array or whatever and then
let data = NSJSONSerialization.dataWithJSONObject(JSONObj, options: [])
request.HTTPBody = data // This needs to be NSData.
// Now make the request.
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, { (data, response, error) -> Void in
if error == nil
{
assert(data != nil)
let JSON = NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [NSObject: AnyObject]
// If you are using swift 2 this needs to be in a do try catch statement.
// TODO: Use JSON for whatever.
}
else
{
print(error!.localizedDescription)
}
}
task?.resume()
Let me know if you have any other questions or if the API doesn't use JSON or is completely different.
I'm working with some API data that gets updated frequently.
I recently discovered that the data does not update properly on the phone, when it's updated on the server.
After hours on hours trying to troubleshoot this, I finally simply tried deleting the app from my phone, and reinstalling in. And it worked.
After some further testing I discovered that it's printing out old JSON.
Once I delete the app and reinstall it, it successfully prints out the correct updated JSON.
From that I gathered that it's probably an issue with the phone caching the old JSON data somehow.
So how can I go about clearing this cache in swift? or forcing it to make a new request.
(I'm using swiftyJson, although I don't think that has anything to do with this specific problem)
I did find one other question like this, but it's old (2014 in Obj-C) and there was no answers.
Here's how I get my data:
var request = NSURLRequest(URL: formulaAPI!)
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
var formula = JSON(data: data!)
// Loop through the api data.
for (index: String, portfolio: JSON) in formula["portfolio"] {
// Save the data into temporary variables
tempStockName = portfolio["name"].stringValue
tempTicker = portfolio["ticker"].stringValue
tempPurchasePrice = portfolio["purchase_price"].floatValue.roundTo(2)
tempWeight = portfolio["percentage_weight"].floatValue
latestAPIPrice = portfolio["latest_price"].floatValue.roundTo(2)
tempDaysHeld = portfolio["days_owned"].intValue
// Continues on for quite a while, but the data in the above segment is definitely getting filled with old JSON data, so the issue is arising before this point
}
I tried changing my request to the following:
var request = init(formulaAPI: NSURL, cachePolicy: NSURLRequestCachePolicy, timeoutInterval: NSTimeInterval)
But that causes an error: "Use of local variable 'request' before it's declaration"
Any help figuring this out would be greatly appreciated!
Instead of creating your request with,
NSURLRequest(URL: formulaAPI!)
you should use the following method so that you can explicitly set the cache policy.
var request = NSURLRequest(URL: formulaAPI!, cachePolicy: .ReloadIgnoringLocalCacheData, timeoutInterval: 30)
NSURLRequest(URL:) uses the default came policy, NSURLRequestUseProtocolCachePolicy, and a time out interval of 60 seconds.