How to cast Protocol to NSObject<Protocol> to access constrained extension interface?

911 views Asked by At

The question refers to Swift3. Since version 4 you can finally cast objects to Type<Protocol> like this:

someObject as (Type & Protocol)

I have a protocol and multiple types inheriting from it.

A, B, C, D: CKEntity (pseudocode)

For that protocol I have a case where an object inherits from NSObject:

Extension is:

extension CKEntity where Self : NSObject {

static func method() {
    ...
}

Work perferctly for multiple cases.

And I have a function which takes Class(Type) which implements CKEntity.

I need to check whether it conforms to the case if it is also NSObject. And if it is, call the appropriate function.

Prototype of what I'm trying to accomplish:

func validateSync(ofType type: CKEntity.Type) {
    let a = (type as NSObject.Type & CKEntity.Type).self

    a.method() // Compilation error: Type 'Any' has no member 'method'
}

Doesn't work.

Question is:

How do I cast a type (Protocol.Type) to NSObject.Type (not an instance) to meet extension constraints?


Scenario (recap):

  1. extension for A, where A is B

  2. A could be B

  3. Need to cast A to A:B and reach constrained A&B interface.

1

There are 1 answers

0
Hexfire On BEST ANSWER

The solution was found, kind of.

The thing is that when it comes to extending NSObject, it's preferable to resort to extending NSObjectProtocol rather than NSObject itself (even if you're about to extend the latter).

The reason being that NSObject itself is not a protocol, which brings difficulties with Swift composite protocol check. NSObjectProtocol, on the other hand, is naturally inherited by any NSObject-derived object and is a protocol, which is a key.

Thus, it's where we should refactor things to downcasting the type to multiple protocols, if possible (and that is possible, if it's about NSObject).

Extension was modified as follows:

extension NSObjectProtocol where Self : CKEntity {
     static func method() {
         // ...
     }
}

And the check is:

 if let extendedType = type as? NSObjectProtocol & CKEntity.Type {
     extendedType.self.method()
 }

And it works!

The drawback one can stumble across would be the one that NSObjectProtocol doesn't contain KVC methods, so you would need to cast your objects like this to get KVC interface:

extension NSObjectProtocol where Self : CKEntity {

    static var staticVar : String {
        return (Self.self as! NSObject.Type).className()
    }


    var publicVar : String! {
        return (self as! NSObject).value(forKey: "Key") as! String
    }
}