3

What I'm trying to do is to have two generic type parameters where one is a specific type and the other a protocol like so:

@propertyWrapper
struct Implementation<T, P> where T : AnyObject, T : P { // Compiler error

    var wrappedValue: P { projectedValue }

    var projectedValue: T

    init(_ instance: T) {
        self.projectedValue = instance
    }

}

This way, the actual type can be hidden and have only the protocol exposed.

Now this doesn't work because P is a non-class, non-protocol type, so T cannot be constrained to it.

Is there a way around this?

funct7
  • 3,407
  • 2
  • 27
  • 33

4 Answers4

1

I think you can create a protocol for T to inherit from, then you wouldn't need the P at all:

protocol ImplementationProtocol: AnyObject {}

@propertyWrapper
struct Implementation<T: ImplementationProtocol> { 

    var wrappedValue: ImplementationProtocol { projectedValue }

    var projectedValue: T

    init(_ instance: T) {
        self.projectedValue = instance
    }

}

now your "T" will have to conform to "ImplementationProtocol" and "wrappedValue" will also have to conform to "ImplementationProtocol" as you tried to accomplish in your code above.

hope it helps

Mr Spring
  • 533
  • 8
  • 17
  • This is not the same as what I'm trying to do. You can only use this property wrapper for `ImplementationProtocol`. – funct7 Apr 22 '20 at 01:15
  • ok i did some digging, how about this approach, its not perfect since wrappedValue is nullable but it should work – Mr Spring Apr 22 '20 at 01:33
0

What you want is not a feature of the language, so your closest option is a runtime solution that negates some property wrapper sugar.

@propertyWrapper
struct Implementation<Object: AnyObject, Protocol> {
  init(_ projectedValue: Object) throws {
    if let error = CastError.Desired(projectedValue, Protocol.self)
    { throw error }

    self.projectedValue = projectedValue
  }

  var projectedValue: Object
  var wrappedValue: Protocol { projectedValue as! Protocol }
}
protocol Protocol { }
class Class: Protocol { init() { } }
struct Struct {
  @Implementation<Class, Protocol> var implementation: Protocol

  init() throws {
    _implementation = try .init( .init() )
  }
}
public enum CastError {
    /// An error that represents that an desired cast is not possible.
  public struct Desired<Instance, DesiredCast>: Error {
    /// `nil` if `instance` is a `DesiredCast`.
    /// - Parameter instance: Anything. 
    public init?(_ instance: Instance, _: DesiredCast.Type) {
      if instance is DesiredCast
      { return nil }
    }
  }

  /// An error that represents that an undesired cast is possible.
  public struct Undesired<Instance, UndesiredCast>: Error {
    /// `nil` if `instance` is not an `UndesiredCast`.
    /// - Parameter instance: Anything. 
    /// - Note: Ineffective if `instance` is a protocol instance
    /// and `UndesiredCast` is `AnyObject`.
    public init?(_ instance: Instance, _: UndesiredCast.Type) {
      guard type(of: instance) is UndesiredCast.Type
      else { return nil }
    }
  }
}
  • 1
    Yeah I guess this is the closest we can get with the current language constraints. Too bad :( – funct7 Apr 22 '20 at 04:56
-1
@propertyWrapper
struct Implementation<T, P> where T : AnyObject{ 
var wrappedValue: P? = nil

var projectedValue: T {
    didSet {
        if let value =  projectedValue as? P {
                wrappedValue = value
            }
        }
    }

    init(_ instance: T) {
        self.projectedValue = instance
    }

}
Mr Spring
  • 533
  • 8
  • 17
  • This is also limited in that there is no way to enforce `T` is also of type `P`. It's basically the same as removing `T` and making `instance: AnyObject`. Thanks for trying though. – funct7 Apr 22 '20 at 02:13
-2

I believe you are approaching the entire problem in a way that is causing far too much abstraction, making it difficult to find a solution because the definition of the problem is not based in a real world use case.

Going back to basic principles. A protocol is defined as "a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality."

You want to define P as a protocol with some kind of function.

It would be good to take a step back and understand what it is you a trying to achieve with your property wrapper.

Comment: Property wrappers are rarely effective and I have yet to see a good use of them outside very concrete cases, like SwiftUI. They are a language extension feature that dramatically limits what you can do with a particular variable. Unless you have a really good architecture around it, property wrappers are rarely an effective choice in solving a problem.

Peter Suwara
  • 781
  • 10
  • 16