Trailing Closures on generics? - swift

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.

Related

Use of flatMap on a generic Publisher results in a compile error

I'm writing a transform function that would take network request results and try to parse them automatically using a dict to Model transformer(not Decodable due to several backend reasons).
So the chain should look like this:
func getModel -> Single<Model> {
return networkRequest(requestParameters).parse(modelTranslator)
}
The translator is a generic protocol:
public protocol Translator {
associatedtype Model
func translateFrom(dictionary json: [String: Any]) throws -> Model
}
Single is a wrapper around Deferred and Future:
public typealias Single<T> = Deferred<Future<T, Error>>
The problematic parse extension method here is:
public extension Publisher {
func parse<T: Translator, M>(translator: T) -> Single<M> where T.Model == M {
return self.flatMap { (data: Data) -> Single<M> in
return Deferred {
return Future<M, any Error> { promise in
guard
let json = try? JSONSerialization.jsonObject(with: data, options: []),
let dict = json as? [String : Any]
else {
let error: any Error = TranslatorError.invalidJSONObject
return promise(Result.failure(error))
}
do {
let translatedModel: M = translator.translateFrom(dictionary: dict)
return promise(Result.success(translatedModel))
} catch let error {
return promise(Result.failure(error))
}
}
}
}
}
}
It won't compile. It shows 2 errors on the .flatmap row:
No 'flatMap' candidates produce the expected contextual result type 'Single' (aka 'Deferred<Future<M, any Error>>')
No exact matches in call to instance method 'flatMap'
I believe that it has something to do with a type mismatch?
Could you please help me see the problem?
Thank you in advance!
You are trying too hard. A simple tryMap is all you need to parse your [String: Any] into the appropriate model type. Here is a complete example:
func getFoo(_ requestParameters: RequestParameters) -> AnyPublisher<Foo, Error> {
getModel(requestParameters, modelTranslator: FooTranslator())
}
func getModel<T>(_ requestParameters: RequestParameters, modelTranslator: T) -> AnyPublisher<T.Model, Error> where T: Translator {
networkRequest(requestParameters)
.tryMap { try modelTranslator.translateFrom(dictionary: $0) }
.eraseToAnyPublisher()
}
The above assumes the following declarations:
func networkRequest(_ params: RequestParameters) -> Single<[String: Any]> ...
struct FooTranslator: Translator {
func translateFrom(dictionary json: [String : Any]) throws -> Foo ...
}

What is the type of struct.self in swift?

I have a structural variable that extends Codable protocol, and want to memorize its type, so that I can use it next time in JSONDecoder. However, when a private variable is declared, its class needs to be specified, but whatever class I'm trying use, I cannot use the result later in JSONDecoder. So my question is what is the type of myVariable.self?
I have this problem, because I want to specify the class when a view decoder is initialized. During the initialization, I call the following function (this part of the code works well):
func getData<T: Codable>(fromURL: String, onSuccess: #escaping (T) -> Void, onError: #escaping (String) -> Void) {
let url = URL(string: fromURL)!
let task = session.dataTask(with: url) {(data, response, error) in
DispatchQueue.main.async {
if let error = error {
onError(error.localizedDescription)
return
}
guard let data = data, let response = response as? HTTPURLResponse else {
onError("Invalid data or response")
return
}
do {
if response.statusCode == 200 {
let ret = try JSONDecoder().decode(T.self, from: data)
onSuccess(ret)
} else {
let err = try JSONDecoder().decode(APIError.self, from: data)
onError(err.message)
}
} catch {
onError(error.localizedDescription)
}
}
}
task.resume()
}
This part of the code works well. However, if I need to upload more data, I need to know T.self for proper decoder, so I need to keep it. A solution could be to create a private variable:
private var type: ??? // what type should I put here?
and to write in the function's body
type = T.self
But whichever type I try, it doesn't work.
What type should I put there? Or, maybe, there are some other solutions?
You can declare the property
private var type: Codable.Type
or wrap the method in a generic struct or class and declare the property
class CodableWrapper<T : Codable> {
private var type: T.Type
...

How to raise an Error for the completionHandler?

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
}

Future implementation in Swift 3

I am trying to implement a small future (promises) library with Swift 3 inspired by this talk here is my implmentation :
public enum Result<T, E: Error> {
case Success(T)
case Error(E)
}
public struct Future<T, E: Error> {
public typealias ResultType = Result<T, E>
public typealias Completion = (ResultType) -> Void
public typealias AsyncOperation = (Completion) -> Void
private let operation: AsyncOperation
public init(result: ResultType) {
self.init(operation: { completion in
completion(result)
})
}
public init(value: T) {
self.init(result: .Success(value))
}
public init(error: E) {
self.init(result: .Error(error))
}
public init(operation: #escaping (Completion) -> Void) {
self.operation = operation
}
public func start(completion: Completion) {
self.operation() { result in
completion(result)
}
}
}
//: ### Error handeling
enum UserInfoErrorDomain: Error {
case UserDoesNotExist
case UserRequestFailure
case NetworkRequestFailure
}
and here is my usage:
func downloadFile(URL: NSURL) -> Future<NSData, UserInfoErrorDomain> {
return Future(operation: { completion in
DispatchQueue.main.async( execute: {
print("Async2")
let result: Result<NSData, UserInfoErrorDomain>
if let data = NSData(contentsOf: URL as URL) {
result = Result.Success(data)
}
else {
result = Result.Error(.NetworkRequestFailure)
}
completion(result) // ERROR here Closure use of non-escaping parameter 'completion' may allow it to escape
})
})
}
but I get in the line of completion(result) and error of Closure use of non-escaping parameter 'completion' may allow it to escape
But the closure is already marked as #escaping in the method public init(operation: #escaping (Completion) -> Void) but maybe because it's a closure that takes a closure as argument and returns void needs another annotation, so to do that in Swift 3 because it seems that the code used to work in Swift 2
[...] but maybe because it's a closure that takes a closure as argument and returns void needs another annotation [...]
You're right. Completion is of type (ResultType) -> Void, which, as it's a parameter to your AsyncOperation function type, means that it's non-escaping by default – meaning that you cannot capture your completion parameter in an escaping closure (such as one passed to DispatchQueue.main.async).
Therefore you need to annotate Completion as #escaping:
public typealias AsyncOperation = (#escaping Completion) -> Void
and you'll want your init(operation:) and start(completion:) functions to look like this:
public init(operation: #escaping AsyncOperation) {
self.operation = operation
}
// the completion: parameter needs to be escaping as it's going to be called after
// an async operation has completed.
public func start(completion: #escaping Completion) {
self.operation { result in
completion(result)
}
}

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
)
}