How to describe a variable in a protocol on swift
any type, but supports a specific protocol
Something like this
protocol MyProtocol: class {
var itemsList: AnyObject where Collection { get } // AnyObject supports a Collection protocol
}
Maybe you want:
protocol MyProtocol: class {
associatedtype T: Collection
var itemsList: T { get }
}
If you want T to definitely be an object as well (not a struct) then you must wait for this proposal to make it into the language.
If you want a class to satisfy this protocol with T unspecified in the class's definition, make the class generic.
class C<T: Collection>: MyProtocol {
let itemsList: T
init(_ itemsList: T) {
self.itemsList = itemsList
}
}
let x = C([1,2,3])
Related
Let's say I have a protocol
protocol MyProtocol {
associatedtype DoSmthValue
func doSmth(_ value: DoSmthValue) -> String
}
I also have another protocol:
protocol AnotherProtocol {
associatedtype T: MyProtocol
var items: [T] { get set }
}
and finally my class where I want to use it:
class MyClass: AnotherProtocol {
typealias T = MyProtocol // this code returns an error
var items = [T]
}
But this code returns me MyProtocol can only be used as a generic constraint because it has Self or associated type requirements which is fair enough. I would like to use it like typealias T = MyProtocol<String> which is not allowed in Swift. So is there a way?
AnotherProtocol has an associated value which must be some type that conforms to MyProtocol. A protocol cannot conform to protocols, including itself, so that type cannot be MyProtocol; it has to be a type conforming to MyProtocol.
var items, then, has to be an array of whatever that type is.
So, let's say you have a type conforming to MyProtocol which defines the associated type to be String:
struct Foo: MyProtocol {
func doSmth(_ value: String) -> String {
value
}
}
Then you can define MyClass as:
class MyClass: AnotherProtocol {
var items: [Foo]
init(_ items: [Foo]) { self.items = items }
}
Of course, if you don't want it to be specific to Foo, you can make it generic with respect to any type T that conforms to MyProtocol and has an associated type of String:
class MyClass<T: MyProtocol>: AnotherProtocol where T.DoSmthValue == String {
var items: [T]
init(_ items: [T]) { self.items = items }
}
let myClass = MyClass([Foo(), Foo(), Foo()])
New Dev's answer is exactly right about how to fix this, but it's also worth realizing why this set of protocols is requiring something that doesn't make sense.
Let's pretend your MyClass definition were legal. I could then write the following code:
for item in items {
// item is of pseudo-type "MyProtocol"
// (that's illegal, but we're pretending it's not)
item.doSmth(... what would go here? ...)
}
As written, DoSmthValue could require literally any type, and you'd have to provide it. But you don't know what it is (and you can't know what it is). There's no way to make use of this protocol; you can't generate the things it requires. So it's not meaningful for item to be of type MyProtocol. You need to go back to thinking about what you want the calling code to look like, and that will tell you what kinds of protocols you need. Starting with the protocols (especially if you're new to this) will typically get you in trouble.
I have some protocol hierarchies on my code where I have protocols defining the objects I use and protocols defining functions to use with this objects.
The object protocols are inherited by other object protocols that add more functionality to the original protocols and so are the functions that use them. The problem is that I can't find a way to specialize the function to take only the inherited parameter.
Here's some code to clarify what I'm trying to do:
protocol A {
var foo: String { get set }
}
protocol B: A {
var bar: String { get set }
}
struct Test: B {
var foo: String = "foo"
var bar: String = "bar"
}
protocol UseAProtocol {
static func use<T: A>(_ obj: T)
}
protocol UseBProtocol: UseAProtocol {
}
extension UseBProtocol {
//If I change the requirement to <T: B> this won't conform to `UseAProtocol`.
static func use<T: A>(_ obj: T) {
print(obj.foo)
// print(obj.bar) - Since obj does not conform to `B` I can't access ".bar" here without a forced casting.
}
}
struct Manager: UseBProtocol {
}
Manager.use(Test())
What I want to do is make the use function on the UseBProtocol only accept objects that conform to B. B inherits from A, but when I change from <T:A> to <T:B> I got an error saying that Manager does not conform to UseAProtocol and I have to change it back to <T:A>.
I know I can do this using associatedtype and where clauses on the inherit protocols - that's what I use today - but I wanted to move the generic requirement to the method so I could group all of them together under the same struct (I have a lot of this hierarchies and by using associatedtype I must use one struct by hierarchy). When the Conditional Conformances came to Swift this would be possible with associatedtype, but until them...
I could also use as! to force the casting from A to B on the UseBProtocol implementation, but that's a really bad solution and the error would be throw only at runtime.
Is there any way to achieve what I'm looking for?
It seems like what you are actually looking for is an associatedType in UseAProtocol rather than making the use function generic.
By declaring an associated type in UseAProtocol and changing the function signature of use to static func use(_ obj: ProtocolType) your code compiles fine and you can access both foo and bar from Manager.
protocol AProtocol {
var foo: String { get set }
}
protocol BProtocol: AProtocol {
var bar: String { get set }
}
struct Test: BProtocol {
var foo: String = "foo"
var bar: String = "bar"
}
protocol UseAProtocol {
associatedtype ProtocolType
static func use(_ obj: ProtocolType)
}
protocol UseBProtocol: UseAProtocol {
}
extension UseBProtocol {
static func use(_ obj: BProtocol) {
print(obj.foo)
print(obj.bar)
}
}
struct Manager: UseBProtocol {
}
Manager.use(Test()) //prints both "foo" and "bar"
I have the following protocols declared.
protocol TypeAProtocol {
...
}
protocol TypeBProtocol {
...
}
protocol SomeProtocol {
associatedtype TypeA: TypeAProtocol
associatedtype TypeB: TypeBProtocol
var objA: TypeA? { get set }
var objB: TypeB? { get set }
}
Now if I want to create a class that implements SomeProtocol, I would have done this
class SomeClass: SomeProtocol {
var objA: ClassOfTypeAProtocol?
var objB: ClassOfTypeBProtocol?
}
The problem I am facing now is, I want to be able to create classes that implement SomeProtocol without var objB: TypeB? { get set }. I want objB to be optional. How can I achieve that?
In Objective-C you can declare an optional member (method/computed property) in a protocol.
In pure Swift this is not possible unless you use the #objc annotation (but in this case structs and enums won't be able to conform to your protocol so it's not the best way to go).
So, how can you solve your problem?
Let's split SomeProtocol into 3 protocols
These are your protocols
protocol TypeAProtocol { }
protocol TypeBProtocol { }
protocol SomeProtocol {
associatedtype TypeA: TypeAProtocol
associatedtype TypeB: TypeBProtocol
var objA: TypeA? { get set }
var objB: TypeB? { get set }
}
Now we'll split SomeProtocol into 3 protocols
protocol SomeProtocolBase {
associatedtype TypeA: TypeAProtocol
associatedtype TypeB: TypeBProtocol
}
protocol SomeProtocolPartA: SomeProtocolBase {
var objA: TypeA? { get set }
}
protocol SomeProtocolPartB: SomeProtocolBase {
var objB: TypeB? { get set }
}
That's it
Now you can write
class ClassOfTypeAProtocol: TypeAProtocol { }
class ClassOfTypeBProtocol: TypeBProtocol { }
class SomeClass: SomeProtocolPartA {
typealias TypeA = ClassOfTypeAProtocol
typealias TypeB = ClassOfTypeBProtocol
var objA: ClassOfTypeAProtocol?
}
The following contrived Swift 2 example from real-world code won't compile:
protocol SomeModelType { }
protocol SomeProtocol {
var someVar: SomeModelType? { get }
}
class ConcreteClass<T: SomeModelType>: SomeProtocol {
var someVar: T?
}
This doesn't make sense to me fully. I would assume in ConcreteClass that because I have T being constrained to SomeModelType and have T as the backing type for the someVar property, the compiler would be able to figure out that the SomeProtocol was being conformed to by ConcreteClass.
How should an example like this be structured? Is it possible to the Swift compiler to determine protocol conformance through generic type constraints?
protocol SomeModelType { }
protocol SomeProtocol {
associatedtype T: Any
var someVar: T? { get }
}
class ConcreteClass<T> :SomeProtocol where T: SomeModelType {
var someVar: T?
}
I'm trying to do something along the lines of this:
protocol Protocol {
associatedtype T
associatedtype ArrayT = Array<T>
}
struct Struct<ProtocolType: Protocol> {
func doSomething(with: ProtocolType.ArrayT) {
let _ = with.map { $0 }
// ^ compiler complains on this line
// "value of type ProtocolType.ArrayT has no member map"
}
}
where I define a convenience typealias ArrayT that uses the associatedtype T. It seems that when I try to use ArrayT like in doSomething(_:), I lose the Array type information of ArrayT.
Shouldn't ArrayT definitely be an Array and therefore a member of the Sequence protocol, exposing the map function? 🤔
the working solution I'm employing now is to just define a generic typealias outside of the protocol:
typealias ProtocolArray<ProtocolType: Protocol> = Array<ProtocolType.T>
struct Struct<ProtocolType: Protocol> {
func doSomething(with: ProtocolArray<ProtocolType>) {
let _ = with.map { $0 } // no complaints
}
}
what am I missing here?
The line associatedtype ArrayT = Array<T> only tells the compiler that the default value of ArrayT is Array<T>. An adaption of the protocol can still change ArrayT like:
struct U: Protocol {
typealias T = UInt32
typealias ArrayT = UInt64 // <-- valid!
}
If you want a fixed type, you should use a typealias...
// does not work yet.
protocol Protocol {
associatedtype T
typealias ArrayT = Array<T>
}
But the compiler complains that the type is too complex 🤷. So the best you could do is constrain the ArrayT to be a Sequence / Collection / etc, and hope that the adaptors won't change the type themselves.
// still somewhat off
protocol Protocol {
associatedtype T
associatedtype ArrayT: Sequence = [T]
}
Note that, however, the Sequence can have any element type, but we want ArrayT's Element must be T. We cannot attach a where clause to the associatedtype:
// fail to compile: 'where' clause cannot be attached to an associated type declaration
associatedtype ArrayT: Sequence where Iterator.Element == T = [T]
Instead, you need to put this constraint every time you use the protocol:
struct Struct<ProtocolType: Protocol>
where ProtocolType.ArrayT.Iterator.Element == ProtocolType.T {
Complete, working code:
protocol Protocol {
associatedtype T
associatedtype ArrayT: Sequence = [T]
// ^~~~~~~~~~
}
struct Struct<ProtocolType: Protocol>
where ProtocolType.ArrayT.Iterator.Element == ProtocolType.T
// ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
func doSomething(w: ProtocolType.ArrayT) {
let _: [ProtocolType.T] = w.map { $0 }
}
}
When you "initialize" an associatedtype, you're not defining it. That's not the point of associated types in the first place. Associated types are deliberately unbound ("placeholders") until the adopting class resolves them all. All you're doing there is giving it a default value, which a conforming class is allowed to override. To extend your example:
protocol Protocol {
associatedtype T
associatedtype ArrayT = Array<Self.T>
func useSomeT(myFavoriteT: T)
func useSomeArrayT(myLeastFavoriteArrayT: ArrayT)
}
class Whatever: Protocol {
func useSomeT(myFavoriteT: Int) {
print("My Favorite Int: \(myFavoriteT)")
}
func useSomeArrayT(myLeastFavoriteArrayT: [Int: String]) {
print(myLeastFavoriteArrayT.map { $0.1 })
}
}
struct Struct<ProtocolType: Protocol> {
func doSomething(stuff: ProtocolType.ArrayT) {
print(type(of: stuff))
}
}
let x = Struct<Whatever>()
x.doSomething(stuff: [3: "Doggies"])
For your example, it seems all you really want is to declare with: Array<ProtocolType.T> (or simply with: [ProtocolType.T]) as the parameter type.