How to raise an Error for the completionHandler? - swift

I'm in process of unittesting a case where the completionHandler is throwing an error. But I'm not sure how to raise that error.
class MockErrorSession: URLSessionProtocol {
var nextDataTask = MockURLSessionDataTask()
var nextData: Data?
var nextError: Error?
func dataTask(with request: NSURLRequest, completionHandler: #escaping DataTaskResult) -> URLSessionDataTaskProtocol {
nextError = ?
completionHandler(nextData, successHttpURLResponse(request: request), nextError)
return nextDataTask as URLSessionDataTaskProtocol
}
}
I need somehow to populate nextError
I tried to do this,
enum MyError : Error {
case RuntimeError(String)
}
func throwError(_ message: String) throws {
throw MyError.RuntimeError(message)
}
nextError = try throwError("test") as! Error
Any advice please?

Your function dataTask(with:completionHandler:) does not seem to be marked with throws/rethrows in which case it seems the error is simply being passed to the completion block.
Since neither the function nor the closure "throws", you can't throw the error & as a result neither will you be able to do-try-catch it later anyways.
So as per my assumptions, just the following should suffice:
nextError = MyError.RuntimeError("test")
i.e.
enum MyError : Error {
case RuntimeError(String)
}
func dataTask(with request: NSURLRequest,
completionHandler: #escaping DataTaskResult) -> URLSessionDataTaskProtocol {
nextError = MyError.RuntimeError("test")
completionHandler(nextData,
successHttpURLResponse(request: request),
nextError)
return nextDataTask as URLSessionDataTaskProtocol
}

Related

Shorthand conversion from Result to Future in Swift?

I'd like to provide Combine counterparts to completion closures which is becoming very cumbersome. Is there a shorter way or extension that can convert the following:
extension MyType {
func send(with request: URLRequest, completion: #escaping (Result<MyResponse, MyError>) -> Void) {
// Logic
}
func send(with request: URLRequest) -> Future<MyResponse, MyError> {
Future<MyResponse, MyError> { promise in
send(with: request) { result in
switch result {
case let .success(response):
promise(.success(response))
case let .failure(error):
promise(.failure(error))
}
}
}
}
}
The Future method is just a wrapper to the completion closure method. I was hoping to do at least something like this:
Future<MyResponse, MyError> { send(with:request, completion: $0) }
Is there a more elegant way to do this since this will be applied in a lot of places in my library.
Note that the completion parameter of the first send overload has the type:
Result<MyResponse, MyError>) -> Void
Which is exactly the same type as promise, which is
Future<MyResponse, MyError>.Promise
Promise is just a type alias for (Result<Output, Failure>) -> Void.
So you can just do:
Future<MyResponse, MyError> { promise in
send(with: request, completion: promise)
}

Throwing errors from closure

