Can't create default closure parameter in Array extension method in Swift
The elements in an array don't have to be Comparable
, which means you can't write that function for all possible Arrays.
Unfortunately Swift doesn't allow you to extend just a subset of Arrays. In other for your function to work, the compiler needs extra type information to infer the type C
.
Others have struggled with this same issue:
How can we create a generic Array Extension that sums Number types in Swift?
Instead of using an extension, you should be able to use minimum
as a global function.
Why doesn't Array.sorted() have default closure?
In let list2 = sorted(list)
the function
func sorted<C : SequenceType where C.Generator.Element : Comparable>(source: C) -> [C.Generator.Element]
is called. This function is only defined for sequences for which the
element type is Comparable
, i.e. can be compared with <
.
On the other hand, it is not possible to define an array extension
method sorted()
which applies only to arrays with comparable elements,
compare
- Array extension to remove object by value: "You cannot write a method on a generic type that is more restrictive on the template."
- Is it possible to make an Array extension in Swift that is restricted to one class?
Apple's Swift compiler complains of partial application when creating extension method on Array
To use an operator as a function, surround it in parens:
var min = array.comparest({ $0 }, (<))!
For example:
[1,2,3].reduce(0,(+)) // -> 6
Method signature to take the same array in array extension
Just simply pass parameter of type Array
extension Array {
func doSomethingWithAnotherArray(arr: Array) {
... // do something
}
}
[Int].doSomethingWithAnotherArray(arr: [Int]) // works
[Int].doSomethingWithAnotherArray(arr: [String]) // doesn't work
Is it possible to make an Array extension in Swift that is restricted to one class?
As of Swift 2, this can now be achieved with protocol extensions,
which provide method and property implementations to conforming types
(optionally restricted by additional constraints).
A simple example: Define a method for all types conforming
to SequenceType
(such as Array
) where the sequence element is a String
:
extension SequenceType where Generator.Element == String {
func joined() -> String {
return "".join(self)
}
}
let a = ["foo", "bar"].joined()
print(a) // foobar
The extension method cannot be defined for struct Array
directly, but only for all types
conforming to some protocol (with optional constraints). So one
has to find a protocol to which Array
conforms and which provides all the necessary methods. In the above example, that is SequenceType
.
Another example (a variation of How do I insert an element at the correct position into a sorted array in Swift?):
extension CollectionType where Generator.Element : Comparable, Index : RandomAccessIndexType {
typealias T = Generator.Element
func insertionIndexOf(elem: T) -> Index {
var lo = self.startIndex
var hi = self.endIndex
while lo != hi {
// mid = lo + (hi - 1 - lo)/2
let mid = lo.advancedBy(lo.distanceTo(hi.predecessor())/2)
if self[mid] < elem {
lo = mid + 1
} else if elem < self[mid] {
hi = mid
} else {
return mid // found at position `mid`
}
}
return lo // not found, would be inserted at position `lo`
}
}
let ar = [1, 3, 5, 7]
let pos = ar.insertionIndexOf(6)
print(pos) // 3
Here the method is defined as an extension to CollectionType
because
subscript access to the elements is needed, and the elements are
required to be Comparable
.
Using a property as a default parameter value for a method in the same class
I don't think you're doing anything wrong.
The language specification only says that a default parameter should come before non-default parameters (p169), and that the default value is defined by an expression (p637).
It does not say what that expression is allowed to reference. It seems like it is not allowed to reference the instance on which you are calling the method, i.e., self, which seems like it would be necessary to reference self.niceAnimal.
As a workaround, you could define the default parameter as an optional with a default value of nil, and then set the actual value with an "if let" that references the member variable in the default case, like so:
class animal {
var niceAnimal: Bool
var numberOfLegs: Int
init(numberOfLegs: Int, animalIsNice: Bool) {
self.numberOfLegs = numberOfLegs
self.niceAnimal = animalIsNice
}
func description(numberOfLegs: Int, animalIsNice: Bool? = nil) {
if let animalIsNice = animalIsNice ?? self.niceAnimal {
// print
}
}
}
Subclass Type as closure parameter
Ok, so I've been working on this for a day and I have found a way to do it using the method in this answer for the didChange and initObserver functions and taking inspiration from this way of saving data in extensions.
First off, all the functions that need to use the type of the subclass are moved to a protocol.
protocol FirebaseObjectType {}
extension FirebaseObjectType where Self: FirebaseObject {
private func initObserver(at ref: DatabaseReference) {
//...
}
mutating func didChange(completion: @escaping (((Self) -> Void))) {
if observer == nil {
// init Firebase observer here so there will be no Firebase
// observer running when you don't check for changes of the
// object, and so the Firebase call uses the type of whatever
// FirebaseObject this function is called on eg:
// RecipeItem.didChange returns RecipeItem
// and NOT:
// RecipeItem.didChange returns FirebaseObject
initObserver(at: ref)
}
if closureWrapper == nil {
// init closureWrapper here instead of in init() so it uses
// the class this function is called on instead of FirebaseObject
closureWrapper = ClosureWrapper<Self>()
}
// Save closure for future updates to object
closures.append(completion)
// Activate closure with current object
completion(self)
}
}
To save the closures I now use a wrapper class so I can do type checking on that. In FirebaseObject:
class ClosureWrapper<T> {
var array: [((T) -> Void)]
init() {
array = []
}
}
fileprivate var closureWrapper: AnyObject?
Now I can get the closures with the right type in FirebaseObjectType protocol:
private var closures: [((Self) -> Void)] {
get {
let closureWrapper = self.closureWrapper as? ClosureWrapper<Self>
return closureWrapper?.array ?? []
}
set {
if let closureWrapper = closureWrapper as? ClosureWrapper<Self> {
closureWrapper.array = newValue
}
}
}
I can now use didChange on a FirebaseObject subclass without checking its type every time.
var recipe = RecipeItem(data)
recipe.didChange { newRecipe in
// Do something with newRecipe
}
Swift Generics Type Inference Extensions
When you define max
(and min
) like this:
func max<T, U : Comparable>(f: T -> U ) -> U?
You're actually saying that the closure f
might take a different type than the elements in the Array
. Since Array
is already a generic struct Array<T>
, you can re-use the element type T
that it's already defined. From the Swift language guide:
When you extend a generic type, you do not provide a type parameter
list as part of the extension’s definition. Instead, the type
parameter list from the original type definition is available within
the body of the extension, and the original type parameter names are
used to refer to the type parameters from the original definition.
So, since the element type T
is already available to you, simply take the T
out of the type parameter list, like so:
func max<U : Comparable>(f: T -> U ) -> U?
That guarantees that the closure f
will take the same type T
that the elements in the Array
have. Then the compiler can correctly infer which types you are using in your test cases and fixes the errors you're seeing.
Related Topics
Does Swift Optimise Chained Creation and Copy of Structs
Xcode11 Error "Open(_:Options:Completionhandler:) Is Unavailable in Application Extensions"
Fetching Child Sum from Core Data
Xcode 8 Swift 3 Pitch-Altering Sounds
Trying to Add a Protocol to a Class Signature in Swift
Firebase and Reading Nested Data Using Swift
Function Builder Not Working When Only One Value
Resulting Mtltexture Lighter Than Cgimage
Changing The Color of a Button in Swiftui on Tvos
Play Sound with a Little Delay
Could Not Find a Storyboard Named 'Maintabcontroller' in Bundle Nsbundle
Swift 3 - Pass Struct by Reference via Unsafemutablerawpointer
How to Pass a Class and Method to Create an Instance of a Class
How to Set iOS 13 Glyphs Programmatically
How to Add 3D Shapes in Swift Ui
Why Use an Extra Let Statement Here