Store Swift closures and cast them back to their original type - swift

I'm trying to create a class that can save different closures (or methods) with an argument of a specific subtype of Decodable that should be called later. This way I can predefine what actions, or methods, can be called on that class in response to some input. For example, the line addCallback(setOption(_:), SetOptionRequest.self) should result in the subsequent call to try! performCallback("setOption", JSONEncoder().encode(SetOptionRequest()) to call setOption(data) where the argument data has type SetOptionRequest.
Here is the code I have so far (I took the bit about DecodableWrapper from here). The problem is that at runtime the cast callback.callback as! (ActionRequest) throws -> Void fails, since the type of the closure is not (ActionRequest) throws -> Void but (SetOptionRequest) throws -> Void. But I have no idea if and how I can cast the closure back to its original type. I considered using Selectors but I would like to keep the compile-time check that I'm binding methods with their correct argument type.
struct DecodableWrapper: Decodable {
static var baseType: ActionRequest.Type!
var base: ActionRequest
init(from decoder: Decoder) throws {
self.base = try DecodableWrapper.baseType.init(from: decoder)
}
}
open class Server {
private var actionCallbacks = [String: (callback: Any, dataType: ActionRequest.Type)]()
open func setup() {
addCallback(setOption, action: SetOptionRequestResponse.self)
}
public func addCallback<T: ActionRequest>(_ callback: #escaping (_ data: T) throws -> Void, action: T.Type) {
actionCallbacks[T.action] = (callback, T.self)
}
private func performCallback(action: String, data: Data) throws {
let callback = actionCallbacks[action]!
DecodableWrapper.baseType = callback.dataType
let data = try! JSONDecoder().decode(DecodableWrapper.self, from: data).base
try (callback.callback as! (ActionRequest) throws -> Void)(data)
}
private func setOption(_ data: SetOptionRequest) {
}
}
protocol ActionRequest {
static var action: String
}
struct Request: SetOptionRequest {
}

Thanks to this article, I came up with a solution. The trick is to store a custom closure that always accepts the same argument type (in this case Data), does whathever needs to be done with the generic type T and then calls the nested closure.
open class Server {
private var actionCallbacks = [String: (Data) throws -> Void]()
open func setup() {
addCallback(setOption)
}
public func addCallback<T: ActionRequest>(_ callback: #escaping (_ data: T) throws -> Void) {
actionCallbacks[T.action] = { data in
let data = try JSONDecoder().decode(T.self, from: data)
try callback(data)
}
}
private func performCallback(action: String, data: Data) throws {
if let callback = actionCallbacks[action] {
try callback(data)
}
}
private func setOption(_ data: SetOptionRequest) {
}
}
public protocol ActionRequest: Codable {
static var action: String { get }
}
struct SetOptionRequest: ActionRequest {
static var action = "setOption"
}

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

Calling a function with a Generic parameter requires a no-arg init for the type

I'm having trouble getting a function call using a generic type to work using a URLSession DataTaskPublisher...
The API I'm calling always responds with an HTTP 200, and indicates whether it was successful or not in the JSON via a code string with an indicator.
I created a protocol that all response objects conform to, e.g.:
protocol APIResponse: Decodable {
var code: String { get }
}
My actual response will be something like:
struct LoginResponse : APIResponse {
let code: String
let name: String
enum CodingKeys: String, CodingKey {
case code = "code"
case name = "name"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
code = try values.decode(String.self, forKey: .code)
name = try values.decode(String.self, forKey: .name)
}
}
Now I want a function like this that I can use:
private func call<T: APIResponse>(_ endpoint: String, using data: Encodable, providing response: T)
-> AnyPublisher<T, Error> {
let request = createRequest(for: endpoint, using: data)
return session
.dataTaskPublisher(for: request)
.map {
$0.data
}
.decode(type: T.self, decoder: decoder)
.tryMap {
response in
if response.code.suffix(1) != "I" {
throw MyError(message: "Error \(code)")
}
return response
}
.eraseToAnyPublisher()
}
So far, so good!
But here's the problem... I want to use it like this:
func login() -> AnyPublisher<String, Error> {
call("login", using: LoginRequest(), providing: LoginResponse)
.map {
$0.name
}
.eraseToAnyPublisher()
}
The compiler complains with Type 'LoginResponse.Type' cannot conform to 'APIResponse'; only struct/enum/class types can conform to protocols
My fix (which works, but is kludgy), it to provide a no-arg init() for LoginResponse and call it like this: call("login", using: LoginRequest(), providing: LoginResponse())
Any way to get the Generic to work without the no-arg init()?
Should I be taking a totally different approach?
In the header for call change response parameter to
...providing response: T.Type)
and call it using .self
call("login", using: "request", providing: LoginResponse.self)
You don't really need to pass the response type to this function:
private func call<T: APIResponse>(_ endpoint: String,
using data: Encodable) -> AnyPublisher<T, Error> {
// no change here
}
But you might have to help the compiler here by specifying the type of the closure used in map as it has no way of guessing which type is returned from this generic function:
func login() -> AnyPublisher<String, Error> {
call("login", using: LoginRequest())
.map { (response: LoginResponse) -> String in
response.name
}
.eraseToAnyPublisher()
}
You can also simplify your LoginResponse:
struct LoginResponse: APIResponse {
let code: String
let name: String
}
As an alternative, the cleanest approach, in my view, is to create an Endpoint protocol that knows about its own response, so as to remove this redundancy from the call function.
It would look something like this:
protocol Endpoint {
associatedtype Response: Decodable
var request: URLRequest { get }
}
func call<E: Endpoint>(endpoint: E) -> AnyPublisher<E.Response, Error> {
//...
}
define the endpoint like this:
struct LoginResponse: Decodable {
// ...
}
struct LoginEndpoint: Endpoint {
typealias Response = LoginResponse
let request: URLRequest
}
and use it:
let login = LoginEndpoint(request: URLRequest(...))
call(endpoint: login)
.sink ...

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

