Conformance to a protocol (as a protocol) - swift

Say I have a protocol Item, and a struct ConcreteItem that conforms to it.
protocol Item {
var name: String { get }
}
struct ConcreteItem: Item {
let name: String
}
At some point I want to have two sets of ConcreteItem.
let set1 = Set([ConcreteItem(name: "item1")])
let set2 = Set([ConcreteItem(name: "item2"), ConcreteItem(name: "item1")])
Which I'd expect to return the item with name "item1".
I can make ConcreteItem conform to Hashable and the Set code will work. However, lets say I also had the following:
struct AnotherConcreteItem: Item {
let name: String
}
I'd like AnotherConcreteItem to also conform to Hashable simply for having conformed to Item.
However, when I try to implement that idea:
extension Item: Hashable {
var hashValue: Int {
return name.characters.count
}
}
I get the following error: Extension of protocol 'Item' cannot have an inheritance clause.

Extension of protocol 'Item' cannot have an inheritance clause
Here item is the protocol so conforming Hashable protocol will not work. For more details Refer here

What you are trying to do is possible with some protocols, but not all. If the protocol you are trying to conform to does not have any associated types or Self, this is possible.
Example:
protocol A {
func foo()
}
protocol Item: A {
var name: String { get }
}
struct ConcreteItem: Item {
let name: String
}
extension Item {
func foo() {
}
}
Everything that conforms to Item will also conform to A.
However, Hashable has Self constraints. To conform to Hashable, you must also conform to Equatable. To conform to Equatable, the implementation of == must be in a concrete class, not another protocol because Equatable can't be used as a parameter type. The most you can do is something like this:
protocol Item: Hashable {
var name: String { get }
}
struct ConcreteItem: Item {
let name: String
// you need to implement == in every concrete class
static func ==(lhs: ConcreteItem, rhs: ConcreteItem) -> Bool {
return ...
}
}
extension Item {
var hashValue: Int {
return name.characters.count
}
}

Related

How to express a relationship between generic parameters in a SwiftUI view

I have defined the following protocols:
protocol ListableArrayElement: Identifiable, Equatable {
associatedtype T = Hashable
var id:T { get }
}
protocol Listable: ObservableObject, RandomAccessCollection where Element == ArrayElement {
associatedtype ArrayElement: ListableArrayElement
var _elements: [ArrayElement] { get set }
var count: Int { get }
var isEmpty: Bool { get }
var endIndex: Int { get }
var startIndex: Int { get }
subscript(position: Int) -> ArrayElement { get set }
func index(after i: Int) -> Int
func append(_ element: ArrayElement)
func insert(_ newElement: ArrayElement, at i: Int)
func remove(at index: Int)
func removeAll()
func index(of element: ArrayElement) -> Int?
}
protocol FavouritesArray: Listable, Codable where Element: Codable {
// MARK: - PROPERTIES
var _elements: [Element] { get set }
var key: String { get set }
init()
init(key: String)
}
There is an associated extension to FavouritesArray that provides conforming types with the ability to add/remove elements, and persist/load the array to/from UserDefaults via the key. All well and good. (Also note the listable protocol helps me avoid writing some boiler plate code for ViewModels that have an array of 'something' at their heart.)
Now I also want to write a generic SwiftUI view that can build a menu, using the FavouriteArray functions. I am struggling to understand how to express the type signature:
I'm passing instances of types conforming to FavouritesArray via EnvironmentObject, and thus want to write something like:
struct FavouritesArrayView<Favourites: FavouritesArray, Favourite>: View
where Favourite == FavouritesArray.Element {
#EnvironmentObject var favourites: Favourites
#ObservedObject var favourite: Favourite
// Other properties here
var body: some View {
// Layout code here
}
}
This gives the compiler error: Associated type 'Element' can only be used with a concrete type or generic parameter base
Any tips on how to achieve this?
First of all you need to declare that Favourite conforms to ObservableObject and then the where condition should use your associated type and not the protocol it conforms to, where Favourites.Element == Favourite
struct FavouritesArrayView<Favourites: FavouritesArray, Favourite: ObservableObject>: View where Favourites.Element == Favourite

Why does allCases Variable of Protocol conforming to CaseIterable not conform to RandomAccessCollection?

Context
I have a protocol Component with an Object conforming to Category as its associated type. I also have a generic SwiftUI ComponentsView, in which I'd like to use the Category of the given Component inside a ForEach. However, I get the following Compiler Error:
Generic struct 'ForEach' requires that 'C.C.AllCases' conform to 'RandomAccessCollection'
Code
protocol Component {
associatedtype C: Category
}
protocol Category: Identifiable, CaseIterable {
var name: String { get }
}
struct ComponentsView<C: Component>: View {
var body: some View {
ForEach(C.C.allCases) { category in
Text(category.name)
}
}
}
Question
What causes the Compiler Error and how can I solve it?
From my understanding, the allCases variable of CaseIterable returns an Array, which already conforms to RandomAccessCollection.
In CaseIterable AllCases is defined with a default value:
associatedtype AllCases : Collection = [Self] where Self == Self.AllCases.Element
but that's all it is, a default value. In ComponentsView:
struct ComponentsView<C: Component>: View {
var body: some View {
ForEach(C.C.allCases) { category in
Text(category.name)
}
}
}
All the compiler sees is a Component
protocol Component {
associatedtype C: Category
}
that has a Category that is CaseIterable:
protocol Category: Identifiable, CaseIterable {
var name: String { get }
}
But there is no information about its associated type. So, it could be an array, or it could be another type of collection.
Adding a constraint on Component.C solves the issue:
protocol Component {
associatedtype C: Category where C.AllCases: RandomAccessCollection
}

