associatedType inside another associatedType using protocols - swift

I have been struggling to find a solution using generics and associated types inside another associated type for the problem above
Case
I want to have an ObjectRequestType, which inside it has an associated type of type ObjectResponseType.
protocol ObjectRequestType {
associatedtype Response: ObjectResponseType
}
ObjectResponseType on the other hand is a protocol having associated type Element
protocol ObjectResponseType {
associatedtype Element
}
What I want to achieve is that I want to extend the functionality of ObjectRequestType depending on different type of elements, that here for the sake of example we have two different types of Element.
protocol ElementType {}
protocol OtherElementType {}
So far I would implement this by the following extensions
extension ObjectRequestType where Response.Element: ElementType {
static func request() {
print("ElementType")
}
}
extension ObjectRequestType where Response.Element: OtherElementType {
static func request() {
print("OtherElementType")
}
}
The extra step would be to handle to pass this request to a class which I need using generics
class DemoClass<Request: ObjectRequestType> {
static func execute() {
Request.request()
}
}
Problem
Since on the fly DemoClass cannot define what kind of Response the Request has it will find two implementations of it, and it will fail throwing the compilation error
error: ambiguous reference to member 'request()'
Modifying the class by adding an extra where clause still won't do it, because I will miss the rest of the implementation of OtherElementType
class DemoClass<Request: ObjectRequestType> where Request.Response.Element: ElementType {
static func execute() {
Request.request()
}
}
I have been trying workarounds about it, but still I haven't been able to implement this kind of case. If anybody has any idea or another approach, it would be happily welcomed.

The usual way to do this is to add the request method to the ObjectResponseType protocol so you can guarantee that it exists on any conforming type. Then you create a protocol extension providing the default implementation for the types you know how to handle, which you've already done. If you need to override the request for a certain request with one of the existing element types, you can do that. If you need to support another element type, you can either do it right in the request or add another protocol extension.
protocol ObjectResponseType {
associatedtype Element
}
protocol ObjectRequestType {
associatedtype Response: ObjectResponseType
static func request()
}
protocol ElementType {}
extension ObjectRequestType where Response.Element: ElementType {
static func request() {
print("ElementType")
}
}
protocol OtherElementType {}
extension ObjectRequestType where Response.Element: OtherElementType {
static func request() {
print("OtherElementType")
}
}
class DemoClass<Request: ObjectRequestType> {
static func execute() {
Request.request()
}
}
class Foo: ElementType {}
class FooResponse: ObjectResponseType {
typealias Element = Foo
}
class FooRequest: ObjectRequestType {
typealias Response = FooResponse
}
class Bar: OtherElementType {}
class BarResponse: ObjectResponseType {
typealias Element = Bar
}
class BarRequest: ObjectRequestType {
typealias Response = BarResponse
// Override the default implementation
static func request() {
print("Bar")
}
}
class Baz {}
class BazResponse: ObjectResponseType {
typealias Element = Baz
}
class BazRequest: ObjectRequestType {
typealias Response = BazResponse
static func request() {
print("Baz")
}
}
DemoClass<FooRequest>.execute() // ElementType
DemoClass<BarRequest>.execute() // Bar
DemoClass<BazRequest>.execute() // Baz

Related

swift generic constraint type is also a return type

protocol Builder {
associatedtype Output where Output: MyProtocol
func build() -> Output?
}
// concrete type
struct ABuilder: Builder {
func builder() -> MyProtocol {
if someCondition {
return aSubClassOfMyProtocol
} else {
return anotherSubClassOfMyProtocol
}
}
}
MyProtocol is a protocol type. It is also the Output constraint. Because the concrete Builder ABuilder is going to return two different sub class types that conform MyProtocol. How can I make the generic constraint work?
I am trying to make the generic constraint be the same.
You can make build() function generic or use typecasting. Anyway this two ways determine concrete type outside of method not inside.
So first of all you need to remove associatedtype. If you specify associated type Output it means that the implementation of your Builder protocol should return some concrete type which conforms to MyProtocol. If you want your Builder to return any type which conforms to MyProtocol not the concrete one specified for this implementation of Builder then you shouldn't declare any associated types.
Let's see how type casting can work:
protocol Builder {
func build() -> MyProtocol?
}
struct ABuilder: Builder {
func build() -> MyProtocol? {
// some code
}
}
let builder = ABuilder()
guard let a = builder.build() as? SomeClass else { return }
Or we can use generic approach:
protocol Builder {
func build<T: MyProtocol>() -> T?
}
struct ABuilder: Builder {
func build<T: MyProtocol>() -> T? {
// some code
}
}
let builder = ABuilder()
let a: SomeClass = builder.build()
You can read more about that here, here and here. Moreover I strongly recommend watching this video which describes all the limitations of generics in swift.
I think you are correct that having the typecasting outside the Builder is a better way. I also found another possibility. The difference is that we have the concrete class types from the build function.
protocol MyProtocol { }
protocol Builder {
associatedtype Output
func build() -> Output?
}
struct ASubClassOfMyProtocol: MyProtocol {}
struct AnotherSubClassOfMyProtocol: MyProtocol {}
struct ABuilder: Builder {
func build() -> (any MyProtocol)? {
if Int.random(in: 0...10) < 5 {
return ASubClassOfMyProtocol()
} else {
return AnotherSubClassOfMyProtocol()
}
}
}

