kristóf baján kristóf baján - 1 month ago 6
Swift Question

Swift cast generic without knowing the type

Is it possible to typecast an object like so (let the code speak for itself):

protocol Parent {
...
}

class Child<LiterallyAnyValue, SameAsThePrevious>: Parent {
...
}


And then when using it:

func foobar(parent: Parent) {
if parent is Child { //ONE
print(parent as! Child) //TWO
}
}


At the signed points xcode wants me to supply the two types of "Child" within <> like Child<Int, String>...

The problem is that those types could be anything... LITERALLY

(And I've tried Child<Any, Any> but that doesn't work in this case)

Is there a workaround or a solution to this?

-------- Clarification --------

I am working on an iOS 7 project so I can't really use any modern library :)

That is including PromiseKit and Alamofire and the app has to make tons of http requests. The use promises in requests has grown on me, so I created my own Promise class.

At first I made it so that the Promise class would not be a generic and it would accept Any? as the value of the resolution procedure.

After that I wanted to improve my little Promise class with type clarification so the class Promise became "class Promise<T>"

In my implementation the then method created a PromiseSubscriber which then would be stored in a Promise property called subscribers.

The PromiseSubscriber is the protocol here that has two subset classes, one being PromiseHandler (this is called when the promise is resolved), and the other the PromiseCatcher (this is called when the promise is rejected)

Both PromiseSubscriber subset classes have a property called promise and one called handler.

These classes are also generics so that you know what kind of Promise they store and what is the return type of the handler.

In my resolution process I have to check if the PromiseSubscriber is a (let's say) PromiseHandler and if it is then call the handler that returns something and then resolve the subscribed promise with that value.

And here is the problem. I can't check if the subscriber is a catcher or a handler...

I hope it's clear enough now. Maybe this is not the right approach, I honestly don't know I am just trying to create something that is fun and easy to use (code completion without checking the type).

If it's still not clear and you are willing to help me, I'll send over the classes!

Answer

It's a little difficult to understand what you're really trying to do here (please tell me it's something other than JSON parsing; I'm so tired of JSON parsing and it's the only thing people ever ask about), but the short answer is almost certainly no. Some part of that is probably a misuse of types, and some part of that is a current limitation in Swift.

To focus on the limitation in Swift part, Swift lacks higher-kinded types. It is not possible to talk about Array. This is not a type in Swift. You can only work with Array<Int> or Array<String> or even Array<T>, but only in cases where T can be determined at compile time. There are several ways to work through this, but it really depends on what your underlying problem is.

To the misuse of types side, you generally should not have if x is ... in Swift. In the vast majority of cases this should be solved with a protocol. Whatever you were going to do in the if, make it part of the Parent protocol and give it a default empty implementation. Then override that implementation in Child. For example:

protocol Parent {
    func doSpecialThing()
}

extension Parent {
    func doSpecialThing() {} // nothing by default
}

class Child<LiterallyAnyValue, SameAsThePrevious>: Parent {}

extension Child {
    func doSpecialThing() {
        print(self)
    }
}

func foobar(parent: Parent) {
    parent.doSpecialThing()
}

Thanks for the clarification; Promise is a great thing to play with. Your mistake is here:

In my resolution process I have to check if the PromiseSubscriber is a (let's say) PromiseHandler and if it is then call the handler that returns something and then resolve the subscribed promise with that value.

Your resolution process should not need to know if it's a handler or a catcher. If it does, then your PromiseSubscriber protocol is incorrectly defined. The piece it sounds like you're missing is a Result. Most Promise types are built on top of Result, which is an enum bundling either success or failure. In your scheme, handlers would process successful Results and ignore failing results. Catchers would process failing results and ignore successful Results. The promise resolution shouldn't care, though. It should just send the Result to all subscribers and let them do what they do.

You can build this without a Result type by using a protocol as described above.

protocol PromiseSubscriber {
    associatedType Wrapped // <=== It's possible you've also missed this piece
    func handleSuccess(value: Wrapped)
    func handleFailure(failure: Error)
}

extension PromiseSubscriber {
    func handleSuccess(value: Wrapped) {} // By default do nothing
    func handleFailure(failure: Error) {} 
}

class PromiseHandler<Wrapped> {
    func handleSuccess(value: Wrapped) { ... do your thing ... }
}

class PromiseCatcher {
    func handleFailure(failure: Error) { ... do your thing ... } 
}

I recommend studying PinkyPromise. It's a nice, simple Promise library (unlike PromiseKit which adds a lot of stuff that can make it harder to understand). I probably wouldn't use a protocol here; the associatedtype makes things a bit harder and I don't think you get much out of it. I'd use Result.