I currently give some users access to my app via TestFlight so that they can test it. On every iPhone the App works perfectly without any problems but on the iPhone 5 the app crashes every time. In the xCode simulator everything works properly but not on a real device.
It looks like Alamofire causes the crash. The problem is in Alamofire>Source>Features>ResponseSerialization.swift>response(queue:completionHandler:)
Here is the code from the function:
/// Adds a handler to be called once the request has finished.
///
/// - parameter queue: The queue on which the completion handler is dispatched.
/// - parameter completionHandler: The code to be executed once the request has finished.
///
/// - returns: The request.
#discardableResult
public func response(queue: DispatchQueue? = nil, completionHandler: #escaping (DefaultDataResponse) -> Void) -> Self {
delegate.queue.addOperation {
(queue ?? DispatchQueue.main).async {
var dataResponse = DefaultDataResponse(
request: self.request,
response: self.response,
data: self.delegate.data,
error: self.delegate.error
)
dataResponse.add(self.delegate.metrics)
completionHandler(dataResponse)
}
}
return self
}
completionHandler(dataResponse) looks like it is the problem.
Below there is also a screenshot from xCode
Is this a Alamofire related problem? Because on every other device (5s, 6 Plus, 7 Plus, SE and 7) it works without any problems. The crash occur when the iPhone is connected to WiFi and when it uses the mobile network.
Thanks for any tips!
EDIT:
This should be the code section that is called when starting the application:
Alamofire.request("https://app.site.tld/mobile/ios", parameters: parameters).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.data)")
if response.response != nil{
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)")
let weatherDataArr = utf8Text.components(separatedBy: "~")
guard let hash = String(weatherDataArr[0]) else {
completion(nil)
return
}
do {
//working with received data.
//let currData: wData = try wData(hash: hash, ....)
completion(currData)
} catch {
print("error creating Object: \(error)")
completion(nil)
}
}
else {
completion(nil)
}
}else {
completion(nil)
}
}
Just found a warning in xCode that says:
'catch' block is unreachable because no errors are thrown in 'do' block
First simplify to remove clutter
Alamofire.request("https://app.site.tld/mobile/ios", parameters: parameters).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.data)")
var result: wData? = nil
defer { completion(result) }
guard response.response != nil else { return }
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Data: \(utf8Text)")
let weatherDataArr = utf8Text.components(separatedBy: "~")
guard let hash = String(weatherDataArr[0]) else { return }
//working with received data.
//let currData: wData = try wData(hash: hash, ....)
result = currData
}
}
Then we can ask ourselves
How are you 100% sure there are elements in weatherDataArr? If not, use guard let hash = weatherDataArr.first else { return }
What happens under //working with received data.?
Related
I am trying to do some parsing of HTML on client side using Swift inside Xcode Project. I first tested this function inside playgrounds for a variety of URLs, and it downloads instantly for all my use cases. However, running this inside my Xcode project for iOS (even when disabling ATS in my info.plist), the URLSession will not download anything for many of the URLs to common websites that worked in playgrounds. It will still download some, such as the html of apple.com. Can anybody explain what I might be missing or need to enable/disable to get this to work.
func fetchHTMLString(url: URL) {
let task = URLSession.shared.downloadTask(with: url) { localURL, urlResponse, error in
if let localURL = localURL {
if let string = try? String(contentsOf: localURL) {
print("String here")
self.sortData(htmlString: string)
} else {
print("couldnt get as string")
}
}
}
task.resume()
print("going")
}
Update, I attempted to change this function to use URLSession data task, and was able to successfully download. I am, however, now just curious to find out why this would allow the download to complete with data task and not with download task. Here's the code that works
func fetchHTMLString(url: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error \(error.localizedDescription)")
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
if let string = String(data: data, encoding: .utf8) {
print(string)
self.sortData(htmlString: string)
print("String here")
} else {
print("couldn't get as string")
}
} else {
print("Error \(response.statusCode)")
}
} else {
print("No data or error returned.")
}
}
task.resume()
print("going")
}
```
I need to make 2 API calls simultaneously. I have 2 URLs for the calls, and if one of the calls will return any error I want to stop all the code execution.
How I tried to do it:
I have a function called performRequest() with a completion block. I call the function in my ViewController to update the UI - show an error/or a new data if all was successful. Inside it I create a URLSession tasks and then parse JSON:
I created an array with 2 urls:
func performRequest(_ completion: #escaping (Int?) -> Void) {
var urlArray = [URL]()
guard let urlOne = URL(string: "https://api.exchangerate.host/latest?base=EUR&places=9&v=1") else { return }
guard let urlTwo = URL(string: "https://api.exchangerate.host/2022-05-21?base=EUR&places=9") else { return }
urlArray.append(urlOne)
urlArray.append(urlTwo)
}
Then for each of the url inside the array I create a session and a task:
urlArray.forEach { url in
let session = URLSession(configuration: .ephemeral)
let task = session.dataTask(with: url) { data, _, error in
if error != nil {
guard let error = error as NSError? else { return }
completion(error.code)
return
}
if let data = data {
let printData = String(data: data, encoding: String.Encoding.utf8)
print(printData!)
DispatchQueue.main.async {
self.parseJSON(with: data)
}
}
}
task.resume()
}
print("all completed")
completion(nil)
}
For now I receive print("all completed") printed once in any situation: if both tasks were ok, if one of them was ok or none of them.
What I want is to show the print statement only if all tasks were completed successfully and to stop executing the code if one of them returned with error (for example if we will just delete one of the symbols in url string which will take it impossible to receive a data).
How can I do it correctly?
I'm currently porting an Android app over to iOS and I've noticed a significant decrease in performance for my HTTPS post requests. On Android, using Java HttpsURLConnection objects, post requests would take about 0.5-1.0 seconds on average (for transferring a maximum of about 100 characters). On Swift, using URLSession.shared.dataTask to perform the same post requests takes anywhere from half a second to 15 seconds, sometimes timing out.
One trend I've noticed is that, while running the app, the various requests that are made are either all slow (> 5 seconds) or all faster. The time for requests sporadically changes every time the app is restarted.
Below is my current dataTask code. getResponse and onSuccess are functions for response handling, but I've found that they aren't what's slowing down the requests. From a few "tests" (print statements), it seems like the slow-downs occur as network connections are first being established.
func execute() {
guard let url = URL(string: "https://website.com") else {
print("ERROR > INVALID URL")
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let postDataString = "data"
guard let requestBody: Data = postDataString.data(using: String.Encoding.utf8) else {
print("ERROR > FAILED TO ENCODE POST STRING")
return
}
request.httpBody = requestBody
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if let unwrappedError = error {
print("ERROR > FAILED TO CONNECT TO SERVER: \(error)")
return
}
guard let unwrappedData = data else {
print("ERROR > FAILED TO GET DATA")
return
}
guard let dataString = String(data: data, encoding: String.Encoding.utf8)?.replacingOccurrences(of: "+", with: "%2B") else {
print("ERROR > FAILED TO DECODE RESPONSE TO STRING")
return
}
let dataStringLines: [String] = dataString.components(separatedBy: "\n")
guard let unwrappedGetResponse = self.getResponse else {
print("ERROR > getResponse FUNCTION UNINITIALIZED")
return
}
let success: Bool = unwrappedGetResponse(dataStringLines)
if (success) {
guard let unwrappedOnSuccess = self.onSuccess else {
print("ERROR > onSuccess FUNCTION UNINITIALIZED")
return
}
DispatchQueue.main.async {
unwrappedOnSuccess()
}
}
else {
print("ERROR > BAD SERVER RESPONSE")
}
}
task.resume()
}
Some additional information: I'm testing the code with an iPhone 5S running iOS 12.4.5. The app is targeting iOS 10.0.
Any idea on what could cause such inconsistent performance?
I'm trying to grab a value from a URL inside a XCUI Test. However it's not outputting so I'm not sure if there's anything I'm supposed to be doing aside from I've already tried in the code below:
import XCTest
class ExampleUITests: XCTestCase {
func testExample() {
print("We are in XCUITest textExample right now")
let urlString = "https://api.ipify.org/"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data
else {
print("error found in data")
return
}
print("*******")
let outputStr = String(data: data, encoding: String.Encoding.utf8) as String!
print (outputStr)
}.resume()
}
}
The problem is, response from server is returned after test is finished. You should add waiting for network response in the test.
At the start of the test add:
let expectation = expectationWithDescription("")
At the end of the test add:
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
And in the network completion block add:
expectation.fulfill()
You can get more info about waining for async task in the test for example here How can I get XCTest to wait for async calls in setUp before tests are run?.
My application receives location updates, and when it does (if they are relevant), I call an API asynchronously using a completion handler. When the application opens, the completion handler responds only if there was no request that finished before (two requests come in at the same time usually). When I debug, after the first 2-3 requests (which come in at the same time) where everything works, when the location update passes as relevant, the whole completion handling part of code gets skipped.
This is how I call the completion handler:
if conditions {
let lat = Float(loc.lat)
let long = Float(loc.long)
// calls function using completion handler in order to add new location
BusStations.allBusStations(lat: lat, long: long) { (busStations, error) in
if let error = error {
// got an error in getting the data
print(error)
return
}
guard let busStations = busStations else {
print("error getting all: result is nil")
return
}
if !busStations.stops.isEmpty || self.locations.isEmpty {
// do stuff
}
}
}
This is how I make the API call:
static func allBusStations (lat: Float, long: Float, completionHandler: #escaping (BusStations?, Error?) -> Void) {
let endpoint = BusStations.endpointForBusStations(lat: lat, long: long)
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not construct URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
completionHandler(nil, error)
return
}
guard error == nil else {
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let stations = try decoder.decode(BusStations.self, from: responseData)
completionHandler(stations, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}
What am I doing wrong? Any help would be appreciated.
I would try to dispatch the completion handler to global or main queue to see if it is deferred by system to execute on a queue of lower levels.