I have this piece of code in my app:
func saveContact2(contact: String) throws {
let contactStore = CNContactStore()
contactStore.requestAccess(for: .contacts, completionHandler: {(granted, error) in
if granted && error == nil {
//...
} else {
if !granted {
throw contactErrors.contactAccessNotGranted(["Error","Access to Contacts is not granted."])
}
}
})
}
I'd like to throw all errors raising in closure to calling function.
Compiler shows error:
Invalid conversion from throwing function of type '(_, _) throws -> ()' to non-throwing function type '(Bool, Error?) -> Void'
Could anyone help me please with the right syntax?
You cannot throw errors from an #escaping closure that is called asynchronously. And this makes sense because your app has carried on with its execution and there’s no where to catch the error.
So, instead, adopt completion handler pattern yourself:
func saveContact2(_ contact: String, completion: #escaping: (Result<Bool, Error>) -> Void) {
let contactStore = CNContactStore()
contactStore.requestAccess(for: .contacts) { (granted, error) in
guard granted else {
completion(.failure(error!)
return
}
//...
completion(.success(true))
}
}
And then you’d call it like:
saveContact2(contactName) { result in
switch result {
case .failure:
// handler error here
case .success:
// handle confirmation of success here
}
}
If you’re using an old compiler that doesn’t have the Result type, it’s basically:
enum Result<Success, Failure> where Failure: Error {
case success(Success)
case failure(Failure)
}

Swift Generic func calling a Generic func

I have an interesting compile error when using one generic to call another. This post is a bit long but hopefully, it's not longer than needed to describe the problem.
I have defined a generic func that works great. I use it a lot and the pattern of use is often the same. I was trying to implement a new generic func that nests the existing generic func, but I'm getting a compile-time error.
For some context, here is how my API works with the generic now. My REST API makes a call the films() func, which hits the StarWarsAPI (swapi.co) and returns a list of all the Star Wars films they have in their database, as follows:
StarWarsAPI.shared.films(){ (films, error) in
for film in films {
print(film.title)
}
}
The films() function calls a generic (restCall()) which works great. Here is the definition of films():
public func films(completion: #escaping (_ films:[Film]?, _ error:StarWarsError?) -> Void) {
guard StarWarsAPI.isOperational else {
return completion(nil,StarWarsError.starWarsAPINotOperational)
}
restCall(fetchUrl: filmsUrl!, modelType: FilmResult()) { (filmResults, error ) in
completion(filmResults?.results, error)
}
}
Where the restCall (the generic) is defined as follows: (Note I'm using the Swift 4 Codable API)
public func restCall<T: Codable>(fetchUrl: URL, modelType: T, completion: #escaping (_ modelObject: T?, _ error:StarWarsError?) -> Void){
var fetchRequest = URLRequest(url: fetchUrl, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
fetchRequest.httpMethod = "GET"
fetchRequest.allHTTPHeaderFields = [
"content-type": "application/json",
"cache-control": "no-cache",
]
let session = URLSession.shared
let fetchDataTask = session.dataTask(with: fetchRequest) { (data, response, error) in
DispatchQueue.main.async { // return to main thread
var modelObject:T?
do {
let jsonDecoder = JSONDecoder()
modelObject = try jsonDecoder.decode(T.self, from: data)// FIXME: Something about decoding the People object is going wrong.
return completion(modelObject, nil)
}catch let error as NSError {
completion(nil, StarWarsError.decodingModelError(error: error))
}
}
}
fetchDataTask.resume()
}
So the above works great. I use it for rest functions films(), people(), ships(), etc. I use the same pattern for each rest call. I want to create a generic that I can use instead of explicit films(), people(), etc.
I've been trying to get the following to work with little success:
public func fetchAll<T: Result>(result:T, completionAll: #escaping (_ result:T?, _ error:StarWarsError?) -> Void) {
restCall(fetchUrl: result.urlPath!, modelType: T) { (finalResults, error ) in
completionAll(finalResults!, error)
}
}
Where Result type is the base type and is defined as follows:
public class Result {
var urlPath:URL?
}
public class FilmResult: Result, Codable {
var count:Int?
var next:String?
var previous:String?
var results:[Film]?
}
The error I'm getting is shown in the screenshot below - hopefully, it's clear.
Any help you can provide would be much appreciated!
Your call has to be
this was T previously ───┐
restCall(fetchUrl: result.urlPath!, modelType: result) { (finalResults, error ) in
Note the result instead of T.
Relatively minimal code to reproduce:
public func restCall<T>(fetchUrl: URL, modelType: T, completion: #escaping (_ modelObject: T?, _ error:String?) -> Void) { }
public func fetchAll<T>(result:T, completionAll: #escaping (_ result:T?, _ error:String?) -> Void) {
┌── should be result
restCall(fetchUrl: URL(string: "asdasd")!, modelType: T) { (finalResults, error ) in
completionAll(finalResults, error)
}
}

Custom Serialization finds incorrect "response" in Alamofire 4.0

I'm attempting to define a custom model serialization for Alamofire 4.0. So far I'm following the model presented used by responseJson and friends. Specifically, what I have so far is:
extension Alamofire.Request {
public static func serializeResponseModel<T:ModelObject>(response:HTTPURLResponse?, data:Data?, error:Error?) -> Alamofire.Result<T> {
switch serializeResponseJSON(options: [], response: response, data: data, error: error) {
case .success(let jsonObject):
do {
return .success(try T(json:jsonObject as! JSONObject))
}
catch {
return .failure(error)
}
case .failure(let error):
return .failure(error)
}
}
}
extension Alamofire.DataRequest {
public static func serializeResponseModel<T:ModelObject>() -> DataResponseSerializer<T> {
return DataResponseSerializer { _, response, data, error in
return Request.serializeResponseConcierge(response: response, data: data, error: error)
}
}
#discardableResult
public func responseModel<T:ModelObject>(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<T>) -> Void) -> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.serializeResponseModel(),
completionHandler: completionHandler
)
}
}
Unfortunately, the framework is somewhat poorly implemented and the line return response( is finding the response property (defined in Request) and not the appropriate response method (defined in DataRequest), which leads to the compile error:
Cannot call value of non-function type 'HTTPURLResponse?'
What am I missing here that allows this to work in the responseJson case, but not in my case?
Apparently the problem arose from over-generalization, and the compiler not being able to generate an appropriate type for DataRequest.serializeResponseModel() When I changed responseModel to the following and specified the appropriate type, things work as expected:
#discardableResult
public func responseModel<T:ModelObject>(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<T>) -> Void) -> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.modelResponseSerializer() as DataResponseSerializer<T>,
completionHandler: completionHandler
)
}

