I get the following two compiler errors when trying to declare a protocol with an associatedType - not sure what a generic constraint is.
protocol Listener {
associatedType ValueType
func call(_ binding:Binding<ValueType>, value:ValueType)
}
class Binding<T> {
var value:T?
var listeners:[Listener] = [] // error 1: Protocol 'Listener' can only be used as a generic constraint because it has Self or associated type requirements
func fire() {
listeners.forEach { $0.call(self,value:self.value) } // error 2: Member 'call' cannot be used on value of protocol type 'Listener'; use a generic constraint instead
}
}
This is an incorrect use of a protocol. Protocols associated types are determined by the implementer of the protocol, not the user of the protocol. Generics are determined by the user of the protocol. There is no automatic wrapping of protocols into generics (that feature will be called "existential containers" and we don't know when it will come if ever).
[Listener] is not a complete type. what is the ValueType?
For this particular case, there is no reason for a protocol. It captures exactly one function. You should just pass the function:
class Binding<T> {
var value: T?
var listeners:[(Binding<T>, T) -> ()] = []
func fire() {
if let value = value {
listeners.forEach { $0(self, value) }
}
}
init() {}
}
If you really need the protocol, you can lift it into a type eraser (AnyListener):
protocol Listener {
associatedtype ValueType
func call(_ binding:Binding<ValueType>, value:ValueType)
}
struct AnyListener<ValueType>: Listener {
let _call: (Binding<ValueType>, ValueType) -> ()
init<L: Listener>(_ listener: L) where L.ValueType == ValueType {
_call = listener.call
}
func call(_ binding: Binding<ValueType>, value: ValueType) {
_call(binding, value)
}
}
class Binding<T> {
var value:T?
var listeners: [AnyListener<T>] = []
func fire() {
if let value = value {
listeners.forEach { $0.call(self, value: value) }
}
}
}
let listener = ... some listener ....
binding.listeners.append(AnyListener(listener))
Or you could just make AnyListener into a more concrete struct and get rid of the protocol. But for such a simple case I'd pass the function usually.
Related
Consider a factory method pattern implementation:
import UIKit
protocol TransportProtocol: CustomStringConvertible {
func techReview()
}
// Make default implemetation opposed to #objc optional methods and properties
extension TransportProtocol {
func techReview() {
print("Reviewing \(type(of: self))")
}
var description: String {
"This is a \(type(of: self))"
}
}
final class Car: TransportProtocol {
func changeOil() {
print("Changed oil")
}
}
final class Ship: TransportProtocol {
}
protocol LogisticProtocol {
associatedtype Transport: TransportProtocol
func createTransport() -> Transport
func delivery(from A: Any, to B: Any)
func techRewiew(for transport: TransportProtocol)
}
extension LogisticProtocol {
func delivery(from A: Any, to B: Any) {
print("Moving \(type(of: self)) from \(A) to \(B)")
}
func techRewiew(for transport: TransportProtocol) {
transport.techReview()
}
}
final class RoadLogistics: LogisticProtocol {
func createTransport() -> some TransportProtocol {
Car()
}
}
final class SeaLogistics: LogisticProtocol {
func createTransport() -> some TransportProtocol {
Ship()
}
}
// Usage:
class Client {
// ...
static func someClientCode<L: LogisticProtocol>(creator: L) -> some TransportProtocol {
let transport = creator.createTransport()
print("I'm not aware of the creator's type, but it still works.\n"
+ transport.description + "\n")
creator.delivery(from: "Source", to: "Destination")
return transport
}
// ...
}
let someTransport = Client.someClientCode(creator: RoadLogistics())
type(of: someTransport.self) // Car
someTransport.changeOil() // Error: Value of type 'some TransportProtocol' has no member 'changeOil'
The question is why I can't call changeOil() on someTransport if the compiler knows it is a Car not just a TransportProtocol.
Are there any benefits we can get from using some directive, not just bare protocol types?
The return type of the method is some TransportProtocol, not Car. So even though the returned instance is of type Car, you cannot call any methods on it that only exist on Car, but not on TransportProtocol.
The runtime knows that the concrete type of someTransport is Car, but the compiler only knows that the return type conforms to TransportProtocol.
If you want to be able to access Car methods on someTransport, you need to downcast it to Car.
if let car = someTransport as? Car {
car.changeOil()
}
Using the some keyword has type-system benefits when used for opaque returns types (introduced in SE-0244 as part of Swift 5.1), since it actually enables returning a protocol with associated types without having to explicitly make the method generic. This is what drives SwiftUI, since it enables you to return some View.
On the other hand, using opaque return types for protocols without associated types holds no benefits, so in your particular example, there's no reason to use it.
I have a structure that I simplified like this:
protocol Protocol {
associatedtype T
var common: T { get }
}
class Superclass<T>: Protocol {
let common: T
init(common: T) { self.common = common }
}
class IntClass<T>: Superclass<T> {
let int = 5
}
class StringClass<T>: Superclass<T> {
let string = "String"
}
class Example<P: Protocol> {
let object: P
init(object: P) { self.object = object }
func common() -> P.T { object.common }
func int() -> Int where P == IntClass<Any> { object.int }
func string() -> String where P == StringClass<Any> { object.string }
}
I would like to create objects of the generic class Example where some of them contain an object of the also generic IntClass while others have a generic StringClass object. Now I’d like to add accessors on Example for IntClass and StringClass specific properties (so I don’t have to access them directly). They would need to be constrained to the respective class. These would be int() and string() in my example.
My example doesn’t work like intended though:
let intExample = Example(object: IntClass(common: Double(1)))
// 👍 (= expected)
intExample.common() // Double 1
// 👍 (= expected)
intExample.string() // Instance method 'string()' requires the types 'IntClass<Float>' and 'StringClass<Any>' be equivalent
// 👎 (= not expected)
intExample.int() // Instance method 'int()' requires the types 'IntClass<Float>' and 'IntClass<Any>' be equivalent
I also tried:
func int() -> Int where P == IntClass<P.T> { object.int }
With these compiler complaints:
- Generic class 'Example' requires that 'P' conform to 'Protocol'
- Same-type constraint 'P' == 'IntClass<P.T>' is recursive
- Value of type 'P' has no member 'int'
And I tried:
func string<T>() -> String where P == StringClass<T> { object.string }
which, when using like intExample.string() results in Generic parameter 'T' could not be inferred (next to Instance method 'string()' requires the types 'IntClass<Double>' and 'StringClass<T>' be equivalent).
I don’t want string() to appear on an Example<IntClass> object in code completion.
Is there a syntax to accomplish what I want (anything with typealias?) or would I have to navigate around that problem?
Since the properties you are trying to access here doesn't depend on the type parameter T of IntClass or StringClass, you can write two non generic protocols HasInt and HasString:
protocol HasInt {
var int: Int { get }
}
protocol HasString {
var string: String { get }
}
extension IntClass: HasInt { }
extension StringClass: HasString { }
Then you can constrain to the protocols, and access int and string through the protocol instead:
func int() -> Int where P: HasInt { object.int }
func string() -> String where P: HasString { object.string }
Here's something I'm playing with. The problem is that I have a container class that has a generic argument which defines the type returned from a closure. I want to add a function that is only available if they generic type is optional and have that function return a instance containing a nil.
Here's the code I'm currently playing with (which won't compile):
open class Result<T>: Resolvable {
private let valueFactory: () -> T
fileprivate init(valueFactory: #escaping () -> T) {
self.valueFactory = valueFactory
}
func resolve() -> T {
return valueFactory()
}
}
public protocol OptionalType {}
extension Optional: OptionalType {}
public extension Result where T: OptionalType {
public static var `nil`: Result<T> {
return Result<T> { nil } // error: expression type 'Result<T>' is ambiguous without more context
}
}
Which I'd like to use like this:
let x: Result<Int?> = .nil
XCTAssertNil(x.resolve())
Any idea how to make this work?
I don't think you can achieve this with a static property, however you can achieve it with a static function:
extension Result {
static func `nil`<U>() -> Result where T == U? {
return .init { nil }
}
}
let x: Result<Int?> = .nil()
Functions are way more powerful than properties when it comes to generics.
Update After some consideration, you can have the static property, you only need to add an associated type to OptionalType, so that you'd know what kind of optional to have for the generic argument:
protocol OptionalType {
associatedtype Wrapped
}
extension Optional: OptionalType { }
extension Result where T: OptionalType {
static var `nil`: Result<T.Wrapped?> {
return Result<T.Wrapped?> { nil }
}
}
let x: Result<Int?> = .nil
One small downside is that theoretically it enables any kind of type to add conformance to OptionalType.
I have created 2 protocols with associated types. A type conforming to Reader should be able to produce an instance of a type conforming to Value.
The layer of complexity comes from a type conforming to Manager should be able to produce a concrete Reader instance which produces a specific type of Value (either Value1 or Value2).
With my concrete implementation of Manager1 I'd like it to always produce Reader1 which in turn produces instances of Value1.
Could someone explain why
"Reader1 is not convertible to ManagedReaderType?"
When the erroneous line is changed to (for now) return nil it all compiles just fine but now I can't instantiate either Reader1 or Reader2.
The following can be pasted into a Playground to see the error:
import Foundation
protocol Value {
var value: Int { get }
}
protocol Reader {
typealias ReaderValueType: Value
func value() -> ReaderValueType
}
protocol Manager {
typealias ManagerValueType: Value
func read<ManagerReaderType: Reader where ManagerReaderType.ReaderValueType == ManagerValueType>() -> ManagerReaderType?
}
struct Value1: Value {
let value: Int = 1
}
struct Value2: Value {
let value: Int = 2
}
struct Reader1: Reader {
func value() -> Value1 {
return Value1()
}
}
struct Reader2: Reader {
func value() -> Value2 {
return Value2()
}
}
class Manager1: Manager {
typealias ManagerValueType = Value1
let v = ManagerValueType()
func read<ManagerReaderType: Reader where ManagerReaderType.ReaderValueType == ManagerValueType>() -> ManagerReaderType? {
return Reader1()// Error: "Reader1 is not convertible to ManagedReaderType?" Try swapping to return nil which does compile.
}
}
let manager = Manager1()
let v = manager.v.value
let a: Reader1? = manager.read()
a.dynamicType
The error occurs because ManagerReaderType in the read function is only a generic placeholder for any type which conforms to Reader and its ReaderValueType is equal to the one of ManagerReaderType. So the actual type of ManagerReaderType is not determined by the function itself, instead the type of the variable which gets assigned declares the type:
let manager = Manager1()
let reader1: Reader1? = manager.read() // ManagerReaderType is of type Reader1
let reader2: Reader2? = manager.read() // ManagerReaderType is of type Reader2
if you return nil it can be converted to any optional type so it always works.
As an alternative you can return a specific type of type Reader:
protocol Manager {
// this is similar to the Generator of a SequenceType which has the Element type
// but it constraints the ManagerReaderType to one specific Reader
typealias ManagerReaderType: Reader
func read() -> ManagerReaderType?
}
class Manager1: Manager {
func read() -> Reader1? {
return Reader1()
}
}
This is the best approach with protocols due to the lack of "true" generics (the following isn't supported (yet)):
// this would perfectly match your requirements
protocol Reader<T: Value> {
fun value() -> T
}
protocol Manager<T: Value> {
func read() -> Reader<T>?
}
class Manager1: Manager<Value1> {
func read() -> Reader<Value1>? {
return Reader1()
}
}
So the best workaround would be to make Reader a generic class and Reader1 and Reader2 subclass a specific generic type of it:
class Reader<T: Value> {
func value() -> T {
// or provide a dummy value
fatalError("implement me")
}
}
// a small change in the function signature
protocol Manager {
typealias ManagerValueType: Value
func read() -> Reader<ManagerValueType>?
}
class Reader1: Reader<Value1> {
override func value() -> Value1 {
return Value1()
}
}
class Reader2: Reader<Value2> {
override func value() -> Value2 {
return Value2()
}
}
class Manager1: Manager {
typealias ManagerValueType = Value1
func read() -> Reader<ManagerValueType>? {
return Reader1()
}
}
let manager = Manager1()
// you have to cast it, otherwise it is of type Reader<Value1>
let a: Reader1? = manager.read() as! Reader1?
This implementation should solve you problem, but the Readers are now reference types and a copy function should be considered.
I have a couple of Swift protocols that describe a general interface that I'm trying to implement in multiple ways:
protocol Identifiable
{
var identifier:String { get }
}
protocol ItemWithReference
{
var resolveReference<T:Identifiable>(callback:(T) -> ())
}
Now I want to implement the ItemWithReference protocol using CloudKit as the back end (this will eventually work with an alternate back-end as well, at which time I expect to provide an alternative implementation of the ItemWithReference protocol.
In my CloudKit implementation, I have something like this:
class CloudKitIdentifiable : Identifiable
{
...
}
class CloudKitItemWithReference : ItemWithReference
{
func resolveReference<T:Identifiable>(callback:(T) -> ())
{
// In this implementation, I want to only proceed if `T` is a CloudKitIdentifiable subtype
// But not sure how to enforce that
}
}
What I would like to do is to constrain T to be a CloudKitIdentifiable rather than just a simple Identifiable. I can't do that directly in the resolveReference declaration because then the function wouldn't conform to the ItemWithReference protocol. So instead, I am hoping to confirm that T is indeed a CloudKitIdentifiable and then invoke it's initializer to create a new instance of the class being resolved.
Is there any way in Swift to use T's metatype T.Type and determine if it is a subtype of another type? Furthermore, is there any way to invoke a required initializer that has been declared on that subtype?
try:
class CloudKitIdentifiable : Identifiable {
var identifier:String = ...
required init() {}
// you need `required`.
}
class CloudKitItemWithReference : ItemWithReference {
func resolveReference<T:Identifiable>(callback:(T) -> ()) {
if T.self is CloudKitIdentifiable.Type {
// do work..
let obj = (T.self as CloudKitIdentifiable.Type)()
callback(obj as T)
}
}
}
OR:
class CloudKitItemWithReference : ItemWithReference {
func resolveReference<T:Identifiable>(callback:(T) -> ()) {
if let CKT = T.self as? CloudKitIdentifiable.Type {
// do work..
let obj = CKT()
callback(obj as T)
}
}
}
But, In this case, you have to call resolveReference like this:
let ref = CloudKitItemWithReference()
ref.resolveReference { (obj: CloudKitIdentifiable) -> () in
// ^^^^^^^^^^^^^^^^^^^^ explicit type is necessary.
println(obj.identifier)
return
}
Rathar than that, I would recommend to use Associated Type:
protocol Identifiable {
var identifier:String { get }
}
protocol ItemWithReference {
typealias Item: Identifiable // <-- HERE is associated type
func resolveReference(callback:(Item) -> ())
}
class CloudKitIdentifiable : Identifiable {
var identifier:String
init(identifier: String) {
self.identifier = identifier
}
}
class CloudKitItemWithReference : ItemWithReference {
// `Item` associated type can be inferred from
// the parameter type of `resolveReference()`
//
// typealias Item = CloudKitIdentifiable
func resolveReference(callback:(CloudKitIdentifiable) -> ()) {
let obj = CloudKitIdentifiable(identifier: "test")
callback(obj)
}
}
let ref = CloudKitItemWithReference()
ref.resolveReference { obj in
println(obj.identifier)
return
}