Saving CoreData to a Web Server with Swift 3.0 - swift

This question is related to: Swift Core Data Sync With Web Server.
I have followed the steps that have been mentioned in the question above however I am unable to apply the third step to my current project.
I currently have a class called Records
class Records {
static let shared = Records()
var records = [Record]()
let context = PersistenceServce.context
let request = NSFetchRequest<Record>(entityName: "Record")
func recordData() -> [Record] {
do {
records = try context.fetch(Record.fetchRequest())
}catch {
print("Error fetching data from CoreData")
}
return records
}
}
and here is how I display the data on my tableViewController.
func getData() {
records = Records.shared.recordData()
self.tableView.reloadData()
}
I do know how save data to a web server as this tutorial explains: https://www.simplifiedios.net/swift-php-mysql-tutorial/ as well as check for internet connection. However I am unsure how to apply it to the CoreData where there are multiple data involved.
If anyone could direct me to a solution or an explain how this can be achieved I'd very much appreciate it.

The question that you have linked is not trying to explain how to communicate with a web server. It is explaining how to store data in core data and tag/mark it in a way that you know which records have been sent to the web server or not.
So the Predicate will fetch all records that have not been sent to the web server and allow you to send them when you have an internet connection available.
Communicating with a web server can be a broad topic and will depend on your web server and API setup, so it is too much to explain here fully. I refer you to some free online resources that will help you understand networking in Swift.
Udacity - Networking with Swift
Ray Wenderlich - Alamofire Tutorial
Stack Overflow - Swift POST Request
Here is an example of a POST Request from the StackOverflow answer above
var request = URLRequest(url: URL(string: "http://test.tranzporthub.com/street45/customer_login.php")!)
request.httpMethod = "POST"
let postString = "user_id=chaitanya3191#gmail.com&password=123"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
}
task.resume()
Using code similar to this, you should be able to send data to your web server, then your web server can do whatever it likes with it.
UPDATE:
To encode your parameters to JSON you can use the following code as a guide
var dictionary = [
"username": "Test User",
"password": "Password"
]
if let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: []) {
// jsonData is a byte sequence, to view it you would need to convert to string
print(String(bytes: jsonData, encoding: String.Encoding.utf8))
}
Which would output:
Optional("{\"username\":\"Test User\",\"password\":\"Password\"}")
Note: you would send it as data, not the string version. so your code might look like this:
request.httpBody = jsonData

Related

Alamofire Bad Network Connection | Swift

I would like to implement a way to check if the network is weak or disconnected during network calls I perform using Alamofire 4.9 - the following is what I am currently attempting to do, but I have noticed that if the network it off it never jumps to this line:
URLError.Code.notConnectedToInternet
why does this occur, is there a better way of attempting this?
//Fetch new data
guard let url = URL(string: "test.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "value1=test1&value2=test2".data(using: .utf8)
URLSession.shared.dataTask(with: request) { [self] data, _, error in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let res = try decoder.decode([Structure].self, from: data)
}
}
catch {
if let err = error as? URLError, err.code == URLError.Code.notConnectedToInternet {
// No internet
} else {
print(error)
}
}
}.resume()
There are a variety of errors that can be produced by poor or entirely broken connectivity. .notConnectedToInternet is simply one of them. There is no single, exhaustive list provided by Apple of which errors may be returned when there are connectivity issues, so you'll want to experiment and see what you can produce. Additionally, there are a variety of errors which you can probably throw into a "network connectivity" bucket, such as .dnsLookupFailed or .cannotConnectToHost. Ultimately it may not be worth it to differentiate different types of URLErrors.
By the way, you should use Alamofire 5 if you can, as Alamofire 4 is no longer supported.

HTTP Request in Swift with POST method for a large amount of data