how to make protocol to conform another protocol in Swift?

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

Swift: Error when creating a Wrapper Class for a Generic Class conforming Generic Protocol

I want to create a Wrapper class for a generic class that is conforming a generic protocol, but for some reason I cannot make it work properly.
The idea behind is to use the wrapper AnyNetworkRequest as an Erased-Type along the application, so that there is no need to define the Generic types as in _NetworkRequest.
I cannot see what is missing/wrong on AnyNetworkRequest. If any could point me out what is missing or wrong I'd appreciate it.
// Protocol with associatedtypes
public protocol NetworkRequest {
associatedtype RequestSerializationType: RequestSerializationProtocol
associatedtype RequestResponseType: NetworkResponseProtocol
var requestSerializer: RequestSerializationType { get }
var requestResponse: RequestResponseType? { get }
}
// Generic Request
public class _NetworkRequest<RequestSerializationType: RequestSerializationProtocol, RequestResponseType: NetworkResponseProtocol>: NetworkRequest {
fileprivate init() {}
public lazy var requestSerializer: RequestSerializationType = { RequestSerializationType.init() }()
public var requestResponse: RequestResponseType?
}
// Concrete Request
public class DataNetworkRequest: _NetworkRequest<ConcreteHTTPRequestSerializer, ConcreteDataNetworkResponse> {}
// Concrete Request
public class JSONDataNetworkRequest: _NetworkRequest<ConcreteJSONRequestSerializer, ConcreteJSONDataNetworkResponse> {}
// Type Erased Wrapper
// Cannot make this wrapper work
// Error 1: Type 'AnyNetworkRequest' does not conform to protocol 'NetworkRequest'
// Error 2: Reference to invalid associated type 'RequestSerializationType' of type 'AnyNetworkRequest'
public class AnyNetworkRequest : NetworkRequest { //E1
private let request : _NetworkRequest<RequestSerializationType, RequestResponseType> //E2
init<T: NetworkRequest>(_ networkRequest: T) where T.RequestSerializationType == RequestSerializationType, T.RequestResponseType == RequestResponseType {
request = networkRequest
}
}
EDITED: 1st MODIFICATION
// Protocol with associatedtypes
public protocol NetworkRequest {
associatedtype RequestSerializationType: RequestSerializationProtocol
associatedtype RequestResponseType: NetworkResponseProtocol
var requestSerializer: RequestSerializationType { get }
var requestResponse: RequestResponseType? { get }
}
// Generic Request
public class _NetworkRequest<RST: RequestSerializationProtocol, RRT: NetworkResponseProtocol>: NetworkRequest {
public typealias RequestSerializationType = RST
public typealias RequestResponseType = RRT
fileprivate init() {}
public lazy var requestSerializer: RequestSerializationType = { RequestSerializationType.init() }()
public var requestResponse: RequestResponseType?
}
// Concrete Request
public class DataNetworkRequest: _NetworkRequest<ConcreteHTTPRequestSerializer, ConcreteDataNetworkResponse> {}
// Concrete Request
public class JSONDataNetworkRequest: _NetworkRequest<ConcreteJSONRequestSerializer, ConcreteJSONDataNetworkResponse> {}
// Type Erased Wrapper
// Cannot make this wrapper work
// Error 1: Type 'AnyNetworkRequest' does not conform to protocol 'NetworkRequest'
// Error 2: Reference to invalid associated type 'RequestSerializationType' of type 'AnyNetworkRequest'
public class AnyNetworkRequest : NetworkRequest { //E1
/* // E1 forces me to include typealiases
public typealias RequestSerializationType = <#type#>
public typealias RequestResponseType = <#type#>
*/
private let request : _NetworkRequest<RequestSerializationType, RequestResponseType>
var requestSerializer: RequestSerializationType { //E2
return request.requestSerializer
}
var requestResponse: RequestResponseType? {
return request.requestResponse
}
init<T: NetworkRequest>(_ networkRequest: T) where T.RST == RequestSerializationType, T.RRT == RequestResponseType {
request = networkRequest
}
}
Errors are quite straightforward to explain.
1) Your AnyNetworkRequest class really does not conform to NetworkRequest protocol. And I don't see why it should, by the way. Unsatisfied requirements are requestSerializer and requestResponse properties along with needed type aliases. In general, you may rewrite this as following:
public class AnyNetworkRequest: NetworkRequest {
var requestSerializer: RequestSerializationType {
return request.requestSerializer
}
var requestResponse: RequestResponseType? {
return request.requestResponse
}
private let request : _NetworkRequest<RequestSerializationType, RequestResponseType>
// ...
}
BUT it takes us to
2) Where you need to specify something for associated types. You cannot use RequestSerializationType and RequestResponseType in generic declaration as they are not concrete types.
So you cannot perform type erasure in such manner.
I don't know why you need to get rid of generics here aside of typing less letters, but what I can offer is to use type aliases like:
typealias DataNetworkRequest = _NetworkRequest<ConcreteHTTPRequestSerializer, ConcreteDataNetworkResponse>
typealias JSONDataNetworkRequest = _NetworkRequest<ConcreteJSONRequestSerializer, ConcreteJSONDataNetworkResponse>
This way you will elude redundant inheritance and have more clearly expressed types in case it's your goal.
AnyNetworkRequest will not be needed at all in this case.
This is not actually how type erasers work - the role of type erasers is to allow homogenous interfaces in a heterogenous world, where many types can conform to the protocols interested into.
Take for example AnySequence, it's interface is similarly to this:
struct AnySequence<Element>: Sequence {
init<S>(_ sequence: S) where S == Sequence, S.Element == Element
}
AnySequence erases the original sequence type, not the Element type. In your case you cannot get rid of the two associated types, a type eraser can only hide the actual class that conforms to NetworkRequest. AnyRequest would still need information about the two types.

