[swift-users] Store objects of generic types in a data structure

Brent Royal-Gordon brent at architechies.com
Sat Apr 8 21:50:21 CDT 2017


> On Apr 8, 2017, at 5:22 AM, Hugo Lundin via swift-users <swift-users at swift.org> wrote:
> 
> How would I be able to achieve this in Swift? My current idea of this approach does not seem to work, but also the idea to create specific subclasses for every type (and then type cast the different properties), gives a lot of work, and there would be a limitation for which types are available. 

I would create a protocol with the same interface (other than initializers) as your `Property` type, but with an `Any` equivalent to any `T`-taking API. Then make your `Array` use the protocol, not `Property` itself, as its element type.

	// Let's say this is your existing type:
	struct Property<T> {
		let description: String
		let property: T
		
		init(description: String, property: T) {
			self.description = description
			self.property = property
		}
	}

	// Create a protocol equivalent that doesn't use T:
	protocol AnyProperty {
		var description: String { get }
		var anyProperty: Any { get }
	}
	
	// Then conform `Property` to it:
	extension Property: AnyProperty {
		var anyProperty: Any {
			return property
		}
	}
	
	// Now, in your Analytics class:
	class Analytics {
		private var properties: [AnyProperty]
		
		func add(_ property: AnyProperty) {
			properties.append(property)
		}
		
		func show() {
			for p in properties {
				print("\(p.description): \(p.anyProperty)")
			}
		}
	}

	// And do:
	let stats = Analytics()
	stats.add(Property(description: "HealthKit", property: false))
	stats.add(Property(description: "iOS Version", property: "9.3"))
	stats.show()

This is called "type erasure"; we say that the `AnyProperty` protocol is "erasing" the type of the `T` generic parameter because it essentially hides it behind the protocol. Java automatically erases all type parameters, which is convenient here, but it can lead to bugs. (For instance, the generic parameter of `List` is similarly erased, so you can cast a `List<Property>` up to `List<Object>`, then add non-`Property` objects to it. When you later run `show()`, it will throw an exception.) Swift is more strict, which prevents bugs but sometimes means you need to explicitly erase types.

Here we've used a protocol to erase the type of `Property`, but that often isn't an option—for instance, when you cast an instance to a protocol type, the "wrapper" around it doesn't itself conform to any protocols. When it isn't, you have to make a more complicated type-erasing wrapper—usually either by making a closure that calls each method and storing them in properties, or by writing a non-generic abstract superclass which declares all the visible members and a generic subclass that overrides the superclass declarations.

Here's an article on how to write type-erased wrappers in more complicated situations: <https://www.bignerdranch.com/blog/breaking-down-type-erasures-in-swift/>

Hope this helps,
-- 
Brent Royal-Gordon
Architechies

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20170408/b8aae494/attachment.html>


More information about the swift-users mailing list