Swift Protocol Extension Implementing Another Protocol with Shared Associated Type

Swift: override associatedtype from protocol extension in protocol conformance

I have created the same code. There are some facts that need to be understood here.

extension MyProtocol {
typealias ResponseType = MyDefaultDecodableType

func handleResponse(data: Data) -> ResponseType {

return try! JSONDecoder().decode(MyDefaultDecodableType.self, from: data)

}
}

Conceptually, there are no generic protocols in Swift. But by using typealias we can declare a required alias for another type.

Your extension doesn't need to define typealias ResponseType = MyDefaultDecodableType as it is going to provide some default implementation using MyDefaultDecodableType, so it is useless.

So your extension would be something like this

extension MyProtocol {
// typealias ResponseType = MyDefaultDecodableType // NO NEED FOR IT

func handleResponse(data: Data) -> MyDefaultDecodableType {
print("Test \(self)")
return try! JSONDecoder().decode(MyDefaultDecodableType.self, from: data)

}
}

Now you can define

class MyObject:MyProtocol {
typealias ResponseType = AnotherDecodableType

func handleResponse(data: Data) -> ResponseType {
print("Test \(self)")

return try! JSONDecoder().decode(AnotherDecodableType.self, from: data)

}

}
class MyObject2:MyProtocol {


}

Without any errors

Now if you use

MyObject().handleResponse(data:data)
MyObject2().handleResponse(data:data2)

You will get

test __lldb_expr_44.MyObject

test __lldb_expr_44.MyObject2

Reuse associated type from protocol in another protocol

The requestType associated type isn't necessary in the RequestProcessor protocol, as it is implicit based on the requester associated type.

You should just be able to define the RequestProcessor protocol like this:

protocol RequestProcesser: AnyObject {
associatedtype requester: Requester
var request: requester { get set }
}

And use it like this:

class MyRequester: Requester {
typealias requestType = Int
var response: ((Int) -> ())?
}

class MyRequestProcessor: RequestProcesser {
typealias requester = MyRequester
var request = MyRequester()
}

Now, the response closure parameter inside the process() method will take on the associated type of the MyRequester protocol (in this case Int), so no casting is necessary.

implement protocol with different associated type

I just fund a way to archive this. The trick is to add another associated type in one of the subtypes of the protocol:

protocol ConvertableInt : Convertable {
associatedtype TResI
typealias TargetType = TResI
}

extension MyData : Convertable {
typealias TargetType = String
func convert() -> String { return String(self.data) }
}

extension MyData : ConvertableInt {
typealias TResI = Int
func convert() -> TResI { return self.data }
}

This also allows to get rid of the second subtype for string.

While this passes the compiler it totally crashes at runtime!

The compiler always calls the method defined which was defined at the explicit typealias. In this case:

typealias TargetType = String

Which will result in interpreting the address as a integer and give you totally wrong results. If you define it vice versa it will simply crash because it tries to interpret the integer as a address.

Protocol restriction on another protocol associated type

As far as I can see MyStruct? (aka Optional) and Codable? (aka
Optional<Decodable & Encodable>) are equivalent?

No, they are not. MyStruct conforms to Codable but is not equivalent of it.

As in: every MyStruct is Codable but not every Codable is MyStruct.


You can try changing MyType == Codable? to MyType: Codable:

protocol MyProtocolCodable where Self: MyProtocol, MyType: Codable {}

as MyStruct is not equal to Codable?.

Swift add constraint extension to Protocol that has an associated type

What happens here is that Swift will gladly satisfy protocol requirements, if it can extract them from the context.

For example, assuming the extension would not exist, and the Doo definition would be like this:

struct Doo: Arr {
func node(_ at: Int) -> String? {
nil
}
}

, the the Swift compiler will happily fill the Element associated type.

A similar thing happens with your first snippet of code, Doo conforms to Arr, and the compiler finds a definition that satisfies all protocol requirements. Swift doesn't ignore the Element == String constraint, because it associates it with the Doo struct.

