I have the following function to load data. The function worked just fine till a couple of days ago. The app crashes trying to load any url, see code below and screenshot. Coding ios/swift just a few days a year, it pretty hard to figure out what's wrong...
class func loadDataFromURL(_ url: URL, completion:#escaping (_ data: Data?, _ error: NSError?) -> Void) {
let session = URLSession.shared
// Use NSURLSession to get data from an NSURL
let loadDataTask = session.dataTask(with: url, completionHandler: { (data: Data?, response: URLResponse?, error: NSError?) -> Void in
if let responseError = error {
completion(nil, responseError)
} else if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode != 200 {
let statusError = NSError(domain:"com.xyz", code:httpResponse.statusCode, userInfo:[NSLocalizedDescriptionKey : "HTTP status code has unexpected value."])
completion(nil, statusError)
} else {
completion(data, nil)
}
}
} as! (Data?, URLResponse?, Error?) -> Void)
loadDataTask.resume()
}
Use below code with latest Swift 3.0 syntax it works on Xcode 8.2:-
func loadDataFromURL(_ url: URL, completion:#escaping (_ data: Data?, _ error: NSError?) -> Void) {
let session = URLSession.shared
// Use NSURLSession to get data from an NSURL
let loadDataTask = session.dataTask(with: url) { (data, response, error) in
if let responseError = error {
completion(nil, responseError as NSError?)
} else if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode != 200 {
let statusError = NSError(domain:"com.xyz", code:httpResponse.statusCode, userInfo:[NSLocalizedDescriptionKey : "HTTP status code has unexpected value."])
completion(nil, statusError)
} else {
completion(data, nil)
}
}
}
loadDataTask.resume()
}
Related
I want to test an API without making a server call so I am mocking URLSession and URLSessionDataTask so that I can inject it in my API class.
class MockURLSession: URLSession {
private let mockTask: MockTask
var cachedUrl: URL?
init(data: Data?, urlResponse: URLResponse?, error: Error?) {
mockTask = MockTask(data: data, urlResponse: urlResponse, error:
error)
}
override func dataTask(with url: URL, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
self.cachedUrl = url
mockTask.completionHandler = completionHandler
return mockTask
}
}
class MockTask: URLSessionDataTask {
private let data: Data?
private let urlResponse: URLResponse?
var completionHandler: ((Data?, URLResponse?, Error?) -> Void)!
init(data: Data?, urlResponse: URLResponse?, error: Error?) {
self.data = data
self.urlResponse = urlResponse
}
override func resume() {
DispatchQueue.main.async {
self.completionHandler(self.data, self.urlResponse, self.error)
}
}
}
Here when I am running this test it's going in my API class calling the getMovies method. Instance I am getting over there is kind of MockURLSession which is fine. Next moment it gives this ApiTests testGetMoviesSuccessReturnsMovies] : failed: caught "NSInvalidArgumentException", "-[MyAppTests.MockTask error]: unrecognized selector sent to instance 0x600002010500
func testGetMoviesSuccessReturnsMovies() {
let jsonData = "[{\"title\": \"Spider Man Far From Home\",\"detail\": \"The first Spider-Man featuring Tom Holland in the iconic role\"}]".data(using: .utf8)
var mockURLSession = MockURLSession(data: jsonData, urlResponse: nil, error: nil)
let apiRespository = APIRepository(session: mockURLSession)
let moviesExpectation = expectation(description: "movies")
var moviesResponse: Result<[Movie]>?
apiRespository.getMovies { (movies) in
moviesResponse = movies
moviesExpectation.fulfill()
}
waitForExpectations(timeout: 10) { (error) in
XCTAssertNotNil(moviesResponse)
}
}
Here is my protocol extension for api
extension Gettable {
func get<T:Decodable>(with decodingType: T.Type, url: String, session: URLSession, completion:#escaping(Result<T>) -> Void) {
let dataTask = session.dataTask(with: URL(string: url)!) { (data, response, error) in
guard data != nil && error == nil else {
return
}
do {
let decoder = JSONDecoder()
let parsedObj = try decoder.decode(T.self, from: data ?? Data())
completion(Result.success(parsedObj))
}
catch let parsedError {
completion(Result.failure(parsedError))
}
}
dataTask.resume()
}
}
Your help will be highly appreciated.
You are not overriding the method dataTask(with:completionHandler:) in your MockURLSession. So, the original dataTask(with:completionHandler:) of URLSession is called, which internally calls dataTaskForRequest:completion:.
If you are successfully overriding existing methods in the parent class, Swift compiler claims to prefix override keyword.
Move your nested dataTask(with:completionHandler:) out of init(data:urlResponse:error:).
init(data: Data?, urlResponse: URLResponse?, error: Error?) {
mockTask = MockTask(data: data, urlResponse: urlResponse, error:
error)
}
override func dataTask(with url: URL, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
self.cachedUrl = url
mockTask.completionHandler = completionHandler
return mockTask
}
For edited part...
Seems inheritance of error property is not working properly. (Which may be a bug of Swift compiler related to bridging NSError and Error.)
Please try this:
class MockTask: URLSessionDataTask {
private let data: Data?
private let urlResponse: URLResponse?
private let _error: Error?
override var error: Error? {
return _error
}
var completionHandler: ((Data?, URLResponse?, Error?) -> Void)!
init(data: Data?, urlResponse: URLResponse?, error: Error?) {
self.data = data
self.urlResponse = urlResponse
self._error = error
}
override func resume() {
DispatchQueue.main.async {
self.completionHandler(self.data, self.urlResponse, self.error)
}
}
}
i have downloaded the twilio quickstarter project and followed the docx and create the function for chat and pass the url to the given place in project, now when i run the app the app crashes with this error,Thread 10: EXC_BREAKPOINT (code=1, subcode=0x1048d85b0). This error comes in their code when it try to seralize the data from the request. How can i get out from this issue?. This is the code where issue is occurring,
struct TokenUtils {
static func retrieveToken(url: String, completion: #escaping (String?, String?, Error?) -> Void) {
if let requestURL = URL(string: url) {
let session = URLSession(configuration: URLSessionConfiguration.default)
let task = session.dataTask(with: requestURL, completionHandler: { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String:String]
let token = json["token"]
let identity = json["identity"]
completion(token, identity, error)
}
catch let error as NSError {
completion(nil, nil, error)
}
} else {
completion(nil, nil, error)
}
})
task.resume()
}
}
}
This is what it shows in debugger,
I am studying the swift and I update the xcode to current version (8.1). So the source that I saved was changed. When I ran the code. It cracked and I cannot fixed it. It showed "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)" on the last line. This is a code that I have studied from web.
Thank you very much.
let urlString = "http://swapi.co/api/people/1/"
let session = URLSession.shared
let url = URL(string: urlString)!
session.dataTask(with: url, completionHandler: { (data: Data?, response:URLResponse?, error: NSError?) -> Void in
if let responseData = data {
do {
let json = try JSONSerialization.jsonObject(with: responseData, options: JSONSerialization.ReadingOptions.allowFragments)
if let dict = json as? Dictionary<String, AnyObject> {
if let name = dict["name"] as? String, let height = dict["height"] as? String, let birth = dict["birth_year"] as? String, let hair = dict["hair_color"] as? String {
let person = SWPerson(name: name, height: height, birthYear: birth, hairColor: hair)
print(person.name)
print(person.height)
print(person.hairColor)
print(person.birthYear)
if let films = dict["films"] as? [String] {
for film in films {
print(film)
}
}
}
}
} catch {
print("Could not serialize")
}
}
} as! (Data?, URLResponse?, Error?) -> Void) .resume()
You can save much time if you simply read the documentation
Press ⇧⌘0 (zero, not O)
Type dataTask, select dataTask(with:completionHandler:) and press return.
You will get
func dataTask(with url: URL,
completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
Do you see the slightly difference in the error parameter?
So the correct syntax in your code is
session.dataTask(with: url, completionHandler: { (data: Data?, response:URLResponse?, error: Error?) -> Void in
...
}).resume()
There is no type cast at the end.
Further the type annotations in the completion block are not needed because the compiler infers the types. The error doesn't occur if you just write
session.dataTask(with: url, completionHandler: { (data, response, error) in
...
}).resume()
The shortest form is using the trailing closure syntax
session.dataTask(with: url) { (data, response, error) in
...
}.resume()
Hi I just migrated to alamofire 4 and I just want to send the error coming from the server, I found a couple ways but I just want to make sure that this is the correct way, here is my custom responseobject class
public protocol ResponseObject {
init?(response: HTTPURLResponse, representation: Any)
}
enum BackendError: Error {
case network(error: Error)
case dataSerialization(error: Error)
case jsonSerialization(error: Error)
case xmlSerialization(error: Error)
case objectSerialization(reason: String)
}
extension DataRequest {
public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
func responseObject<T: ResponseObject>(
queue: DispatchQueue? = nil,
completionHandler: #escaping (DataResponse<T>) -> Void)
-> Self
{
let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
guard error == nil else {
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
debugPrint(result)
return .failure(BackendError.network(error: error!))
}
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
guard case let .success(jsonObject) = result else {
return .failure(BackendError.jsonSerialization(error: result.error!))
}
guard let response = response, let responseObject = T(response: response, representation: jsonObject) else {
return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized: \(jsonObject)"))
}
return .success(responseObject)
}
return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
I add a debugprint so see the error from the server and I see it but do I have to serialize de data again inside the error?? and how can I pass the message to my custom error?
I'm trying to create a NSURLSession task based on a tutorial I found online (https://www.raywenderlich.com/85528/user-accounts-ios-ruby-rails-swift#next_section) and I am getting the following error:
Cannot convert value of type '(NSData!, NSURLResponse!, NSError!) -> ()' to expected argument type '(NSData?, NSURLResponse?, NSError?) -> Void
at this block of code:
let task = session.dataTaskWithRequest(request) { (data: NSData!, response: NSURLResponse!, error: NSError!) in
The function where the issue belongs to can be found here
func sendRequest(request: NSURLRequest, completion:(NSData!, NSError!) -> Void) -> () {
// Create a NSURLSession task
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data: NSData!, response: NSURLResponse!, error: NSError!) in
if error != nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(data, error)
})
return
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode == 200 {
completion(data, nil)
} else {
var jsonerror:NSError?
if let errorDict = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error:&jsonerror) as? NSDictionary {
let responseError : NSError = NSError(domain: "HTTPHelperError", code: httpResponse.statusCode, userInfo: errorDict as? [NSObject : AnyObject])
completion(data, responseError)
}
}
}
})
}
The full code block can be found here (https://codeshare.io/uJPcX) at line 68.
Change
data:NSData!, response: NSURLResponse!, error: NSError!
to
data: NSData?, response: NSURLResponse?, error: NSError?
when using data or response etc further down you may have to write is as data! to unwrap the variable, but be careful because if the variable is nil it will crash, so you must check that it is not nil first