I have a data structure in which a very large amount of images (~500) are "recorded" from the screen and sent to a server for processing. This data structure has caused an abundance of memory issues within my app.
My current solution is to use CoreData for the storage and retrieval of each structure, but how would I be able to send this structure to the server without loading the images into memory?
I am currently using URLSession data tasks for POSTing, which works for structures with a small number of images since I load them into memory for JSON encoding. Each frame also needs to have high resolution. The "recording" is done using snapshot() from ARSCNView since the renders need to be in each frame as well.
Here is the code I use for POST requests:
/*
Configures POST request for the server
Returns a URLRequest configured to be POSTed containing a JSON encoded DISCRequest object
*/
private func generatePostRequest(from images: [Data], dataList: [[Int]])->URLRequest{
var request=URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "content-type")
request.httpMethod = "POST"
request.httpBody = try? JSONEncoder().encode(DISCRequest(images: images, dataList: dataList))
return request
}
/*
Performs a POST request to the server.
Success: passes results to the completion handler
Fail: prints error, nothing returned
Param:
-imgs: array of images that make up input video
-dataList: screen locations of each faceAnchor index in order
-sender: view controller to handle possible error handling for reference
-completion: closure for the call, manages returned results
*/
func postToServer(imgs: [Data], dataList: [[Int]], _ sender: ViewController, completion: #escaping([UIImage])->Void){
let request=generatePostRequest(from: imgs, dataList: dataList)
URLSession.shared.dataTask(with: request) { data, response, error in
if error != nil {
DispatchQueue.main.async {
//sender.videoError(title: "Unable to process video", message: "Unable to connect to Network", presentVideo: false)
}
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
DispatchQueue.main.async {
//sender.videoError(title: "Unable to process video", message: "Error with the response, unexpected status code: \((response as! HTTPURLResponse).statusCode)", presentVideo: false)
}
return
}
guard let data=data else{
print("No data in response.")
return
}
if let decoded=try? JSONDecoder().decode(DISCRequest.self, from: data){
completion(decoded.images.map({ i in
UIImage(data: i)!
}))
}
}.resume()
}
Where DISCRequest is just an in-memory structure that houses the images/other data. The CoreData equivalent (SaveData) structure has the same properties as DISCRequest.

pho.to API Request Failing in Swift

Im currently trying to work with the pho.to API in my iOS application. I am experimenting with making simple requests according to the documentation, however I cannot seem to get the request to go through successfully. Inside my API client file, I have this code:
let dataStr = """
<image_process_call>
<image_url>http://developers.pho.to/img/girl.jpg</image_url>
<methods_list>
<method order="1">
<name>desaturation</name>
</method>
<method order="2">
<name>caricature</name>
<params>type=1;crop_portrait=true</params>
</method>
</methods_list>
<thumb1_size>100</thumb1_size>
</image_process_call>
"""
let encodedStr = dataStr.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "")
let signData = encodedStr.hmac(key: key)
let urlStr = "https://opeapi.ws.pho.to/addtask/?app_id=\(appId)&key=\(key)&sign_data=\(signData)&data=\(encodedStr.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!))"
The HMAC encoding is being done according to this Stack Overflow post. Unfortunately when making a request to this URL using URLSession I get this response:
<?xml version=\"1.0\"?>\n<image_process_response><status>SecurityError</status><err_code>614</err_code><description>Error in POST parameters: one or more parameters (DATA , SIGN_DATA or APP_ID) are empty</description></image_process_response>
I feel like my issue is more related to actually forming the request rather than something specific to the API itself. I know my code is a little messy, however I was hoping that somebody could point me in the right direction in terms of making a request like this. Thanks!
As per their documentation you can see that data sent over from POST requests are in body (In cURL calls -d specifies the body of the request)
You are sending params/data in query, which the pho.to API doesn't accept, hence the error.
Here's a sample on how you can do:
let defaultSessionConfiguration = URLSessionConfiguration.default
let defaultSession = URLSession(configuration: defaultSessionConfiguration)
// Setup the request with URL
let url = URL(string: "https://opeapi.ws.pho.to/addtask")!
var urlRequest = URLRequest(url: url)
// Convert POST string parameters to data using UTF8 Encoding
let postData = yourXMLString.data(using: .utf8)
// Set the httpMethod and assign httpBody
urlRequest.httpMethod = "POST"
urlRequest.httpBody = postData
// Create dataTask
let dataTask = defaultSession.dataTask(with: urlRequest) { (data, response, error) in
// Handle your response here
}
// Fire the request
dataTask.resume()