Using swift protocols as Equatable argument

For example we have two simple protocols:
protocol Container {
var items: [Item] {get}
}
protocol Item {
var name: String {get}
}
And we want to use a function that requires Item to be Equatable, like:
extension Container {
func indexOfItem(_ item: Item) -> Int? {
items.firstIndex(of: item)
}
}
Yet we can't write it like this, as firstIndex requires argument conforming to Equatable. But Item being protocol, can't conform to Equatable.
I've tried to implement it using type erasure, but it became too complicated.
So I am curious is there some workaround to implement such functions (provided that actual items are equatable)?
Since you're using func firstIndex of an array in this func indexOfItem(_ item: Item) -> Int? therefore the Item has to be a concrete object (behind the scene of firstIndex func is comparing each element of an array and print out the index of the element).
There are 2 ways to do this
First is using associatedtype to keep your protocol generic
protocol Item: Equatable {
var name: String { get }
}
protocol Container {
associatedtype Item
var items: [Item] { get }
}
struct MyItem: Item {
var name: String
}
extension Container where Item == MyItem {
func indexOfItem(_ item: Item) -> Int? {
return items.firstIndex(of: item)
}
}
Second is using an equatable object MyItem instead a protocol Item inside the Container protocol
protocol Item {
var name: String { get }
}
protocol Container {
var items: [MyItem] { get }
}
struct MyItem: Item, Equatable {
var name: String
}
extension Container {
func findIndex(of item: MyItem) -> Int? {
return items.firstIndex(of: item)
}
}
Finally find simple enough solution:
То make protocol generic with associated type and constraint
this type to Equatable.
public protocol Container {
associatedtype EquatableItem: Item, Equatable
var items: [EquatableItem] {get}
}
public protocol Item {
var name: String {get}
}
public extension Container {
func indexOfItem(_ item: EquatableItem) -> Int? {
items.firstIndex(of: item)
}
}
This compiles and now if I have some types
struct SomeContainer {
var items: [SomeItem]
}
struct SomeItem: Item, Equatable {
var name: String
}
I only need to resolve associatedtype to provide protocol conformance for SomeContainer type:
extension SomeContainer: Container {
typealias EquatableItem = SomeItem
}

What is the proper way to reference a static variable on a Swift Protocol?

Assume a protocol defined below:
protocol Identifiable {
static var identifier: String { get }
}
extension Identifiable {
static var identifier: String { return "Default Id" }
}
What is the best way to reference the static variable? The example below illustrates two ways to access the variable. What is the difference, and is the type(of:) better?
func work<I: Identifiable>(on identifiable: I) {
let identifier: String = I.identifier
print("from Protocol: \(identifier)")
let identiferFromType: String = type(of: identifiable).identifier
print("using type(of:): \(identiferFromType)")
}
struct Thing: Identifiable {
static var identifier: String { return "Thing" }
}
work(on: Thing())
In the example you show, there is no difference. Because identifier is a protocol requirement, it will be dynamically dispatched to in both cases, therefore you don't need to worry about the wrong implementation being called.
However, one difference arises when you consider the value of self inside the static computed property when classes conform to your protocol.
self in a static method/computed property is the metatype value that it's is called on. Therefore when called on I, self will be I.self – which is the static type that the compiler infers the generic placeholder I to be. When called on type(of: identifiable), self will be the dynamic metatype value for the identifiable instance.
In order to illustrate this difference, consider the following example:
protocol Identifiable {
static var identifier: String { get }
}
extension Identifiable {
static var identifier: String { return "\(self)" }
}
func work<I : Identifiable>(on identifiable: I) {
let identifier = I.identifier
print("from Protocol: \(identifier)")
let identiferFromType = type(of: identifiable).identifier
print("using type(of:): \(identiferFromType)")
}
class C : Identifiable {}
class D : C {}
let d: C = D()
// 'I' inferred to be 'C', 'type(of: d)' is 'D.self'.
work(on: d)
// from Protocol: C
// using type(of:): D
In this case, "which is better" completely depends on the behaviour you want – static or dynamic.

Swift associatedType from protocol type - how to do that?

I'm having issues using associated type as protocol:
protocol Searchable{
func matches(text: String) -> Bool
}
protocol ArticleProtocol: Searchable {
var title: String {get set}
}
extension ArticleProtocol {
func matches(text: String) -> Bool {
return title.containsString(text)
}
}
struct FirstArticle: ArticleProtocol {
var title: String = ""
}
struct SecondArticle: ArticleProtocol {
var title: String = ""
}
protocol SearchResultsProtocol: class {
associatedtype T: Searchable
}
When I try to implement search results protocol, I get compile issue:
"type SearchArticles does not conform to protocol SearchResultsProtocol"
class SearchArticles: SearchResultsProtocol {
typealias T = ArticleProtocol
}
As I understand, it happens because T in SearchArticles class is not from concrete type (struct in that example), but from protocol type.
Is there a way to solve this issue?
Thanks in advance!
An associatedtype is not meant to be a placeholder (protocol), it is meant to be a concrete type. By adding a generic to the class declaration you can achieve the same result you want like this....
class SearchArticles<V: ArticleProtocol>: SearchResultsProtocol {
typealias T = V
}
Then, as you use SearchArticles around your app, you can declare let foo = SearchArticles<FirstArticle> or let foo = SearchArticles<SecondArticle>