Swift Partial application of protocol method is not allowed

I get a strange error message from Swift
Partial application of protocol method is not allowed
I try to pass a function into a class. My code looks like follow
Condition.swift
public protocol Condition {
var type: String { get set }
init(conditionJson: JSON)
func getExpression() -> NSPredicate
func getConditionAction(actionHandler: () -> Void)
}
TimerCondition.swift
public class TimerCondition: Condition {
public var type: String
public var seconds: Int
required public init(conditionJson: JSON) {
self.type = conditionJson["conditionType"].stringValue
self.seconds = conditionJson["conditionType"].intValue
}
public func getExpression() -> NSPredicate {
return NSPredicate(format: "1 == 1")
}
public func getConditionAction(actionHandler: () -> Void){
actionHandler()
}
}
Now in here im getting the error
public class ConditionHandler {
var test:((actionHandler: () -> Void) -> Void)
public init(eventResponse:JSON) {
//error happens here
test = condition.getConditionAction
}
If I do the whole thing without a protocol it works.
Can some help me out here?
EDIT
The solution described in Partial application of protocol method is not allowed can't be applied to my problem, as I want to use a parameter

How to pass a class type as a function parameter

I have a generic function that calls a web service and serialize the JSON response back to an object.
class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: AnyClass, completionHandler handler: ((T) -> ())) {
/* Construct the URL, call the service and parse the response */
}
What I'm trying to accomplish is is the equivalent of this Java code
public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
final Class<T> classTypeToReturn) {
}
Is my method signature for what I'm trying to accomplish correct?
More specifically, is specifying AnyClass as a parameter type the
right thing to do?
When calling the method, I'm passing MyObject.self as the returningClass value, but I get a compilation error "Cannot convert the expression's type '()' to type 'String'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/
}
Edit:
I tried using object_getClass, as mentioned by holex, but now I get:
error: "Type 'CityInfo.Type' does not conform to protocol 'AnyObject'"
What need to be done to conform to the protocol?
class CityInfo : NSObject {
var cityName: String?
var regionCode: String?
var regionName: String?
}
You are approaching it in the wrong way: in Swift, unlike Objective-C, classes have specific types and even have an inheritance hierarchy (that is, if class B inherits from A, then B.Type also inherits from A.Type):
class A {}
class B: A {}
class C {}
// B inherits from A
let object: A = B()
// B.Type also inherits from A.Type
let type: A.Type = B.self
// Error: 'C' is not a subtype of 'A'
let type2: A.Type = C.self
That's why you shouldn't use AnyClass, unless you really want to allow any class. In this case the right type would be T.Type, because it expresses the link between the returningClass parameter and the parameter of the closure.
In fact, using it instead of AnyClass allows the compiler to correctly infer the types in the method call:
class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: T.Type, completionHandler handler: ((T) -> ())) {
// The compiler correctly infers that T is the class of the instances of returningClass
handler(returningClass())
}
Now there's the problem of constructing an instance of T to pass to handler: if you try and run the code right now the compiler will complain that T is not constructible with (). And rightfully so: T has to be explicitly constrained to require that it implements a specific initializer.
This can be done with a protocol like the following one:
protocol Initable {
init()
}
class CityInfo : NSObject, Initable {
var cityName: String?
var regionCode: String?
var regionName: String?
// Nothing to change here, CityInfo already implements init()
}
Then you only have to change the generic constraints of invokeService from <T> to <T: Initable>.
Tip
If you get strange errors like "Cannot convert the expression's type '()' to type 'String'", it is often useful to move every argument of the method call to its own variable. It helps narrowing down the code that is causing the error and uncovering type inference issues:
let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self
CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/
}
Now there are two possibilities: the error moves to one of the variables (which means that the wrong part is there) or you get a cryptic message like "Cannot convert the expression's type () to type ($T6) -> ($T6) -> $T5".
The cause of the latter error is that the compiler is not able to infer the types of what you wrote. In this case the problem is that T is only used in the parameter of the closure and the closure you passed doesn't indicate any particular type so the compiler doesn't know what type to infer. By changing the type of returningClass to include T you give the compiler a way to determine the generic parameter.
you can get the class of AnyObject via this way:
Swift 3.x
let myClass: AnyClass = type(of: self)
Swift 2.x
let myClass: AnyClass = object_getClass(self)
and you can pass it as paramater later, if you'd like.
I have a similar use case in swift5:
class PlistUtils {
static let shared = PlistUtils()
// write data
func saveItem<T: Encodable>(url: URL, value: T) -> Bool{
let encoder = PropertyListEncoder()
do {
let data = try encoder.encode(value)
try data.write(to: url)
return true
}catch {
print("encode error: \(error)")
return false
}
}
// read data
func loadItem<T: Decodable>(url: URL, type: T.Type) -> Any?{
if let data = try? Data(contentsOf: url) {
let decoder = PropertyListDecoder()
do {
let result = try decoder.decode(type, from: data)
return result
}catch{
print("items decode failed ")
return nil
}
}
return nil
}
}
Simply copy paste each code here into swift file:
# save as: APICaller.swift
import Foundation
struct APICaller
{
public static func get<T: Decodable>(url: String, receiveModel: T.Type, completion:#escaping (Decodable) -> ())
{
send(url: url, json: nil, receiveModel: receiveModel, completion: completion, httpMethod: "GET")
}
public static func post<T: Decodable>(url: String, json: [String: Any]?, receiveModel: T.Type, completion:#escaping (Decodable) -> ())
{
send(url: url, json: nil, receiveModel: receiveModel, completion: completion, httpMethod: "POST")
}
public static func delete<T: Decodable>(url: String, json: [String: Any]?, receiveModel: T.Type, completion:#escaping (Decodable) -> ())
{
send(url: url, json: nil, receiveModel: receiveModel, completion: completion, httpMethod: "DELETE")
}
private static func send<T: Decodable>(url: String, json: [String: Any]?, receiveModel: T.Type, completion:#escaping (Decodable) -> (), httpMethod: String)
{
// create post request
let urlURL: URL = URL(string: url)!
var httpRequest: URLRequest = URLRequest(url: urlURL)
httpRequest.httpMethod = httpMethod
if(json != nil)
{
// serialize map of strings to json object
let jsonData: Data = try! JSONSerialization.data(withJSONObject: json!)
// insert json data to the request
httpRequest.httpBody = jsonData
httpRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
}
// create an asynchronus task to post the request
let task = URLSession.shared.dataTask(with: httpRequest)
{ jsonData, response, error in
// on callback parse the json into the receiving model object
let receivedModelFilled: Decodable = Bundle.main.decode(receiveModel, from: jsonData!)
// cal the user callback with the constructed object from json
DispatchQueue.main.async {
completion(receivedModelFilled)
}
}
task.resume()
}
}
# save as: TestService.swift
import Foundation
struct TestService: Codable
{
let test: String
}
then you can use it like this:
let urlString: String = "http://localhost/testService" <--- replace with your actual service url
// call the API in post request
APICaller.post(url: urlString, json: ["test": "test"], receiveModel: TestService.self, completion: { testReponse in
// when response is received - do something with it in this callback
let testService: TestService = testReponse as! TestService
print("testService: \(testService)")
})
Tip:
i use online service to turn my JSONs into swift files, so all i have left is to write the call and handle the response
i use this one: https://app.quicktype.io but you can search for the one you prefer
Swift 5
Not exactly the same situation, but I was having similar problem. What finally helped me was this:
func myFunction(_ myType: AnyClass)
{
switch myType
{
case is MyCustomClass.Type:
//...
break
case is MyCustomClassTwo.Type:
//...
break
default: break
}
}
Then you can call it inside an instance of said class like this:
myFunction(type(of: self))
Hope this helps someone in my same situation.
Use obj-getclass:
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: obj-getclass(self)) { cityInfo in /*...*/
}
Assuming self is a city info object.
I recently came across this looking for a way to make my UINavigationController invisible to everything but the subview buttons. I put this in a custom nav controller:
// MARK:- UINavigationBar Override
private extension UINavigationBar {
override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// Make the navigation bar ignore interactions unless with a subview button
return self.point(inside: point, with: event, type: UIButton.self)
}
}
// MARK:- Button finding hit test
private extension UIView {
func point<T: UIView>(inside point: CGPoint, with event: UIEvent?, type: T.Type) -> Bool {
guard self.bounds.contains(point) else { return false }
if subviews.contains(where: { $0.point(inside: convert(point, to: $0), with: event, type: type) }) {
return true
}
return self is T
}
}
Don't forget to use bounds instead of frame as point is converted before calling.