How can I use Type Erasure with a protocol using associated type
To your actual question, the type eraser is straight-forward:
final class AnyClient<T: EndpointType>: ClientType {
let _request: (T) -> Void
func request(_ request: T) { _request(request) }
init<Client: ClientType>(_ client: Client) where Client.T == T {
_request = client.request
}
}
You'll need one of these _func/func
pairs for each requirement in the protocol. You can use it this way:
let client = AnyClient(Client<ProfilesAPI>())
And then you can create a testing harness like:
class RecordingClient<T: EndpointType>: ClientType {
var requests: [T] = []
func request(_ request: T) -> Void {
requests.append(request)
print("recording: \(request.baseURL)")
}
}
And use that one instead:
let client = AnyClient(RecordingClient<ProfilesAPI>())
But I don't really recommend this approach if you can avoid it. Type erasers are a headache. Instead, I would look inside of Client
, and extract the non-generic part into a ClientEngine
protocol that doesn't require T
. Then make that swappable when you construct the Client
. Then you don't need type erasers, and you don't have to expose an extra protocol to the callers (just EndpointType).
For example, the engine part:
protocol ClientEngine: class {
func request(_ request: String) -> Void
}
class StandardClientEngine: ClientEngine {
func request(_ request: String) -> Void {
print(request)
}
}
The client that holds an engine. Notice how it uses a default parameter so that callers don't have to change anything.
class Client<T: EndpointType> {
let engine: ClientEngine
init(engine: ClientEngine = StandardClientEngine()) { self.engine = engine }
func request(_ request: T) -> Void {
engine.request(request.baseURL)
}
}
let client = Client<ProfilesAPI>()
And again, a recording version:
class RecordingClientEngine: ClientEngine {
var requests: [String] = []
func request(_ request: String) -> Void {
requests.append(request)
print("recording: \(request)")
}
}
let client = Client<ProfilesAPI>(engine: RecordingClientEngine())
How can you store an array of protocols with associated types in Swift without using Any?
PATs are usually complex and do not involve an easier solution. I would suggest you come up with some simpler design for your problem. The problem with your method is actually having PAT and nested protocols and trying to make them work together.
Even if you type erase Action
to some type like AnyAction
, these two different types DogAction
and CatAction
again produce different types and you cannot intermix them inside the array. What you can possibly do is have two type erasures, one for Action
and other for StateType
. When you wrap CatAction
or DogAction
inside AnyAction
both of them would then wrap in type erase state to AnyState
.
Here is one example you could approach this type erasing both the protocols,
protocol StateType { }
struct DogState: StateType { }
struct CatState: StateType { }
protocol Action {
associatedtype ST: StateType
func process() -> ST
}
struct DogAction: Action {
func process() -> DogState { return DogState() }
}
struct CatAction: Action {
func process() -> CatState { return CatState() }
}
struct AnyState: StateType {
let state: StateType
init(_ state: StateType) {
self.state = state
}
}
struct AnyAction: Action {
typealias ST = AnyState
let processor: () -> AnyState
init<T: Action>(_ a: T) {
self.processor = {
return AnyState(a.process())
}
}
func process() -> AnyState {
return processor()
}
}
let cat = AnyAction(CatAction())
let dog = AnyAction(DogAction())
let actions = [cat, dog]
actions.forEach { action in
action.process()
}
I still think that you should rethink your solution, this can get more complicated as your types increase.
Swift - Inherited Protocol Associated Type Erasure
Since Sub1
uses associated types, you cannot determine at runtime if a certain variable is of that type. Type erasers help to a certain degree, however it's hard to use multiple eraser types. My recommendation would be to overload the test
method for every type you need to handle. This also adds more type safety to you code.
func test<S: Sub1, T>(_ s: S) where S.T == T
func test(_ s: Sub2)
The above solution won't work however for the scenario where you have a collection of Super
elements, and you need to execute some actions based on the actual type. For this scenario a possible approach is to move the test
method at the protocol level, and override in the child protocols.
protocol Super {
func test()
}
protocol Sub1: Super { associatedtype T }
protocol Sub2: Super {}
extension Sub1 {
func test() { ... do stuff for Sub1 }
}
extension Sub2 {
func test() { ... do stuff for Sub2 }
}
The downside is that conformers can override test
, thus you'd loose the original implementation.
Swift Type Erasure attempt: Reference to invalid associated type
Your code doesn't compile because the associated type needs to be resolved at compile time by providing a concrete implementation for the Mover
protocol.
What you can do, is to also erase the MoverType
protocol:
struct AnyMover: MoverType {
private let mover: MoverType
init(_ mover: MoverType) {
self.mover = mover
}
func move() {
mover.move()
}
}
class AnyAnimal: Animal {
let mover: AnyMover
init<A: Animal>(animal: A) {
mover = AnyMover(animal.mover)
}
}
Use protocol with constrained associated type as property in Swift
To expand on my questions in the comments, looking at this code it looks like it would be exactly as flexible without adding AddressBookCellModelType
or AddressBookViewModelType
, and this would also get rid of the headaches, while still being generic over DataSourceCompatible
.
// This protocol is fine and very useful for making reusable view controllers. Love it.
protocol DataSourceCompatible {
associatedtype CellModel
func cellModelForItem(at indexPath: IndexPath) -> CellModel
}
// No need for a protocol here. The struct is its own interface.
// This ensures value semantics, which were being lost behind the protocol
// (since a protocol does not promise value semantics)
struct AddressBookCellModel {
var name: String
var photo: UIImage?
var isInvited: Bool
}
// AddressBookViewModel conforms to DataSourceCompatible
// Its conformance sets CellModel to AddressBookCellModel without needing an extra protocol
class AddressBookViewModel: DataSourceCompatible {
let sectionedContacts: [[AddressBookCellModel]] = []
func cellModelForItem(at indexPath: IndexPath) -> AddressBookCellModel {
return sectionedContacts[indexPath.section][indexPath.row]
}
}
class AddressBookViewController: UIViewController {
private var viewModel: AddressBookViewModel!
func configure(viewModel: AddressBookViewModel) {
self.viewModel = viewModel
}
}
Doing it this way allows for a generic VC without introducing more pieces that required:
class DataSourceViewController<DataSource: DataSourceCompatible>: UIView {
private var viewModel: DataSource.CellModel!
func configure(viewModel: DataSource.CellModel) {
self.viewModel = viewModel
}
}
let vc = DataSourceViewController<AddressBookViewModel>()
Use Type Erasure return Generic Type in a function with Swift (Cannot convert return expression of type…)
EDIT to respond to the edit to the question:
createTwo
doesn't work because you have the same misconception as I said in my original answer. createTwo
decided on its own that F
should be either String
or Int
, rather than "any type that conforms to Fooable
".
For createOne
, you have another common misconception. Generic classes are invariant. AnyFoo<String>
is not a kind of AnyFoo<Fooable>
. In fact, they are totally unrelated types! See here for more details.
Basically, what you are trying to do violates type safety, and you redesign your APIs and pick another different approach.
Original answer (for initial revision of question)
You seem to be having a common misconception of generics. Generic parameters are decided by the caller, not the callee.
In createOne
, you are returning anyFoo
, which is of type AnyFoo<Int>
, not AnyFoo<P>
. The method (callee) have decided, on its own, that P
should be Int
. This shouldn't happen, because the caller decides what generic parameters should be. If the callee is generic, it must be able to work with any type (within constraints). Anyway, P
can't be Int
here anyway, since P: FooProtocol
.
Your createOne
method should not be generic at all, as it only works with Int
:
func createOne() -> AnyFoo<Int> {
let anyFoo = AnyFoo(p: FooImpClass())
return anyFoo
}
Swift: Is it possible to type erase a return type for use in a collection
Hopefully the following approach fits your needs. I did it only for getter just to simplify the demo, but the idea should be clear.
Note: used Xcode 11.2 / Swift 5.1 / Catalina
So here is your original entities
protocol Setting {
associatedtype Value
var value: Value { get }
}
struct ProjectSetting<T>: Setting {
let value: T
}
Now we need some helper protocols to hide your type differences, aka type erasers
private protocol TypeErasing {
var value: Any { get }
}
private struct TypeEraser<V: Setting>: TypeErasing {
let orinal: V
var value: Any {
return self.orinal.value
}
}
Now the core entity that wraps your concrete implementors holding different type values, but still allows to use those values and be stored in standard containers
struct AnySetting : Setting {
typealias Value = Any
private let eraser: TypeErasing
init<V>(_ setting: V) where V:Setting {
eraser = TypeEraser(orinal: setting)
}
var value: Any {
return eraser.value
}
}
Now testing your expectation
let settings = [AnySetting(ProjectSetting(value: 1)), AnySetting(ProjectSetting(value: "abc"))]
if let value = settings[0].value as? Int {
print("Stored value: \(value)")
}
if let value = settings[1].value as? String {
print("Stored value: \(value)")
}
PlayGround output
Stored value: 1
Stored value: abc
Related Topics
Output Video Size Huge Using Hevc Encoder on iOS
How to Rotate a Scnsphere Using a Pan Gesture Recognizer
Simplest Way to Implement a "Read More" Button to Expand a Uitextview in iOS Swift 2
It Is Possible to Know If a String Is Encoded in Base64
Xcode 6 Swift Wkwebview Keyboard Settings
Blur Effect - Background Uitextfield
Set the Center of a Uibutton Programmatically - Swift
Wkwebview Blank After 'Successful' Https Nsurlrequest
How to Trigger an API Call Before Displaying Local Notifications
What's Happening Behind the Scenes in Xctest's @Testable
How to Check If a Nstimer Is Running or Not in Swift
How to Handle Homophones in Speech Recognition
iOS Swift Nsmutabledata Has No Member Appendstring
Issue with Observing Wkwebview Url Changes via JavaScript Events
Get Latitude and Longitude Center of Google Map
How to Handle Oauth-Style Log Ins with Imessage Apps in iOS 10, Xcode 8