I want to create an interface (protocol) for Tree in Swift. This tree will use interface (protocol) for TreeNodes. But all these interfaces should be generic. Ideally I want to have something like this:
protocol TreeNodeInterface {
associatedtype T: ElementInterface
var value: T { get set }
// ... some other methods
}
protocol TreeInterface {
var rootNode: TreeNodeInterface<T>? { get }
func clean()
// .. some other methods
}
class Tree<T: ElementInterface>: TreeInterface {
var root: TreeNodeInterface<T>?
var rootNode: TreeNodeInterface<T>? {
get {
return root
}
}
func clean() {
}
}
So for example I will have class Tree inherited from TreeInterface and I can initialize that Tree with any type (Int, String, CustomClass etc), so that each node will have that type as a value.
I managed to do this with Object Oriented Programming, but cannot do it with Protocol Oriented Programming
Swift doesn't allow me to do this. Can someone help me here?
Thanks
I think you're trying to do too much with protocols. Your biggest problem here is that you cannot create a variable with a protocol type if that protocol has an associated type (or a Self requirement). By replacing these definitions with generics, I ended up with this:
protocol TreeNodeInterface {
associatedtype Element: ElementInterface
var value: Element { get set }
// ... some other methods
}
protocol TreeInterface {
associatedtype Node: TreeNodeInterface
var rootNode: Node? { get }
func clean()
// .. some other methods
}
class Tree<T: TreeNodeInterface>: TreeInterface {
typealias Node = T
var rootNode: T?
init() {}
func clean() {
}
}
This compiles, but now you have to figure out how to initialize it. Next step is to make a type which conforms to TreeNodeInterface:
struct TreeNode<T: ElementInterface>: TreeNodeInterface {
typealias Element = T
var value: T
}
This looks strikingly similar to the protocol, but that's alright. Now let's initialize Tree:
// Assuming that Int conforms to ElementInterface
let tree = Tree<TreeNode<Int>>()
Phew! That was a lot of work, most of which I consider unnecessary. Do you really need TreeNodeInterface and TreeInterface? I'd argue that you don't. Here's what it might look like if you used concrete types instead:
struct TreeNode<T: ElementInterface> {
var value: T
}
class Tree<T: ElementInterface> {
var root: TreeNode<T>?
init() {}
func clean() {
}
}
let tree = Tree<Int>()
Related
How would one go about assigning an instance of a class which can have a generic type, but you don't know what type that is until run time?
For example.
We have a protocol and enums that conform to it like this:
protocol Stage: CaseIterable, Hashable {
var fooBarLength: Int { get }
}
enum FirstStage: String, Stage {
var fooBarLength: Int { 10 }
case section1
case section2
}
enum SecondStage: String, Stage {
var fooBarLength: Int { 10 }
case section1
case section2
case section3
}
Next we have some kind of controller that uses the protocol as a generic type... comme ça...
class FooBarController<StageType: Stage>: UIViewController {
private var stages: [StageType: Float] = [:]
}
Then used like this:
func fooBarScreen(boop: SomethingThatKnowsAboutTheStages) {
var fooBarController: FooBarController // <---- how do I define this????
if boop.someCondition() {
fooBarController = FooBarController<FirstStage>()
} else {
fooBarController = FooBarController<SecondStage>()
}
}
In Java / Kotlin I could just do this as it is above, how do I achieve the same thing in Swift?
Currently I get
"Reference to generic type 'FooBarController' requires arguments in <...>"
Secondary Question
Is there a more generic way than having to use that if-statement here? Ideally I would like the fooBarScreen method to not care about the generic type and just have SomethingThatKnowsAboutTheStages provide the type for me.
You can specify a protocol for providing Stage types like so:
protocol StageProvider {
associatedtype T: Stage
func getType() -> T.Type
}
Then make your SomethingThatKnowsAboutTheStages or any other one conform this protocol:
class SomethingThatKnowsAboutTheStages: StageProvider {
typealias T = SecondStage
func getType() -> T.Type {
SecondStage.self
}
}
Add an initializer for your FooBarController:
class FooBarController<StageType: Stage>: UIViewController {
convenience init(stage: StageType.Type) {
self.init()
}
}
And finally use all these:
func fooBarScreen<T: StageProvider>(boop: T) {
let controller = FooBarController(stage: boop.getType())
}
I make a extension for Array where Elements are Object, so I can compare them by their address.
public extension Array where Element: AnyObject {
func hasExactSameItem(_ searchingItem: Element) -> Bool {
guard let _ = indexOfExactSameItem(searchingItem) else { return false }
return true
}
func indexOfExactSameItem(_ searchingItem: Element) -> Int? {
for (index, item) in self.enumerated() {
if searchingItem === item {
return index
}
}
return nil
}
}
There is another protocol conforming AnyObject protocol
public protocol IMServerListener: AnyObject {
}
I have a array containing IMServerListener
private var listeners = [IMServerListener]()
When I start adding listener to that array, the compiler complaining that '[IMServerListener]' requires that 'IMServerListener' conform to 'AnyObject'
func addListener(_ listener: IMServerListener) {
listeners.hasExactSameItem(listener)
}
I thought the IMServerListener is conforming the AnyObject protocol, so why did it happened?
I tried to construct a similar example in a playground:
import UIKit
protocol Foo: AnyObject {
func foo()
}
protocol Bar: Foo {}
class A: Bar {
func foo() {}
}
var bars = [Bar]()
bars.append(A())
bars.append(A())
bars[0] === bars[1] // equals operator used from AnyObject
Observations:
(1) Protocols can inherit from each other. If I leave out the implementation of foo() in class A it leads to a compiler error.
(2) Class A does inherit from AnyObject through Bar and Foo otherwise I could not use the equals operator.
To your question:
compiler complaining that '[compiler complaining that '[IMServerListener]' requires that 'IMServerListener' conform to 'AnyObject']' requires that 'IMServerListener' conform to 'AnyObject' sounds like there might be something wrong with the implementation of IMServerListener itself.
I am happy to extend my answer if you can show us this implementation / more code in general.
Cheers,
Dominic
While experimenting with generics in Swift I came across this problem and am not able to find an answer.
Say I have the following code:
protocol Component {
}
protocol Contains {
associatedtype CompType: Component
var components: [CompType] { get set }
}
Here, types that implement Contains should be able to store any type that implements the Component protocol specified by their typealias. If I extend the code to the following, it works as expected.
protocol Component {
}
struct SomeComponent: Component {
init() {
}
}
struct AnotherComponent: Component {
init() {
}
}
protocol Contains {
associatedtype CompType: Component
var components: [CompType] { get set }
}
struct Container: Contains {
typealias CompType = SomeComponent
var components: [SomeComponent]
}
var x = Container(components: [SomeComponent()]) // works perfectly!
var y = Container(components: [AnotherComponent()]) // fails as expected
Finally the question: is it possible to make Container.components accept both SomeComponent and AnotherComponent, but reject other types that implement the Component protocol? In other words, can typealias hold more that one type?
Thanks!
You can conform to multiple protocols
typealias CompType = SomeComponent & AnotherComponent
Or you can do changing logic
protocol Component {
}
struct SomeComponent: Component {
init() {
}
}
struct AnotherComponent: Component {
init() {
}
}
protocol Contains {
var components: [Component] { get set }
}
struct Container: Contains {
var components: [Component]
}
var x = Container(components: [SomeComponent()])
var y = Container(components: [AnotherComponent()])
The type alias serves to resolve the generic associated type. It must resolve it to one type unambiguously; that is what resolution is.
Well, the way you’ve set things up, that type can and must be any Component adopter. That is what this line means:
associatedtype CompType: Component
You used a generic constraint of Component. If that’s not what you wanted, you shouldn’t have set things up that way. As others have suggested, if you want just SomeComponent and AnotherComponent to satisfy the generic constraint, then you would need to use a protocol that only SomeComponent and AnotherComponent adopt.
I don't think you need the associatedtype at all
protocol Component {
}
struct SomeComponent: Component {
init() {
}
}
struct AnotherComponent: Component {
init() {
}
}
protocol Contains {
var components: [Component] { get set }
}
struct Container: Contains {
var components: [Component]
}
var x = Container(components: [SomeComponent()])
var y = Container(components: [AnotherComponent()])
Use associatedtype on protocols when you need to specify a type.
On this case you don't want to specify because you want to be able to accept a conformer to a protocol
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 Car {
var wheels : Int { get set}
init(wheels: Int)
}
extension Car {
init(wheels: Int) {
self.wheels = wheels
}
}
on self.wheels = wheels i get the error
Error: variable 'self' passed by reference before being initialized
How can I define the initializer in the protocol extension?
As you can see this doesn't work under these circumstances because when compiling, one has to make sure that all properties are initialized before using the struct/enum/class.
You can make another initializer a requirement so the compiler knows that all properties are initialized:
protocol Car {
var wheels : Int { get set }
// make another initializer
// (which you probably don't want to provide a default implementation)
// a protocol requirement. Care about recursive initializer calls :)
init()
init(wheels: Int)
}
extension Car {
// now you can provide a default implementation
init(wheels: Int) {
self.init()
self.wheels = wheels
}
}
// example usage
// mark as final
final class HoverCar: Car {
var wheels = 0
init() {}
}
let drivableHoverCar = HoverCar(wheels: 4)
drivableHoverCar.wheels // 4
As of Xcode 7.3 beta 1 it works with structs as expected but not with classes since if they are not final the init(wheels: Int) in the protocol is a required init and it can be overridden therefore it cannot be added through an extension. Workaround (as the complier suggests): Make the class final.
Another workaround (in depth; without final class)
To work with classes without making them final you can also drop the init(wheels: Int) requirement in the protocol. It seems that it behaves no different than before but consider this code:
protocol Car {
var wheels : Int { get set }
init()
// there is no init(wheels: Int)
}
extension Car {
init(wheels: Int) {
self.init()
print("Extension")
self.wheels = wheels
}
}
class HoverCar: Car {
var wheels = 0
required init() {}
init(wheels: Int) {
print("HoverCar")
self.wheels = wheels
}
}
// prints "HoverCar"
let drivableHoverCar = HoverCar(wheels: 4)
func makeNewCarFromCar<T: Car>(car: T) -> T {
return T(wheels: car.wheels)
}
// prints "Extension"
makeNewCarFromCar(drivableHoverCar)
So if you make a Car from a generic context where the type on which you call init is only to be known as Car the extension initializer is called even though an initializer is defined in HoverCar. This only occurs because there is no init(wheels: Int) requirement in the protocol.
If you add it you have the former problem with declaring the class as final but now it prints two times "HoverCar". Either way the second problem probably never occurs so it might be a better solution.
Sidenote: If I have made some mistakes (code, language, grammar,...) you're welcome to correct me :)
My understanding is that this isn't possible, because the protocol extension can't know which properties the conforming class or struct has - and therefore cannot guarantee they are correctly initialized.
If there are ways to get around this, I'm very interested to know! :)
#Qbyte is correct.
In addition, you can take a look at my Configurable
In that I have Initable protocol
public protocol Initable {
// To make init in protocol extension work
init()
}
public extension Initable {
public init(#noescape block: Self -> Void) {
self.init()
block(self)
}
}
Then in order to conform to it
extension Robot: Initable { }
I have 2 ways, using final or implement init
final class Robot {
var name: String?
var cute = false
}
class Robot {
var name: String?
var cute = false
required init() {
}
}
May not be the same but in my case instead of using init I used a static func to return the object of the class.
protocol Serializable {
static func object(fromJSON json:JSON) -> AnyObject?
}
class User {
let name:String
init(name:String) {
self.name = name
}
}
extension User:Serializable {
static func object(fromJSON json:JSON) -> AnyObject? {
guard let name = json["name"] else {
return nil
}
return User(name:name)
}
}
Then to create the object I do something like:
let user = User.object(fromJSON:json) as? User
I know its not the best thing ever but its the best solution I could find to not couple business model with the data layer.
NOTE: I'm lazy and I coded everything directly in the comment so if something doesn't work let me know.