Why 'there cannot be more than one conformance, even with different conditional bounds'?
Is it impossible? Yes and no. It's not currently possible in Swift, as it has been implemented. It is in principle possible to be implemented.
The name for this is "overlapping conformances", and it was explicitly and purposely rejected. You can find the rationale in the "Alternatives considered" section of SE-0143 Conditional conformances. The TL;DR is: because it's really complicated.
Without knowing more about what exactly you were trying to use this for, there's not much direction we can provide.
How can I get conditional protocol conformance with a protocol's parent?
The question as posed, "How can I get conditional protocol conformance with a protocol's parent?" is meaningless, because a protocol always conforms with its parent; there is no "conditional" about it.
As for your actual code, the problem is the phrase A<Resource>
. Saying A<Resource>
is not a correct resolution of A's T. You need to resolve T as a class, struct, or enum — not as a protocol.
For example, if you have a class B that conforms to Resource, you can declare
let a = A<B>()
and all is well.
if there was a way to let
A<Resource>
know that it will always be working with concrete types that conform toCodable
Well, as I said, your code compiles fine as soon as A is working with a concrete type that does conform to Codable
. So if that's what A will always be working with, there's nothing more to do. You could of course tell A that its T will always conform to Resource (which by definition will always conform to Codable):
class A<T:Resource> {}
Swift Generic Classes and Extensions with Conditional Generics
Consider the case when T
is BaseClass
, or when T
is AnotherSubclass
that I defined as
class AnotherSubclass : BaseClass {
}
What would happen? You haven't declared a conformance to Configure
when T
is AnotherSubclass
!
There's really only two (not bad) choices here.
- You want
configure
to do nothing whenT
is neitherSubTypeAOfBaseClass
norSubTypeBOfBaseClass
- you only want
MyClass<SubTypeAOfBaseClass>
andMyClass<SubTypeBOfBaseClass>
to be valid types -MyClass<BaseClass>
andMyClass<AnotherSubclass>
would give compiler errors.
Choice 2 is not possible in Swift. That would require something similar to the sealed types in Java or Kotlin.
Choice 1 can be done like this:
class BaseClass {
...
func configure() {
}
}
class SubTypeAOfBaseClass: BaseClass {
...
override func configure() {
print("Configuring SubTypeAOfBaseClass")
doSomethingA()
}
}
class SubTypeBOfBaseClass: BaseClass {
...
override func configure() {
print("Configuring SubTypeAOfBaseClass")
doSomethingB()
}
}
class MyClass<T: BaseClass> {
let aThing = T()
func someMethod() {
aThing.configure()
}
}
You might notice that the each implementation of configure
has been moved to the base classes. If you want to implement them all in MyClass
, you must check the type by hand:
class MyClass<T: BaseClass> {
let aThing = T()
func someMethod() {
if let selfA = self as? MyClass<SubTypeAOfBaseClass> {
selfA.configure()
} else if let selfB = self as? MyClass<SubTypeBOfBaseClass> {
selfB.configure()
}
}
}
extension MyClass where T == SubTypeAOfBaseClass {
func configure() {
print("Configuring SubTypeAOfBaseClass")
aThing.doSomethingA()
}
}
extension MyClass where T == SubTypeBOfBaseClass {
func configure() {
print("Configuring SubTypeBOfBaseClass")
aThing.doSomethingB()
}
}
This is because of the second problem in your code - different parameterisations of a generic type, MyClass<SubTypeAOfBaseClass>
and MyClass<SubTypeBOfBaseClass>
, can't conform to a protocol differently. This is a limitation of Swift, unfortunately. See here for more info.
Unable to cast Array Codable to Codable
In accordance with the laws of conditional conformance that went into effect in Swift 4.2:
An array of some type (class, struct, or enum) that conforms to Decodable is decodable.
An array of the protocol Decodable is not, because a protocol does not conform to itself.
(What was happening before Swift 4.2 was that conditional conformance didn't exist and we were just getting a kind of universal pass; you could treat any array as decodable and you wouldn't hit a problem until runtime if you were wrong. Now, with conditional conformance, the compiler actually looks at the element type.)
Conditional Protocol Conformance: Cannot convert value of type 'Array _ ' to specified type '[UInt8]'
In order for Array<UInt8>
to be BinaryCodable
, its element type UInt8
must be BinaryCodable
, which it isn't. That protocol has
default implementations of its required methods, but conformance must
still be declared explicitly:
extension UInt8: BinaryCodable {}
Then your extension String
compiles,
and you can even get rid of the forced cast as! BinaryCodable
in the
encoding method (and using guard
allows to save one line):
extension String: BinaryCodable {
public func binaryEncode(to encoder: BinaryEncoder) throws {
try Array(self.utf8).binaryEncode(to: encoder)
}
public init(fromBinary decoder: BinaryDecoder) throws {
let utf8: [UInt8] = try Array(fromBinary: decoder)
guard let str = String(bytes: utf8, encoding: .utf8) else {
throw BinaryDecoder.Error.invalidUTF8(utf8)
}
self = str
}
}
Extentions for more than one protocol at once
In general, it is not possible to write a single extension on multiple protocols. This would require the compiler to figure out the set intersection of all the members in all those protocols (those are the members you have access to in the extensions), which the compiler cannot do.
To avoid code duplication, you would need to work out the members that you need - in your case init(integerLiteral:)
and <=
, put those in your own protocol, and make the concrete types you want the extension to apply to, conform to your own protocol:
// inheriting from Comparable and ExpressibleByIntegerLiteral gives you the members you need
protocol BinaryNumbers : Comparable & ExpressibleByIntegerLiteral {
}
extension BinaryNumbers {
func atMost127() -> Bool {
self <= 127
}
}
extension Int: BinaryNumbers {}
extension Int8: BinaryNumbers {}
extension Int16: BinaryNumbers {}
extension Int32: BinaryNumbers {}
extension Int64: BinaryNumbers {}
// ... plus the unsigned versions, if you need them
extension Float16: BinaryNumbers {}
extension Float32: BinaryNumbers {}
extension Float64: BinaryNumbers {}
extension Float80: BinaryNumbers {}
Now you might ask, why don't we just make an extension of Comparable & ExpressibleByIntegerLiteral
then, as they provide all the members we are using? Well, because that's not a nominal type, and you can't write extensions on non-nominal types :(
However, you could write it in this way:
// now you don't need your own "BinaryNumbers"!
extension Comparable where Self: ExpressibleByIntegerLiteral {
func atMost127() -> Bool {
self <= 127
}
}
You are only able to do this because the two members you need all came from protocols. If for some reason you need a member that both BinaryFloatingPoint
and BinaryInteger
has, but isn't provided by any of the protocols they conform to, then you need to write your own protocol with those members, and manually conform everything to that protocol.
Related Topics
Does Swift Have an Implicit Object Initializer, Like in C#
How to Open Another Window in MACos in Swift with Cocoa
Macos/Swift Capture Audio with Avcapturesession
Create Skscene Subclasses Programmatically, Without Size Info
Treat *Some* Warnings as Errors in Swift
How to Detect Switch Between MACos Default & Dark Mode Using Swift 3
!? Strange Double Unwrapped Optional Syntax in For_In []
How to Apply Borders and Corner Radius to Uibarbuttonitem
My Data Changes in Uitableviewcell When I Scroll Down and Get Back
How to Continue Ble Activities Onto Next View Controller
When Should I Use Optionals and When Should I Use Non-Optionals with Default Values
Type Ccc Doesnt Conform to Protocol 'Nsobjectprotocol'
How to Create a Static Class in Swift
Sending a Parameter Argument to Function Through Uitapgesturerecognizer Selector
How to Determine If a Variable Passed in Is Reference Type or Value Type
Where Is the .Camera Anchorentity Located