Swift Function Input Generic Conform To Both Class And Protocol - swift

I'm trying to have a function take a generic input that conforms to both a class and a protocol in order to process values where some belong to the class and others belong to the delegate. It works fine for individual types, but not for an array of types (which conform to both the super class and the delegate). Instead the compiler throws the error "Instance method 'printValues(for:)' requires that 'MeasurementClass' conform to 'UnitDelegate'"
I've added the code below, which will make much more sense. Just put it in a Swift Playground. If you run it as is, you can see that it properly prints the users distance and steps individually. If you uncomment the last few lines, the same function cannot be used with an array.
My goal is to be able to pass an array of types that conform to the UnitDelegate and MeasurementClass (aka MeasurementObject typealias) and have the values processed. I want an array, because I will need up having a bunch of different classes that conform to MeasurementObject.
protocol UnitDelegate: class{
var units: [String: String] { get }
}
class MeasurementClass{
var imperial: Double!
var metric: Double!
convenience init(imperial: Double, metric: Double){
self.init()
self.imperial = imperial
self.metric = metric
}
}
typealias MeasurementObject = MeasurementClass & UnitDelegate
class Distance: MeasurementObject{
var units = ["imperial": "miles", "metric":"kms"]
}
class Steps: MeasurementObject{
var units = ["imperial": "steps", "metric":"steps"]
}
class User{
func printValues<T: MeasurementObject>(for type: T) {
print("\(type.imperial!) \(type.units["imperial"]!) = \(type.metric!) \(type.units["metric"]!)")
}
}
//This is what I'm trying to achieve in the for loop below
let distance = Distance(imperial: 30, metric: 48.28)
let steps = Steps(imperial: 30, metric: 30)
let user = User()
user.printValues(for: distance)
user.printValues(for: steps)
//let types = [distance, steps]
//
//for type in types{
// user.printValues(for: type)
//}

Not a direct answer to your question but all you need is to add an enumeration to store the kind of measurement to your class, and a computed property to return the corresponding dictionary. No need to use a protocol, protocol composition, class and/or subclass to accomplish what you are trying to do:
struct AMeasurement {
let imperial: Double
let metric: Double
let kind: Kind
enum Kind { case distance, steps }
var units: [String: String] {
switch kind {
case .distance: return ["imperial": "miles", "metric":"kms"]
case .steps: return ["imperial": "steps", "metric":"steps"]
}
}
}
extension AMeasurement {
func printValues() {
print("\(imperial) \(units["imperial"]!) = \(metric) \(units["metric"]!)")
}
}
let distance = AMeasurement(imperial: 30, metric: 48.28, kind: .distance)
let steps = AMeasurement(imperial: 30, metric: 30, kind: .steps)
let types = [distance, steps]
for type in types {
type.printValues()
}
30.0 miles = 48.28 kms
30.0 steps = 30.0 steps

Related

Swift: Is it possible to type erase a return type for use in a collection

So here's something that's been bugging me for a while and I've been trying to find a good pattern to use. The problem occurs when I need to create a list of items that conform to a protocol, that has an associated type. For example:
protocol Setting {
associatedtype Value
var value: Value
}
struct ProjectSetting<T>: Setting {
var value: T
}
let setting1 = ProjectSetting(value: 1)
let setting1 = ProjectSetting(value: "abc")
The problem occurs when then trying to store ProjectSetting instances in an array.
let settings: /* ??? */ = [ProjectSetting(value: 1), ProjectSetting(value: "abc")]
Swift won't let me do let settings: [Setting] = ... because of the associated type, and it won't let me do let settings: [ProjectSetting<Any>] = ... either.
So I need to do some sort of type erasure to hide the type of the setting, but everything I've tried ends up needing the generic type exposed. I've tried to type erase by wrapping closures but I end up either exposing an Any or the generic type again.
Does anyone have a technique for wrapping a generic protocol so that it can be stored in an array regardless of the type being used?
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

Nested enums which conform to Equatable protocol produce error