How to handle 500 http errors

I am trying to access the custom server response body for 500 errors in class HTTPURLResponse (URLResponse) using URLSession.shared.dataTask function. I can only have access to statusCode and allHeaderFields but it doesn't seem to help.
The equivalent in java for ex. is HttpURLConnection.getErrorStream(), but I cannot find something similar in pure swift (I would like to solve this without using 3rd party libs).
How can I get the text response for the 500 error?
let task = session.dataTask(with: urlRequest) { data, response, error in
if let data = data, let response = response as? HTTPURLResponse {
switch response.statusCode {
case 500...599:
let yourErrorResponseString = String(data: data, encoding: .utf8)
default:
break
}
}
}
There is no way you can get the response data out of HTTPURLResponse. It only contains header information.
If you want to retrieve the response data, you need to use something like dataTask(with:completionHandler:) to send your request. That function passes (Data?, URLResponse?, Error?) to your completion handler. The data parameter of the completion handler is the data returned by the server.
For example:
import Foundation
let url = URL(string: "http://httpstat.us/500")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data, let response = response as? HTTPURLResponse else {
return
}
switch response.statusCode {
case 500...599:
print(String(data: data, encoding: .utf8) ?? "No UTF-8 response data")
default:
print("not a 500")
}
}
task.resume()
Edit: Removed force unwrap according to #Rob‘s suggestion
There is no way to get more details about a 500 error from the client side.
500 is "Internal Server Error" and it's intentionally vague and unhelpful since disclosing information about the cause of the error would assist hackers in compromising the site.
However you can get a great deal of information about the error from the server log and the log for whatever was processing your code on the server side (php, etc.).
If you have access to the server logs and don't see enough information, you can increase the level of logging for the server and application.

Partially downloading data in Swift

I'm trying to develop a download accelerator in Swift. It should get the file's size and divide it to n parts. Then it should download them at once by running multiple threads, and then merge the parts.
I read C# - Creating a Download Accelerator, unfortunately it doesn't help me.
I can do the multiple thread part easily by
DispatchQueue.main.async {
// The new thread
}
but the other part is harder. I usually download a file like this:
try Data(contentsOf: URL(string: assetsUrl!)!)
or I can do the thing that is explained in this answer
class Downloader {
class func load(url: URL, to localUrl: URL, completion: #escaping () -> ()) {
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = try! URLRequest(url: url, method: .get)
let task = session.downloadTask(with: request) { (tempLocalUrl, response, error) in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Success: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: localUrl)
completion()
} catch (let writeError) {
print("error writing file \(localUrl) : \(writeError)")
}
} else {
print("Failure: %#", error?.localizedDescription);
}
}
task.resume()
}
}
But this is not C - it's very simplistic and doesn't accept many arguments. How can I make it get "first 200_000 bytes" from the server?
First of all, the server needs to implement HTTP range requests. If it doesn't, and you don't control the server, then you will not be able to do this.
If the server supports HTTP range requests, then you need to specify the range with request headers, as explained here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
The essentials are that you first send a HEAD request to figure out whether the server supports HTTP range requests. This is determined by whether the response includes the Accept-Ranges header, with a non-zero value.
If the server supports HTTP range requests, then you can make a request for the resource, with the Range header set for example to a value of bytes=0-1023 (depends which format the Accept-Ranges header specified, in this case bytes)