Swift struct extension by protocol - swift

I have a simple struct to group some strings
struct MyStruct {
static let bar = "BAR"
static let foo = "FOO"
}
MyStruct.bar will return the string BAR
I want to add more properties to each item in the struct. Like:
MyStruct.bar.desc should return some custom description about the MyStruct item bar.
Can we extend the struct like an enum? Something like:
protocol MyProtocol {
var desc:String { get }
}
extension MyProtocol where <No idea> { //where Self: RawRepresentable in Enum-case
var desc: String {
return "Custom description" //Based on the struct item
}
}
So that, struct MyStruct:MyProtocol {.. should work.
Note: I have N number of structs like MyStruct. So, I don't want to implement the custom properties inside each struct individually.
Thanks

Yes you can create a protocol
protocol MyProtocol {}
With a default value for desc
extension MyProtocol {
var desc: String { "Custom description" /*Based on the struct item*/ }
}
And add it to your structs like
struct MyStruct: MyProtocol {}
See https://replit.com/#Dev1an/Swift-protocol-extension for a more advanced version

If you you want to be able to write MyStruct().<item>.desc I would suggest you take a look at CustomStringConvertible:
Types that conform to the CustomStringConvertible protocol can provide their own representation to be used when converting an instance to a string.
I'm not sure why bar is static since it seems you want a property so I added a baz property of type Baz:
struct MyStruct: MyProtocol, CustomStringConvertible { // or you can make `MyProtocol` inherit from CustomStringConvertible
struct Baz: CustomStringConvertible {
let qux = "QUX"
var description: String {
qux
}
}
static let bar = "BAR"
static let foo = "FOO"
let baz = Baz()
}
protocol MyProtocol {
associatedtype Item: CustomStringConvertible
associatedtype OtherItem: CustomStringConvertible
static var bar: Item { get }
var baz: OtherItem { get }
}
extension MyProtocol where Self: CustomStringConvertible {
var description: String {
return print(String(describing: baz)
}
}
print(String(describing: MyStruct.bar)) // prints "BAR"
print(String(describing: MyStruct()) // prints "QUX"

Related

Use generics in place of a dedicated variable enum

I have a protocol. This is implemented by many structs that fall into one of two types of category: TypeOne and TypeTwo. I want to be able to distinguish between their types, so I've added an enum ProtocolType that defines the types typeOne and typeTwo. By default I set the protocolType to be typeOne, but I manually specify typeTwo when it's a TypeTwo struct:
enum ProtocolType {
case typeOne
case typeTwo
}
protocol MyProtocol {
let name: String { get }
var protocolType: ProtocolType { get }
}
extension MyProtocol {
var protocolType: ProtocolType {
return .typeOne
}
}
enum TypeOne {
struct Foo: MyProtocol {
let name = "foo"
}
}
enum TypeTwo {
struct Bar: MyProtocol {
let name = "bar"
let protocolType = .typeTwo
}
}
Is there any way I can remove the necessity for defining protocolType in all structs and somehow use generics to identify what type a struct is? They're already defined under the TypeOne and TypeTwo convenience enums, I was wondering if I could utilise that some how?
Given some protocol:
protocol MyProtocol {
var name: String { get }
}
It sounds like you want to "tag" certain types as special, even though they have the same requirements. That's not an enum, that's just another type (protocol):
// No additional requirements
protocol SpecialVersionOfMyProtocol: MyProtocol {}
You can then tag these at the type level, not the value level:
struct Foo: MyProtocol {
let name = "foo"
}
struct Bar: SpecialVersionOfMyProtocol {
let name = "bar"
}
And you can tell the difference using is if you need to:
func f<T: MyProtocol>(x: T) {
if x is SpecialVersionOfMyProtocol {
print("special one")
}
}
In most cases, though, I wouldn't use this kind of runtime check. I'd just have two protocols (one for TypeOne and one for TypeTwo), and implement whatever you need as extensions on those. For example, say you want to print the name differently depending on the type. Start with a protocol that just expresses that:
protocol NamePrintable {
var name: String { get }
func printName()
}
func printIt<T: NamePrintable>(x: T) {
x.printName()
}
Then extend that for TypeOnes and TypeTwos:
protocol TypeOne: NamePrintable {}
extension TypeOne {
func printName() {
print("I'm a type one with the name \(name)")
}
}
protocol TypeTwo: NamePrintable {}
extension TypeTwo {
func printName() {
print("I'm a type two with the name \(name)")
}
}
And conform your structs:
struct Foo: TypeOne {
let name = "foo"
}
struct Bar: TypeTwo {
let name = "bar"
}
printIt(x: Foo()) // I'm a type one with the name foo
printIt(x: Bar()) // I'm a type two with the name bar
If you want a default implementation, you can hang it on NamePrintable, but I kind of recommend not doing that for what you've described. I'd probably just have "type one" and "type two" explicitly.
extension NamePrintable {
func printName() {
print("BASE FUNCTIONALITY")
}
}

Encountering issues when using a protocol based Enum

A simplified version of what I currently have, taken from a Playground file I setup:
import Foundation
/// Simplified protocol here
protocol MyProtocol: CaseIterable {
static var count: Int { get }
var name: String { get }
}
/// Simplified extension. This works fine with app
extension MyProtocol {
static var count: Int {
return Self.allCases.count
}
}
/// Simplified enum, this works fine as well
enum MyEnum: MyProtocol {
case value
var name: String {
return "name"
}
}
Using the following works as expected:
print(MyEnum.count) // 1
let myEnum = MyEnum.value
print(myEnum.name) // name
However, I would like to create an object that is initialized with MyEnum.
First, I attempted the following:
final class MyManager {
private let myEnum: MyProtocol
init(myEnum: MyProtocol) {
self.myEnum = myEnum
}
}
However, both spots where I use MyProtocol provide the following error:
Protocol 'MyProtocol' can only be used as a generic constraint because
it has Self or associated type requirements
I then switched it up with the following that got rid of the error, but produces a new issue:
final class MyManager<MyProtocol> {
private let myEnum: MyProtocol
init(myEnum: MyProtocol) {
self.myEnum = myEnum
}
}
When I attempt to access properties for myEnum, they do not appear in Xcode:
I need to be able to access the properties defined in MyProtocol, but neither approach is working properly for me & I have run out of ideas.
The MyProtocol generic is not the same as the MyProtocol protocol. You need something like this
final class MyManager<MyP: MyProtocol> {
private let myEnum: MyP
init(myEnum: MyP) {
self.myEnum = myEnum
print(myEnum.name)
}
}
I also want to point out that a better way to implement count is by extending CaseIterable
extension CaseIterable {
static var count: Int {
return Self.allCases.count
}
}

Conformance to a protocol (as a protocol)

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
}
}

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>

Protocol Oriented Programming: General struct of type Any in Protocol + introspection valueForKey()

When setting a protocol for a struct with type Any, I expect it would be possible to set the type of a struct to what I want. The following code is pasted from a playground I made:
import Foundation
// MyStruct does not conform to protocol StructProtocol
// With a general type for struct, like Any, AnyStruct or just Struct, this should be possible
struct MyStruct: StructProtocol {
var thisStruct: ThisStruct
var thatStruct: ThatStruct
}
struct ThisStruct {
var someString: String
}
struct ThatStruct {
var someOtherString: String
var andAnotherString: String
}
protocol StructProtocol {
// Should be able to put Any / AnyStruct / Struct here for a generic struct
var thisStruct: Any {get}
var thatStruct: Any {get}
}
extension StructProtocol {
func saySomething() {
let firstMirror = Mirror(reflecting: self.thisStruct)
let secondMirror = Mirror(reflecting: self.thatStruct)
for structVar in firstMirror.children {
print(self.thisStruct.valueForKey(structVar))
}
for structVar in secondMirror.children {
print(self.thatStruct.valueForKey(structVar))
}
}
}
var thisStruct = ThisStruct(someString: "Hi")
var thatStruct = ThatStruct(someOtherString: "Hello", andAnotherString: "Hola")
var myStruct = MyStruct(thisStruct: thisStruct, thatStruct: thatStruct)
myStuct.saySomething()
// Should output "Hi", "Hello" and "Hola"
Today there is no generic struct type the protocol can refer to. One have to set the struct vars to exactly the same type as the protocol. Maybe I misunderstood this and there is a way to accomplish what Im looking for, but so far I have not found an answer.
I think it would be great to do this in Swift, so if there is no way of accomplishing this, Ill post it as a feature request in the Swift project, but first Ill run this by you experts here in StackOverflow.
So the question is: Is this possible in some other way?
You can do what you describe using multiple protocols. The issue with what you're asking for is how you would use these generic structs (if they don't conform to some other protocol or take some generic parameters) - you'd have to cast them and then the whole point of the generic nature you ask for is lost.
So, you can do something like:
protocol InnerProtocol {
func printChildren()
}
protocol StructProtocol {
var inner1: InnerProtocol {get}
var inner2: InnerProtocol {get}
}
struct MyStruct : StructProtocol {
var inner1: InnerProtocol
var inner2: InnerProtocol
}
struct ThisStruct : InnerProtocol {
var someString: String
func printChildren() {
print(someString)
}
}
struct ThatStruct : InnerProtocol {
var someOtherString: String
var andAnotherString: String
func printChildren() {
print(someOtherString)
print(andAnotherString)
}
}
extension StructProtocol {
func saySomething() {
inner1.printChildren()
inner2.printChildren()
}
}
var thisStruct = ThisStruct(someString: "Hi")
var thatStruct = ThatStruct(someOtherString: "Hello", andAnotherString: "Hola")
var myStruct = MyStruct(inner1: thisStruct, inner2: thatStruct)
myStruct.saySomething()