I have a function that is responsible for making the HTTP requests in the app. Basically, it sets up all the required headers, timeouts, etc... etc...
Then, when the request is complete, I run 2 functions (provided by the developer): whenSuccess/whenError (depending on the result of the call) and whenComplete (regardless the result of the call). I want the last one to be able to receive the result of the whenSuccess function.
I have the doRequest function declared as
private func doRequest<S>(whenSuccess: (_ json: JSON?)->(S?),
whenError: (_ description: String)->(),
whenComplete: Optional<(S?)->()>) {
// yada yada yada
}
So, the developer provides a function that gets a JSON and returns a generic. If the whenError is called, the whenComplete is called with nil as parameter.
I'm invoking it with
doRequest<Int>(whenSuccess: { (json) -> (Int?) in //Cannot specialize a non-generic definition
return 5
}, whenError: { (error) in
}) { (success) in
}
I get the error commented:
Cannot specialize a non-generic definition
Any idea if this can be done and how?
In Swift you must not explicitly specialize generic functions. Instead, generic functions automatically get specialized via type inference from theirs arguments.
The call you you're trying to make should look like this:
doRequest(whenSuccess: { json -> Int? in
//...
}, whenError: { error in
//...
}, whenComplete: { success in
//...
})
Related
I've created a function a completion handler that I send back results in for use in other functions. I'd like to be able to use the results from there but they come back as a constant. I'd like them to be a var. Here is the function :
func queryForObject (_ modelType: Model.Type, success: #escaping (_ theObject:Model)->(), failure: #escaping (_ error: NSError)->()) {
// do some network stuff that returns the response.result object and handle errors...
success (response.result)
}
Here's the call to this function...
self.queryForObject(MyObject.self) { returnObject in
returnObject.field1 = "some new value"
// do other stuff.
}
The problem is the call returnObject.field1 = "some new value" cause a compiler warning :
Cannot assign to property: 'returnObject' is a 'let' constant
Therefore how do I get the parameter in my callback to be assignable? Is it even possible? (I'm worried it is not.) Unfortunately the return object is from a library so I can't change it so it supports the copy() function and therefore can't just do a var assignableObject = returnObject.copy(). Also assigning all the different fields, while possible, is not going to be scalable for us.
I have the following function :
Amplify.API.query(request: .get(
self.getObjectForOCObject(ocObject: ocObject)!, byId: id)) { event in
The definition of the get function is :
public static func get<M: Model>(_ modelType: M.Type,
byId id: String) -> GraphQLRequest<M?> {
My getObjectForObject is as follows :
func getObjectForOCObject <M: Model>(ocObject:NSObject) -> M.Type?
{
if type (of:ocObject) == type(of: OCAccount.self)
{
return Account.self as? M.Type
}
return nil;
}
But I'm getting a"Generic Parameter 'M' could not be inferred. I get that the compiler cannot determine what type of object this is. However I don't know how to fix this. I need my getObjectForOCObject function to return what it is expecting. Essentially I'm trying to return the type of object based on the type of another object.
Normally this call is done this way and it works...
Amplify.API.query(request: .get(Account.self, byId: id)) { event in
I'm trying to avoid having to create a number of functions to handle each different Model in my code as that is being handled in higher up in the call hierarchy. Which is why I'm trying to do this. Otherwise I'm going to have to write essentially the same code in multiple places. In Objective C one can use the super class to pass objects around but the inheriting class still comes along with it. But with Swift being dynamically typed, I'm running into this issue.
The closest I've gotten is this... Changing the getObjectForOCObject return to Model.Type? as so :
func getObjectForOCObject (ocObject:NSObject) -> Model.Type?
{
if type (of:ocObject) == type(of: OCAccount.self)
{
return Account.self
}
return nil;
}
Then I get a Cannot convert value of type 'Model.Type?' to expected argument type 'M.Type' But aren't these essentially the same thing based on the .get function above?
Hoping for some insight here. Also, FYI, I'm pretty terrible with Swift so I'm certain I'm definitely doing some bad things in Swift here. I'm an expert with ObjC. I'm just trying to create a bridge between the two for this Swift library I'm using.
[Updated with a less contrived example]
I'm trying to extend a generic function to provide different behavior for a specific type. This works as expected when I call the function directly. But If I call it from within another generic function, the original generic type is not preserved and I get the default behavior. I'm a bit new to Swift, so I may be missing something obvious here.
My code looks something like this:
protocol Task {
associatedtype Result
}
struct SpecificTask: Task {
typealias Result = String
// other taks related properties
}
enum TaskRunner<T: Task> {
static func run(task: T) throws -> T.Result {
// Task not supported
throw SomeError.error
}
}
extension TaskRunner where T == SpecificTask {
static func run(task: T) throws -> T.Result {
// execute a SpecificTask
return "Some Result"
}
}
func run<T: Task>(task: T) throws -> T.Result {
// Additional logic related to running the task
return try TaskRunner.run(task: task)
}
print(try TaskRunner.run(task: SpecificTask())) // Prints "Some Result"
print(try run(task: SpecificTask())) // Throws SomeError
I need the top-level run function to call the SpecificTask version of the lower-level run() function, but the generic version of the function is called instead
You're trying to reinvent class inheritance with generics. That is not what generics are for, and they don't work that way. Generic methods are statically dispatched, which means that the code is chosen at compile-time, not runtime. An overload should never change the behavior of the function (which is what you're trying to do here). Overrides in where clauses can be used to improve performance, but they cannot be used to create dynamic (runtime) dispatch.
If you must use inheritance, then you must use classes. That said, the problem you've described is better solved with a generic Task rather than a protocol. For example:
struct Task<Result> {
let execute: () throws -> Result
}
enum TaskRunner {
static func run<Result>(task: Task<Result>) throws -> Result {
try task.execute()
}
}
let specificTask = Task(execute: { "Some Result" })
print(try TaskRunner.run(task: specificTask)) // Prints "Some Result"
Notice how this eliminates the "task not supported" case. Rather than being a runtime error, it is now a compile-time error. You can no longer call this incorrectly, so you don't have to check for that case.
If you really want dynamic dispatch, it is possible, but you must implement it as dynamic dispatch, not overloads.
enum TaskRunner<T: Task> {
static func run(task: T) throws -> T.Result {
switch task {
case is SpecificTask:
// execute a SpecificTask
return "Some Result" as! T.Result // <=== This is very fragile
default:
throw SomeError.error
}
}
}
This is fragile because of the as! T.Result. If you change the result type of SpecificTask to something other than String, it'll crash. But the important point is the case is SpecificTask, which is determined at runtime (dynamic dispatch). If you need task, and I assume you do, you'd swap that with if let task = task as? SpecificTask.
Before going down that road, I'd reconsider the design and see how this will really be called. Since the Result type is generic, you can't call arbitrary Tasks in a loop (since all the return values have to match). So it makes me wonder what kind of code can actually call run.
I'm doing some refactoring to move code to using Promises (using Hydra) instead of async callbacks. I originally had this method and it worked fine:
static func fetch<D: AnyDTO, E: AnyEntity>(
_ context : NSManagedObjectContext,
fetch request : NSFetchRequest<E>,
successHandler : #escaping ([D]) -> (),
errorHandler : #escaping ErrorHandler)
So I changed this to work with promises like this:
import Hydra
static func fetch<D: AnyDTO, E: AnyEntity>(
_ context : NSManagedObjectContext,
fetch request : NSFetchRequest<E>) -> Promise<[D]>
{
return Promise<[D]>(in: .background) { resolve, reject, _ in
...
}
}
with client code trying to call the function like this:
let request: NSFetchRequest<Location> = Location.fetchRequest()
return CacheUtils.fetch(context, fetch: request)
But the compiler is giving me this error:
Cannot convert value of type 'NSFetchRequest' to expected
argument type 'NSFetchRequest<_>'
and I'm unsure why. I've checked out similar questions, and noticed the issue of using a concrete type within the function (see this). I think Promise might fit this bill, but on the other hand, the Promise is generic, so I'm not confident that is the problem. Is it possible to do what I'm trying to achieve in Swift?
I have come across this code:
class WebServerTests: XCTestCase {
let webServer: GCDWebServer = GCDWebServer()
var webServerBase: String!
/// Setup a basic web server that binds to a random port and that has one default handler on /hello
private func setupWebServer() {
webServer.addHandlerForMethod("GET", path: "/hello", requestClass: GCDWebServerRequest.self) { (request) -> GCDWebServerResponse! in
return GCDWebServerDataResponse(HTML: "<html><body><p>Hello World</p></body></html>")
}
I am confused by the webServer.addHandlerForMethod part. It seems to me it is already a complete function call (webServer.addHandlerForMethod("GET", path: "/hello", requestClass: GCDWebServerRequest.self)). Therefore I do not understand why it is followed by a closure ( {(request) -> ... )
EDIT: Clarify what I do not understand
According to the documentation on https://github.com/swisspol/GCDWebServer, the function signature in obj-c is:
[webServer addDefaultHandlerForMethod:#"GET"
requestClass:[GCDWebServerRequest class]
asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) {
Therefore I expect its swift counterpart will be called somewhat like this:
webServer.addHandlerForMethod("GET", path: "/hello", requestClass: GCDWebServerRequest.self, { (request) -> GCDWebServerResponse! in
return GCDWebServerDataResponse(HTML: "<html><body><p>Hello World</p></body></html>")
})
i.e. the handling of the incoming request is passed as the third parameter. But since the closure comes after the closing ')', it does not look like part of the function call at all.
Why the function signature is mapped from obj-c to swift this way?
In Swift, you can use this syntax if the last argument to a function is a closure. Here's the example from the Swift language guide section about closures (scroll down to Trailing Closures):
func someFunctionThatTakesAClosure(closure: () -> ()) {
// function body goes here
}
// here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure({
// closure's body goes here
})
// here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}
And then there's also this note:
If a closure expression is provided as the function’s only argument and you provide that expression as a trailing closure, you do not need to write a pair of parentheses () after the function’s name when you call the function.
This means it's also legal to write this:
someFunctionThatTakesAClosure {
// closure body
}
… which helps provide a nice meta-programming syntax. For example:
let lock = NSLock()
func locked(closure: () -> ()) {
lock.lock();
closure()
lock.unlock();
}
locked {
NSLog("Hello, world!")
}
The closure is where the handling of the incoming request is done. It tells the server to run the closure's code when a GET method that requests /hello path comes.
In the code you posted code in the closure creates a response that the server returns.