Generic protocol inside generic protocol in Swift 3

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>()

Swift protocol forcing the Equatable protocol

I have define 2 protocols.
I need the first one (NameProtocol) to enforce the Equatable protocol.
While the other class (BuilderProtocol) have a method that return the first one (NameProtocol).
public protocol NameProtocol : Equatable {
var name: String { get }
}
public protocol BuilderProtocol {
func build() -> NameProtocol? // Compiler error
init()
}
The compiler error :
"Protocol 'NameProtocol' can only be used as a generic constraint because it has Self or associated type requirements"
I need the object return by build() to return an object conforming to the NameProtocol and on which I can define ==
Is there a way I can make this work?
Thanks
If using a typealias in BuilderProtocol how can I make the array declaration work?
public protocol OtherRelatedProtocol {
var allNames : Array<NameProtocol> { get }
}
Conclusion
I will remove the Equatable and implement an isEqual method.
public protocol NameProtocol {
func isEqual(nameable: NameProtocol) -> Bool
var name: String { get }
}
If you're familiar with Java or C#, Swift protocols are about halfway between generics and interfaces. One thing that you can do in a protocol, for instance, is this:
protocol Foo {
func compareWith(foo: Self)
}
Classes that implement this protocol will have a method compareWith that accept an object of their own type (and not an object of type Foo).
This is what the compiler calls "Self or associated type requirements", and this is how Equatable is defined (it needs an operator== that accepts two Self operands). The downside of these protocols, though, is that you can only use them as generic constrains: you can't use them as an expression type.
The solution is to use generics. In this case, you'd make your ProtocolBuilder protocol generic, with a constraint that the type implements NameProtocol.
public protocol NameProtocol : Equatable {
var name: String { get }
}
public protocol BuilderProtocol {
typealias T: NameProtocol
func build() -> T?
init()
}