I have a protocol named TableViewItem. This protocol enforces that conforming objects implement a type property, which has the protocol TableViewCellIdentifiable as its type. TableViewCellIdentifiable is used to group three nested enums together, as shown below:
internal protocol TableViewCellIdentifiable: Equatable { }
internal enum TableViewCellType {
internal enum PortfolioSelection: String, TableViewCellIdentifiable {
case portfolio = "portfolioTableViewCell"
case enterPortfolioDetails = "enterPortfolioDetailsTableViewCell"
case addPortfolio = "actionTableViewCell"
}
internal enum EditPortfolio: String, TableViewCellIdentifiable {
case editPortfolioName = "editPortfolioNameTableViewCell"
case deletePortfolio = "deletePortfolioTableViewCell"
}
internal enum Portfolio: String, TableViewCellIdentifiable {
case portfolioAsset = "portfolioAssetTableViewCell"
case addAsset = "actionTableViewCell"
}
}
Here is an example of how this is being used:
internal final class EditPortfolioNameTableViewItem: TableViewItem {
// MARK: - Internal Properties
internal let type: TableViewCellIdentifiable = TableViewCellType.EditPortfolio.editPortfolioName
internal let viewModel: TableViewCellModel
// MARK: - Initialization
internal init(viewModel: EditPortfolioNameTableViewCellModel) {
self.viewModel = viewModel
}
}
Unfortunately, on the line that I am declaring the type property, I receive the following error:
Protocol 'TableViewCellIdentifiable' can only be used as a generic constraint because it has Self or associated type requirements
I have read through other questions/answers from others who have encountered this error but I can't quite understand why this particular implementation is problematic, and what the solution would be. I know that Equatable is the source of the problem, however this is crucial to the functionality, as the enums serve two purposes:
To provide reuse identifiers for the table view cells (the raw values).
To allow types to be compared - i.e:
self.tableViewItems.contains(where: { $0.type == item.type })
Any suggestions would be much appreciated, even if it means taking an alternative approach.
In your head, should the following code compile?
var x : Equatable
It shouldn't. Why?
Because if you had:
var x : Equatable
var y : Equatable
Then the compiler can't ensure that x & y are of the same type. x can be "John", because "John"/Strings are Equatable...all while y can be 10, because 10/integers are equatable.
and the compiler would suspect that a few lines below you might want to do
if x == y { print ("equal" }
which it can't process. So it just stops you from ever doing it in the beginning.
The following line of your code will trigger the same error because of the reason above.
internal let type: TableViewCellIdentifiable = TableViewCellType.EditPortfolio.editPortfolioName
As explained by Honey's answer, TableViewCellIdentifiable doesn't provide enough type information for the compiler to work with. You could potentially adopt a different approach which changes the structure a bit (and is potentially overkill), but provides the functionality you're looking for:
internal protocol ValueAssociated { }
internal extension ValueAssociated {
fileprivate var association: (label: String, value: Any?)? {
get {
let mirror = Mirror(reflecting: self)
if let association = mirror.children.first, let label = association.label {
return (label, association.value)
}
return nil
}
}
}
internal protocol CellIdentifiable {
var rawValue: String { get }
}
internal enum CellType: Equatable, ValueAssociated {
case portfolio(PortfolioIdentifier)
case portfolioSelection(PortfolioSelectionIdentifier)
case editPortfolio(EditPortfolioIdentifier)
internal var identifier: String? {
return (self.association?.value as? CellIdentifiable)?.rawValue
}
internal enum PortfolioIdentifier: String, Equatable, CellIdentifiable {
case portfolioAsset = "portfolioAssetTableViewCell"
case addAsset = "actionTableViewCell"
}
internal enum PortfolioSelectionIdentifier: String, Equatable, CellIdentifiable {
case portfolio = "portfolioTableViewCell"
case enterPortfolioDetails = "enterPortfolioDetailsTableViewCell"
case addPortfolio = "actionTableViewCell"
}
internal enum EditPortfolioIdentifier: String, Equatable, CellIdentifiable {
case editPortfolioName = "editPortfolioNameTableViewCell"
case deletePortfolio = "deletePortfolioTableViewCell"
}
}
This can be used as follows:
internal let cellType: CellType = .portfolio(.portfolioAsset)
print(cellType.identifier!) // Prints "portfolioAssetTableViewCell"
Hope this helps.

"Generic parameter 'T' could not be inferred" error in Swift

I am trying to practice "class with generic". I encountered 2 errors:
Generic parameter 'T' could not be inferred
Reference to generic type 'GenericObject' requires arguments in <...>
The 2 errors in GenericManager class. Please reference the following code. How do I solve this issue?
class User {
var name: String
init(name: String) {
self.name = name
}
}
class Employee {
var name: String
var position: String
init(name: String, position: String) {
self.name = name
self.position = position
}
}
class GenericObject<T> {
var items = [T]()
init(forType: T.Type) {}
func addObject(_ obj: T) {
self.items.append(obj)
}
}
class GenericManager {
//issue: Generic parameter 'T' could not be inferred
var objects = [GenericObject]()
//issue: Reference to generic type 'GenericObject' requires arguments in <...>
func addObject(_ obj: GenericObject) {
self.objects.append(obj)
}
}
let u = User(name: "User")
let uo = GenericObject(forType: User.self)
uo.addObject(u)
let e = Employee(name: "Employee", position: "session manager")
let eo = GenericObject(forType: Employee.self)
eo.addObject(e)
let manager = GenericManager()
manager.addObject(uo)
manager.addObject(eo)
The compiler needs to know the type of T, and in this case you haven't supplied it.
You can do it like this:
var objects = [GenericObject<YourTypeHere>]()
For example, if GenericObject will hold an array of Int, it would look like this:
var objects = [GenericObject<Int>]()
I noticed you updated your question. It would be helpful to know what you're trying to achieve, but I'll try to help you anyway.
When you have a generic object, you need to tell the compiler the type of the generic at compile time, that's why it's complaining that the type can't be inferred, it needs to know.
Since you want to be able to add objects to the GenericManager array, you need the generic in those two cases to be the same, so you can modify your class like this:
class GenericManager<T> {
var objects = [GenericObject<T>]()
func addObject(_ obj: GenericObject<T>) {
self.objects.append(obj)
}
}
However, since the objects have to be of the same generic, you can't add a GenericObject<User> and GenericObject<Employee> to the same manager, what you can do is to implement those as GenericObject<Any>, and do the same with the GenericManager, then it will look like this:
let u = User(name: "User")
let uo = GenericObject(forType: Any.self)
uo.addObject(u)
let e = Employee(name: "Employee", position: "session manager")
let eo = GenericObject(forType: Any.self)
eo.addObject(e)
let manager = GenericManager<Any>()
manager.addObject(uo)
manager.addObject(eo)
Keep in mind that this will lose you any advantage that generics would do, what you could do is to create a protocol or common superclass and use that instead of Any, but that depends on what you're trying to achieve.
If you have any further questions, please add a comment instead of silently updating your question.
The problem you are having is that you are trying to use generics, but want to ignore that in GenericManager and store references to objects of different types.
Consider this - when you call manager.objects[0] what would you expect to be returned?
You can solve this by type-erasure using Any as EmilioPelaez suggested. However this is often a codesmell which leads to casting hacks throughout your code.
One alternative would be to use an enum to specify the different types of data you want to represent:
enum GenericObject {
case users([User])
case employees([Employee])
}
...
let uo = GenericObject.users([ u ])
...
let eo = GenericObject.employees([ e ])
Now when you access the properties inside GenericManager you would be required to switch over the different supported types, and when you add a new type you would be required to implement code whenever you use a GenericObject

swift reflections: how to construct a new struct instance based on reflections?

My example consists of:
protocol with read-only properties
struct implementing that protocol
"+" operator function
I'd like the "+" operator to be able to work for all implementations of the protocol by creating a new instance of the specific type that implements that protocol.
As you can see in the source code below, the operator accepts parameters of type "Aable" (the protocol). Using the generic placeholder T to construct a new instance fails with the error "'Aable' cannot be constructed because it has no accessible initializers".
Is that even possible?
Could it be achieved using reflections or some sort of introspection?
protocol Aable {
var name: String { get }
}
func +(left: Aable, right: Aable) -> Aable {
let leftType = left.dynamicType
//error: 'Aable' cannot be constructed because
// it has no accessible initializers
return leftType(name: left.name + right.name)
}
struct A: Aable {
let name: String
}
let a1 = A(name: "A #1")
let a2 = A(name: "A #2")
a1 + a2
You don't need reflection and AableError.
First of all lets add an init to your protocol
protocol Aable {
var name: String { get }
init(name:String)
}
Now you want a + operator where:
both params conform to the protocol Aable
the params have the same type
the return type is the same of the params
You can define this constraints with generics
func +<T:Aable>(left: T, right: T) -> T {
return T(name:left.name + right.name)
}
Now the check is performed at compile time so there's no need of the throws. The compiler will make sure the used values do have the proper type.
Test #1
struct Box:Aable {
let name:String
}
let amazon = Box(name: "Amazon")
let apple = Box(name: "Apple")
let res = amazon + apple
res.name // "AmazonApple"
Test #2
struct Box:Aable {
let name:String
}
struct Box:Aable {
let name:String
}
struct Bottle:Aable {
let name:String
}
let box = Box(name: "Amazon")
let bottle = Bottle(name: "Water")
box + bottle
// ^ compile error

Make a Functions Able to Handle Different Classes as Parameters

I'm trying to pass different classes, with the same properties, to the same function. How do I cast them for use with this function?
Below is a simple example to highlight what I'm trying to achieve.
class A {
var height:Int = 10
}
class B {
var height:Int = 20
}
class C {
static func grow(class:AnyObject) {
class.height + 5
}
}
C.grow(A)
C.grow(B)
The two final calls should yield 15 and 25, but without casting the AnyObject back to A or B, an error like the following is generated: "AnyObject has no member named".
How do I accomplish something like this?
Swift reflection API enables you to read values but not modify them. So if that's enough for you, you may use a method similar to the following which takes an object and the label of the member you want to access:
func getValue<T>(object: AnyObject, memberLabel: String) -> T? {
let mirror = Mirror(reflecting: object)
for member in mirror.children {
if let _ = member.label where member.label == memberLabel,
let value = member.value as? T {
return value
}
}
return nil
}
But if you want to modify the values, you have to define a protocol and make the classes conform to it:
protocol HasHeight {
var height: Int { get set }
}
extension HasHeight {
mutating func grow() {
self.height += 5
}
}
class A : HasHeight {
var height = 10
}
class B : HasHeight {
var height = 20
}
var a = A()
print(a.height)
a.grow()
print(a.height)
var b = B()
print(b.height)
b.grow()
print(b.height)
Here I defined grow() as a protocol extension so that it is available on every class/struct that conforms to the HasHeight protocol.
The results are:
10
15
20
25
You may define it elsewhere, but the call will have to be changed to include an & reference:
func grow<T: HasHeight>(inout sized: T) {
sized.height += 5
}
grow(&b)
Looks like a good case for a protocol! Define one with a height property, have A and B both implement that protocol, and then have the grow method accept the protocol as its parameter.