Trailing Closures on generics?

Hi I am trying to understand the following code from Alamofire. How can you initialise a struct with "{}" I know that you can call a closure with Trailing Closures. I know I am totally missing something, but what?
extension Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<T, NSError> { // What is this?
request, response, data, error in
guard error == nil else { return .Failure(error!) }
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
if let
response = response,
responseObject = T(response: response, representation: value)
{
return .Success(responseObject)
} else {
let failureReason = "JSON could not be serialized into response object: \(value)"
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
The struct ResponseSerializer from Alamofire
public struct ResponseSerializer<Value, Error: ErrorType>: ResponseSerializerType {
/// The type of serialized object to be created by this `ResponseSerializer`.
public typealias SerializedObject = Value
/// The type of error to be created by this `ResponseSerializer` if serialization fails.
public typealias ErrorObject = Error
/**
A closure used by response handlers that takes a request, response, data and error and returns a result.
*/
public var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Result<Value, Error>
/**
Initializes the `ResponseSerializer` instance with the given serialize response closure.
- parameter serializeResponse: The closure used to serialize the response.
- returns: The new generic response serializer instance.
*/
public init(serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Result<Value, Error>) {
self.serializeResponse = serializeResponse
}
}
Your question can be greatly pared down (and should have been). Here is the relevant declaration of ResponseSerializer:
public struct ResponseSerializer<Value, Error: ErrorType>: ResponseSerializerType {
public init(serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Result<Value, Error>) {
self.serializeResponse = serializeResponse
}
}
So this initializer, init(serializeResponse:), takes one parameter — a function taking four parameters and returning one parameter (of the specified types).
Thus, we can initialize like this:
func f (request:NSURLRequest?, response:NSHTTPURLResponse?, data:NSData?, error:NSError?) -> Result<Value, Error>) {
guard error == nil else { return .Failure(error!)
}
let responseSerializer = ResponseSerializer<T, NSError>(serializeResponse:f)
However, this can be condensed. We don't really need the function f for anything else, so we can supply it as an anonymous function; it doesn't need a name or a full declaration. Moreover, there is a "shortcut" rule for anonymous functions, that if an anonymous function is the last parameter to a function, it can be provided literally after the function's closing parentheses, with the parameter name omitted. And if the function takes no other parameters, its parentheses can be omitted altogether.
Well, this init is exactly such a function — it takes a function as its last (and only) parameter — so that is exactly what the code in question does:
let responseSerializer = ResponseSerializer<T, NSError> {
request, response, data, error in
guard error == nil else { return .Failure(error!)
}
If I read it all correctly the code above has a pattern similar to this:
// just a something
struct Blah {
var stuffs : (message:String) -> Void
init(closure:(message:String) -> Void) {
self.stuffs = closure
}
}
// an extension because the code above is also in an extension, but not needed at all
extension Blah {
// a function with a closure that also returns an instance of Self
func spawnChild(closure:(message:String) -> Void) -> Blah {
return Blah(closure: closure) // closure is passed to spawn
}
}
Tests :
let alpha = Blah { (message) -> Void in
print("alpha",message)
}
let beta = alpha.spawnChild { (message) -> Void in
print("beta", message)
}
alpha.stuffs(message: "parrent") // alpha parent
beta.stuffs(message: "spawn") // beta spawn
Remember that the trailing closure is just syntactic sugar for an input parameter that is a closure. So anything that takes input parameters can have a trailing closure.