returning swift protocol associated type in multiple methods
The solution you came up with by playing around is exactly what you need
As mentioned elsewhere, the main issue with your first protocol is that you're enforcing createSomeView()
createAnotherView()
both return the same type. While ViewA()
and ViewB()
are both candidates for V
, since they conform to View
they are still different types, and therefore cannot BOTH be V
in a given object.
By defining both V1
and V2
, you allow for each function to return a different type, or the same type, it's all acceptable. By making both V1
and V2
require View
conformance, you allow for the some View
syntax
Swift Protocol referencing itself and associated type
You would look after something like this:
protocol Handler {
// ...
var next: some Handler<HandlerRequest == Self.HandlerRequest>? { get }
// ...
}
The problem here is that Swift doesn't (yet) have support for opaque return types that are protocols with associated types.
A solution to this limitation is to use a type eraser for the next
property:
protocol Handler {
associatedtype HandlerRequest
// shift the generic from a protocol with associated type to a generic struct
var next: AnyHandler<HandlerRequest>? { get }
func handle(_ request: HandlerRequest) -> LocalizedError?
}
struct AnyHandler<HandlerRequest> {
private var _handle: (HandlerRequest) -> LocalizedError?
private var _next: () -> AnyHandler<HandlerRequest>?
init<H: Handler>(_ handler: H) where H.HandlerRequest == HandlerRequest {
_next = { handler.next }
_handle = handler.handle
}
}
extension AnyHandler: Handler {
var next: AnyHandler<HandlerRequest>? { return _next() }
func handle(_ request: HandlerRequest) -> LocalizedError? {
return _handle(request)
}
}
This way you can benefit both the protocol, and having the next
property tied to the handler request type that you need.
As an added bonus for using protocols, you can still benefit the default implementation from the base class:
extension Handler {
func handle(_ request: HandlerRequest) -> LocalizedError? {
return next?.handle(request)
}
}
That's how cool protocols are in Swift, they allow you to avoid classes and use value types for as much as possible, by improving the concept of polymorphism.
Usage example:
struct LoginHandler: Handler {
var next: AnyHandler<AccountRequest>?
func handle(_ request: AccountRequest) -> LocalizedError? {
// do the login validation
}
}
struct SignupHandler: Handler {
var next: AnyHandler<AccountRequest>?
func handle(_ request: AccountRequest) -> LocalizedError? {
// do the signup validation
}
}
extension Handler {
// Helper function to easily create a type erased AnyHandler instance
func erase() -> AnyHandler<HandlerRequest> {
return AnyHandler(self)
}
}
// now let's put the handers to work:
let loginHandler = LoginHandler()
let signupHandler = SignupHandler(next: loginHandler.erase())
let someOtherAccountHandler = SomeOtherAccountHandler(next: signupHandler.erase())
return protocol with associated type
In this function
func sendB_<T: BProtocol>() -> T{
return B() as! T
}
you cannot return a B
as a T
because the person who uses the function defines what T
is, not you and T
can be any type that conforms to Protocol
For example, I could do this:
class C: BProtocol
{
typealias B = Float
}
let c: C = Main().sendB_()
By doing that, I am setting T
to be a C
and the forced typecast in sendB_()
will fail.
Unfortunately, protocols with associated types cannot, themselves, be treated like a concrete type, so the approach you took with AProtocol
will not work.
As I see it, you have two options. Change your function return type to B
. After all, you always do return a B
func sendB_() -> B {
return B()
}
If you want to keep it generic, try
protocol BProtocol
{
associatedtype B
init() // Needed to be able to do T() in generic function
}
func sendB_<T: BProtocol>() -> T{
return T()
}
You need to add the initialiser to the protocol to make sure that an instance of type T
always exists.
Swift: Set protocol's associated type in argument / member variable
ConsumerA
needs to be generic. Replace ModuleName
with your actual module name.
class ConsumerA<KeyValueStore: ModuleName.KeyValueStore>
where KeyValueStore.Key == String, KeyValueStore.Value == Int {
Also, your method pair should be a subscript instead.
subscript(key: Key) -> Value { get set }
keyValueStore["Hello"] = 5
Protocol associatedType and
Defined associated type makes classes which conform protocol strong typed. This provide compile-time error handling.
In other hand, generic type makes classes which conform protocol more flexible.
For example:
protocol AssociatedRepository {
associatedtype T
func add(data : T) -> Bool
}
protocol GenericRepository {
func add<T>(data : T) -> Bool
}
class A: GenericRepository {
func add<T>(data : T) -> Bool {
return true
}
}
class B: AssociatedRepository {
typealias T = UIViewController
func add(data : T) -> Bool {
return true
}
}
class A
could put any class into add(data:)
function, so you need to makes your sure that function handle all cases.
A().add(data: UIView())
A().add(data: UIViewController())
both would be valid
But for class B
you will get compile-time error when you will try to put anything except UIViewController
B().add(data: UIView()) // compile-time error here
B().add(data: UIViewController())
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))
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.
Related Topics
iOS Charts Remove Decimal from Yvalues
A Swiftier Way to Convert String to Unsafepointer<Xmlchar> in Swift 3 (Libxml2)
Does Untimeintervalnotificationtrigger Nexttriggerdate() Give the Wrong Date
How to Override Private Method and Call Super in Swift
How to Add Multiple Values for One Key in a Dictionary Using Swift
Nswindow with Round Corners in Swift
Weak Method Argument Semantics
Nsposixerrordomain When Binding to Socket on MACos 10.12
Using @Fetchrequest(Entity: ) for Swiftui MACos App Crashes
Ibdesignable and Uitableviewcell
Make a Type Itself -- Not Its Instances -- Conform to a Protocol
Swift Codable - Parse JSON Array Which Can Contain Different Data Type
Symbol Is Considered to Be an Identifier, Not an Operator
From Any Utf-16 Offset, Find the Corresponding String.Index That Lies on a Character Boundary
Swiftui Widget iOS 14 Gradient Issue
Rename Default Rendered Headings Like "Example" in Swift Markup Language