Constrained protocol still complaining as being generic [duplicate] - ios

I've got the following snippet:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
var test: [MyProtocol] = []
Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Why doesn't this work? Shouldn't the where ID == UUID remove the ambiguity the error is concerned with? Am I missing something here?
I think this question is similar to this one: Usage of protocols as array types and function parameters in swift
However, I would have assumed that adding where ID == UUID should fix the problem? Why is that not the case?
Thanks!
Edit
So, this problem has occurred while experimenting with SwiftUI and struct data models. I've always used classes for any kind of data model but it seems like SwiftUI wants to get you to use structs as often as possible (I still don't see how that's realistically possible but that's why I'm experimenting with it).
In this particular case, I tried to have a manager that contains structs that all conform to MyProtocol. For example:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
struct A: MyProtocol { // First data model
var id: UUID = UUID()
}
struct B: MyProtocol { // Second data model
var id: UUID = UUID()
}
class DataManager: ObservableObject {
var myData: [MyProtocol]
}
...
I don't actually have to declare Identifiable on MyProtocol but I thought it would be nicer and cleaner.

Because this is not a current feature of Swift. Once there is an associated type, there is always an associated type. It doesn't go away just because you constrain it. And once it has an associated type, it is not concrete.
There is no way to "inherit" protocols this way. What you mean is:
protocol MyProtocol {
var id: UUID { get }
}
And then you can attach Identifiable to structs that require it:
struct X: MyProtocol, Identifiable {
var id: UUID
}
(note that no where clause is required.)
There is no Swift feature today that allows you to say "types that conform to X implicitly conform to Y." There is also no Swift feature today that allows for an Array of "things that conform to Identifiable with ID==UUID." (That's called a generalized existential, and it's not currently available.)
Most likely you should go back to your calling code and explore why you require this. If you post the code that iterates over test and specifically requires the Identifiable conformance, then we may be able to help you find a design that doesn't require that.

Related

What does it mean when no implementation is provided by the class/struct that adopts a protocol?

As far as I understand, a protocol is a blueprint for properties, methods, and other requirements without the actual implementation. The conformance to a protocol means providing the actual implementation of the blueprint. But, I frequently see classes and struct's adopting a protocol without actually providing the implementation.
For example:
class Item: Codable {
var title: String = ""
var done: Bool = false
}
var itemArray = [Item]()
let encoder = PropertyListEncoder()
do {
let data = try encoder.encode(itemArray)
try data.write(to: dataFilePath!)
} catch {
print("Error encoding item array, \(error)")
}
The Item class here adopts the Codable protocol and the instance of the class is used as an argument to the instance method of PropertyListEncoder(). However, no implementation for the Codable protocol was provided or used in this process.
There are protocols that offer what is called automatic synthesis. In the case of Codable that means that as long as the properties of Item conform to Codable, methods such as init(from:) or encode(to:) are automatically added to that type.
As long as all of its properties are Codable, any custom type can also be Codable.
Encoding and Decoding Custom Types
Another great example is Equatable, see Conforming to the Equatable Protocol
Automatic synthesis is done via protocol extensions.
The Codable protocol is one of a few protocols (Equatable, Hashable) that can be synthesized by the compiler so long as all properties of a type also conform to that protocol. This means that the compiler is generating the implementation for the protocol that you noticed was missing

Calling class properties from within protocol extension

In using protocol extension for default implementation I encounter a problem. I define a protocol with different optional properties and functions to use. In the extension I implement a default function and nil properties. In the implementation I use properties and functions from within the protocol.
On the device it works how I expect. Also on the debugger it gave me the property from the class when I run into breakpoint within the extension implementation.
Can someone help me out why I didn't get the property in the example from the class but the nil property from extension.
Example from playground
import UIKit
protocol Collectable {
var item: String? { get }
func collect()
}
extension Collectable {
var item: String? { return nil }
func collect() {
if let some = item {
print("collect \(some)")
} else {
print("nothing")
}
}
}
class Car: Collectable {
var item = "letter"
}
let car = Car()
car.collect()
In the protocol your item is optional string, while in your class you declared another variable named also item. Your collect function is looking for the optional item, and in the extension you specified that it should return nil.
As #SavcaMarin stated in their answer, you have a type conflict between the item property declared in your extension (String?) and the one in your Car (String).
You can see this if you comment the extension. The compiler will tell you that Car does not conform to Collectable. If you ask it to create the required stubs it will create func collect and var item: String?. It then gives an error that item is re-defined.
As Rob and Matt pointed out in comments on the other answer, this seems to be a bug in the Swift compiler. It doesn't indicate the re-definition of item or the non-conformance of your item to the protocol if there is a default implementation that satisfies the protocol conformance (your extension).
The simple fix is to tell Swift that the item in Car is the same as item in Collectable by declaring the type explicitly (String?) rather than relying on type inference (Which gives String):
class Car: Collectable {
var item:String? = "letter"
}

Dependency injection with associated types causing arguments without type names (undescores) in Swift