If you would add a second extension in a similar fashion, but for another type (an Int for example), you'll see that you receive the expected error. This happens because the compiler can no longer infer the protocol requirements.

The Swift compiler eagerly infers as much as possible from the context it can reach to, most of the time gives great results, sometimes not so great (especially when working with closures), and sometimes it gives unexpected results (like this one).

If you want to be sure that the compiler infers the types you want, a solution would be to explicitly declare all involved types.

Protocol extension to be applied to several types

You can unite the types with another protocol (for example HasDefaultFoo). This allows the types to decide when adopting the protocol if they want the default implementation.

protocol P {
func foo()
}

protocol HasDefaultFoo {
func foo()
}

extension Int: P, HasDefaultFoo { }
extension Float: P, HasDefaultFoo { }

extension P where Self: HasDefaultFoo {
func foo() {
print(self)
}
}

extension Double: P {
func foo() {
print("I provide my own foo()")
}
}

Example:

5.foo()
(5.5 as Float).foo()
5.5.foo()

Output:

5
5.5
I provide my own foo()

Unable to use protocol as associatedtype in another protocol in Swift

The problem, which David has already alluded to, is that once you constrain a protocol's associatedtype to a specific (non @objc) protocol, you have to use a concrete type to satisfy that requirement.

This is because protocols don't conform to themselves – therefore meaning that you cannot use Address to satisfy the protocol's associated type requirement of a type that conforms to Validator, as Address is not a type that conforms to Validator.

As I demonstrate in my answer here, consider the counter-example of:

protocol Validator {
init()
}
protocol Address : Validator {}

protocol FormRepresentable {
associatedtype ValueWrapper: Validator
}

extension FormRepresentable {
static func foo() {
// if ValueWrapper were allowed to be an Address or Validator,
// what instance should we be constructing here?
// we cannot create an instance of a protocol.
print(ValueWrapper.init())
}
}

// therefore, we cannot say:
enum AddressFrom : FormRepresentable {
typealias ValueWrapper = Address
}

The simplest solution would be to ditch the Validator protocol constraint on your ValueWrapper associated type, allowing you to use an abstract type in the method argument.

protocol FormRepresentable {
associatedtype ValueWrapper
func valueForDetail(valueWrapper: ValueWrapper) -> String
}

enum AddressFrom : Int, FormRepresentable {

// ...

func valueForDetail(valueWrapper: Address) -> String {
// ...
}
}

If you need the associated type constraint, and each AddressFrom instance only expects a single concrete implementation of Address as an input – you could use generics in order for your AddressFrom to be initialised with a given concrete type of address to be used in your method.

protocol FormRepresentable {
associatedtype ValueWrapper : Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}

enum AddressFrom<T : Address> : Int, FormRepresentable {

// ...

func valueForDetail(valueWrapper: T) -> String {
// ...
}
}

// replace ShippingAddress with whatever concrete type you want AddressFrom to use
let addressFrom = AddressFrom<ShippingAddress>.Address1

However, if you require both the associated type constraint and each AddressFrom instance must be able to handle an input of any type of Address – you'll have use a type erasure in order to wrap an arbitrary Address in a concrete type.

protocol FormRepresentable {
associatedtype ValueWrapper : Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}

struct AnyAddress : Address {

private var _base: Address

var addressLine1: String {
get {return _base.addressLine1}
set {_base.addressLine1 = newValue}
}
var country: String {
get {return _base.country}
set {_base.country = newValue}
}
var city: String {
get {return _base.city}
set {_base.city = newValue}
}

init(_ base: Address) {
_base = base
}
}

enum AddressFrom : Int, FormRepresentable {

// ...

func valueForDetail(valueWrapper: AnyAddress) -> String {
// ...
}
}

let addressFrom = AddressFrom.Address1

let address = ShippingAddress(addressLine1: "", city: "", country: "")

addressFrom.valueForDetail(AnyAddress(address))


Related Topics



Leave a reply



Submit