Generics in Swift
The subscript method of collections is defined as
public subscript(position: Self.Index) -> Self.Iterator.Element { get }
which means that your function should take as arguments
- a collection
C
, and - a value of the associated type
C.Iterator.Element
and return an array of C.Index
. In addition, the element type
should be Equatable
:
func findAll<C: Collection> (_ collection: C, _ element: C.Iterator.Element) -> [C.Index]
where C.Iterator.Element: Equatable
{ ... }
Similar as in your solution for arrays, one can loop over the
collection's indices:
func findAll<C: Collection> (_ collection: C, _ element: C.Iterator.Element) -> [C.Index]
where C.Iterator.Element: Equatable
{
var result: [C.Index] = []
var idx = collection.startIndex
while idx != collection.endIndex {
if collection[idx] == element {
result.append(idx)
}
collection.formIndex(after: &idx)
}
return result
}
One would expect that something like
for idx in collection.startIndex ..< collection.endIndex
// or
for idx in collection.indices
works, but (in Swift 3) this requires an additional constraint
on the associated Indices
type:
func findAll<C: Collection> (_ collection: C, _ element: C.Iterator.Element) -> [C.Index]
where C.Iterator.Element: Equatable, C.Indices.Iterator.Element == C.Index
{
var result: [C.Index] = []
for idx in collection.indices {
if collection[idx] == element {
result.append(idx)
}
}
return result
}
This is no longer necessary in Swift 4, see for example
Unable to use indices.contains() in a Collection extension in Swift 3 for a good explanation.
This can now be simplified using filter
:
func findAll<C: Collection> (_ collection: C, _ element: C.Iterator.Element) -> [C.Index]
where C.Iterator.Element: Equatable, C.Indices.Iterator.Element == C.Index
{
return collection.indices.filter { collection[$0] == element }
}
Example (a collection of Character
):
let chars = "abcdabcdabcd".characters
let indices = findAll(chars, "c")
for idx in indices {
print(chars[idx])
}
Set
is a Collection
as well, it has an associated Index
type and a subscript
method. Example:
let set = Set([1, 2, 3, 4, 5, 6, 7, 8, 9])
let indices = findAll(set, 3)
for idx in indices {
print(set[idx])
}
Finally you might want to define the function as a method
on the Collection
type:
extension Collection where Iterator.Element: Equatable, Indices.Iterator.Element == Index {
func allIndices(of element: Iterator.Element) -> [Index] {
return indices.filter { self[$0] == element }
}
}
// Example:
let indices = [1, 2, 3, 1, 2, 3].allIndices(of: 3)
Using Any instead of generics
In swift, you can assign value to a variable ONLY if the type of the variable is same with the value. So imagine you have this:
var a = 1
var b = 2
func swapTwoValues(_ a: inout Any, _ b: inout Any) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoValues(&a, &b) // <- Cannot pass immutable value as inout argument: implicit conversion from 'Int' to 'Any' requires a temporary
So compiler forces you to implicitly assign Any for the type of the variables. This is not what you want I think. So you have to tell the compiler that it doesn't matter the type of the arguments. The only thing matters is they both have same type to fulfill compilers need. So to achieve this, you can use generic or some kind of protocol.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoValues(&a, &b) // Done
Tip: You can swap values with tuple in single line of code without using temporaryVar like this: (Even without function and generic and etc.)
(a, b) = (b, a)
How to get a class with generic type accept an array of different by same generic types?
It's good that you want to experiment with generics, but this isn't the occasion for it. You say:
The idea ... is to support other items in the future in case I want to expand the app...not just coffee.
But you don't need a generic for that. The only requirement for managing an order is that the order's item be Priceable. Priceable is already a type; you don't need to add a generic type to the mix.
struct Order {
var name: String
var item: Priceable
}
class OrderManager {
private var orders: [Order]
init(orders: [Order]) {
self.orders = orders
}
func add(_ order: Order) {
self.orders.append(order)
}
}
What's the difference between using or not using the 'where' clause with generics?
This is clearly stated in the Swift guide:
The requirements in a generic where clause specify that a type
parameter inherits from a class or conforms to a protocol or protocol
composition. Although the generic where clause provides syntactic
sugar for expressing simple constraints on type parameters (for
instance,<T: Comparable>
is equivalent to<T> where T: Comparable
and
so on), you can use it to provide more complex constraints on type
parameters and their associated types. For instance, you can constrain
the associated types of type parameters to conform to protocols. For
example,<S: Sequence> where S.Iterator.Element: Equatable
specifies
thatS
conforms to theSequence
protocol and that the associated type
S.Iterator.Element
conforms to theEquatable
protocol. This constraint
ensures that each element of the sequence is equatable.
Simply put, where
lets you specify constraints about the associated types of a generic parameter, while in <>
you can't do this.
Swift, use generic property with different specific types - Reference to generic type requires arguments in
You can specify a protocol for providing Stage
types like so:
protocol StageProvider {
associatedtype T: Stage
func getType() -> T.Type
}
Then make your SomethingThatKnowsAboutTheStages
or any other one conform this protocol:
class SomethingThatKnowsAboutTheStages: StageProvider {
typealias T = SecondStage
func getType() -> T.Type {
SecondStage.self
}
}
Add an initializer for your FooBarController
:
class FooBarController<StageType: Stage>: UIViewController {
convenience init(stage: StageType.Type) {
self.init()
}
}
And finally use all these:
func fooBarScreen<T: StageProvider>(boop: T) {
let controller = FooBarController(stage: boop.getType())
}
Swift generics and Any
I ended up using a generic function and adding a generic value as needed, somethign like this.
private func invoke<T: Encodable, U: Encodable>(_ methodName: String, _ callBack: Any?, _ callbackState: Any?, expectedArgs: Int,
_ arg1: T? = nil, _ arg2: U? = nil, _ arg3: T? = nil, _ arg4: T? = nil) {...
Related Topics
How to Define an Extension to Collectiontype So That Its Methods Are Available to Dictionaries
Swift Progress View with Nstimer
From the Swift Repl, How to Get a List of Available Modules
How to Cast a Metaclass Object to a Protocol Type in Swift
How to Have Arview and Arscnview Coexist
Can Not Conform to Protocol by Creating Extension with Where Clauses
Margin Between Images in Uiscrollview
Text Overlapping Itself in Table Cells Swift
How to Prevent Eventstore Access Error on First Run
How to Create a Custom Nstablecellview from a Nib
How to Duplicate a Sprite in Sprite Kit and Have Them Behave Differently
Swiftui Sheet Not Updating Variable
How to Access Cfdictionary in Swift 3
Different Colors for Bars in Barchart Depend on Value
Swiftui Widget iOS 14 Gradient Issue