The situation is following: I'm using a protocol to inject dependencies and the best way I found to implement this in Swift is to use the associatedtype keyword. I am also using protocol composition since some implementations of TestProtocol need more than one dependency.
protocol TestProtocol: class {
associatedtype Dependencies
func inject(_ dependency: Dependencies)
}
protocol HasSomething {
var something: Something { get set }
}
protocol HasSomethingElse {
var somethingElse: SomethingElse { get set }
}
To use this I found that I'll need to use generics like this:
class TestService<T> where T: TestProtocol, T.Dependencies == TestService {
weak var testProtocol: T?
init(with testProtocol: T) {
self.testProtocol = testProtocol
self.testProtocol?.inject(self)
}
}
Now when I want to use this service somewhere else and I'm trying to initiate it I get following problem:
The parameter is displayed as _ and not as the protocol name TestProtocol.
Let's say I would use this code in a library. How would a user know (without reading the documentation of course) what type could be used in this context when he is not even knowing what protocol he has to implement?
Is there a better way on how to use dependency injection with the type actually being displayed to the user, or am I doing something wrong in the where clause of the TestService class, or is this simply not possible in the current versions of Swift?
There is nothing wrong with your code, this is simply not possible.
class TestService<T> where T: TestProtocol
The where clause means T could be anything, with the constraint that the given object must conform to TestProtocol.
The Xcode autocomplete feature only displays the resolved type when available, but it doesn't show the constraints on a generic, and unfortunately there is nothing you can do about that.
You have the exact same issue in the swift standard library, with Dictionary for example
public struct Dictionary<Key, Value> where Key : Hashable {
public init(dictionaryLiteral elements: (Key, Value)...) {
// ..
}
}
The generic Key as a constraint to Hashable, but Xcode still shows _ in the autocomplete list.
I guess Swift developers are use to this behaviour, so it won't be a big issue, even if your code is embedded in a library.
How would a user know (without reading the documentation of course) what type could be used in this context when he is not even knowing what protocol he has to implement?
Because Xcode is pretty clear about the protocol requirement.
If I try to initialize the TestService with a String I'll get the error:
Referencing initializer 'init(with:)' on 'TestService' requires that 'String' conform to 'TestProtocol'
Which is pretty self explanatory.
Actually at the time of init(with testProtocol: T) Compiler doesn't know about T of course because it is generic
if you provide directly class it will show you in suggestion
For example
class TestService<T:Something> {
weak var testProtocol: T?
init(with testProtocol: T) {
self.testProtocol = testProtocol
}
}
Now you will see compiler know that it need SomeThing at T
For your case For TestProtocol You can replace with with something user readable world. for next time compiler will give you provided type as suggestion
For Example
class TestService<T:TestProtocol> {
weak var testProtocol: T?
init(with testProtocol: T) {
self.testProtocol = testProtocol
}
func add(t:T) {
}
}
class Test {
init() {
let t = Something()
let ts = TestService(with: t)
}
}
In Test class you can type ts.add now it knows

Protocol Oriented Programming

As we transition our brains from Object Oriented Programming to Protocol Oriented Programming how can I do the following ?
let's say I have a JSON object representing Model has {created_time,updated_time,type,...} and those values are common in 5 Model objects.
is it right to make a protocol contains all the above properties like the following
protocol xxx {
var type : String { get }
var updatedTime : String { get }
var createdTime : String { get }
//...//
}
and then all the 5 structs conform to this protocol
I would say that's a perfectly good solution. The alternative would be having a base class with those properties and have all five of those models inherit from the base class, but there's no particular reason to use inheritance here.
A protocol is just a "contract" that guarantees a class has certain properties or behavior. To me, your example here feels very "contractual."
By contrast, inheritance implies a "is-a" relationship (e.g. a Ford is-a car). To me, this feels more like a contract than an "is-a" case. Of course, neither choice is wrong, but think your protocol idea here is good.
Speaking of Protocol Oriented Programming Swift 2 has protocol extensions which allow default implementations. This also replaces many cases where you would use a superclass instead.
In this case:
// extension of your example protocol
extension xxx {
var type : String { return "a type" }
var updatedTime : String { return "00:00" }
var createdTime : String { return "00:00" }
//...//
}
// struct which conforms to it
struct Astruct: xxx {}
// using the properties
Astruct().type
Astruct().updatedTime
if all properties and methods have a default implementation by the protocol extension you don't have to provide any yourself. However you can "override" them only by implementing them.
So you're decision is right and you should use protocols as often as possible.
The only big drawback is that there is no super where you can explicitly call the default implementations. A workaround (see this answer) would require a superclass which makes the protocol almost redundant.

How to define an array of objects conforming to a protocol?

Given:
protocol MyProtocol {
typealias T
var abc: T { get }
}
And a class that implements MyProtocol:
class XYZ: MyProtocol {
typealias T = SomeObject
var abc: T { /* Implementation */ }
}
How can I define an array of objects conforming to MyProtocol?
var list = [MyProtocol]()
Gives (together with a ton of SourceKit crashes) the following error:
Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Even though the typealias is in fact defined in MyProtocol.
Is there a way to have a list of object conforming to a protocol AND having a generic constraint?
The problem is about using the generics counterpart for protocols, type aliases.
It sounds weird, but if you define a type alias, you cannot use the protocol as a type, which means you cannot declare a variable of that protocol type, a function parameter, etc. And you cannot use it as the generic object of an array.
As the error say, the only usage you can make of it is as a generic constraint (like in class Test<T:ProtocolWithAlias>).
To prove that, just remove the typealias from your protocol (note, this is just to prove, it's not a solution):
protocol MyProtocol {
var abc: Int { get }
}
and modify the rest of your sample code accordingly:
class XYZ: MyProtocol {
var abc: Int { return 32 }
}
var list = [MyProtocol]()
You'll notice that it works.
You are probably more interested in how to solve this problem. I can't think of any elegant solution, just the following 2:
remove the typealias from the protocol and replace T with AnyObject (ugly solution!!)
turn the protocol into a class (but that's not a solution that works in all cases)
but as you may argue, I don't like any of them. The only suggestion I can provide is to rethink of your design and figure out if you can use a different way (i.e. not using typealiased protocol) to achieve the same result.

Resources