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
"Ambiguous Use" on Generic Method After Migration to Swift 4
How to Byte Reverse Nsdata Output in Swift The Littleendian Way
How to Check If a Variable Is Nil
Convert/Wrap Swift Struct as Nsvalue for Caanimation Purposes
How to Restore In-App Purchases Correctly
How to Mutate an Array in a Dictionary
How to Specify Japanese Encoding for a UIlabel
Arkit - Getting "Unexpectedly Found Nil" When Using Scn File > 300 Mb
Firebase Connection State Always Returning False on First Run
How to Override Setter in Swift
Nsjsonserialization Error. Code=3840 "Invalid Value Around Character 0
Nil Cannot Be Assigned to Type Avcapturedeviceinput
Why Is The Leading Swipe Action Also Duplicated as a Trailing Action
What's The Difference Between [String] VS. [(String)]