Swift error with generic array
Swift doesn’t have builtin behaviour to speculatively convert an array’s contents from one arbitrary type to another. It will only do this for two types that it knows have a subtype/supertype relationship:
class A { }
class B: A { }
let a: [A] = [B(),B()]
// this is allowed - B is a subtype of A
let b = a as? [B]
let a: [AnyObject] = [1,2,3]
// this is allowed - NSNumber is a subtype of AnyObject
let b = a as? [NSNumber]
struct S1 { }
struct S2 { }
let a = [S1(),S1()]
// no dice - S2 is not a subtype of S1
let b = a as? [S2]
The protocol doesn’t help either:
protocol P { }
struct S1: P { }
struct S2: P { }
let a = [S1(),S1()]
// still no good – just because S1 and S2 both conform to P
// doesn’t mean S2 is a subtype of S1
let b = a as? [S2]
Your example is basically a variant of this last one. You have an array of type [T]
, and you want to cast it to [MyType]
. It’s important to understand that you do not have an array of type [MyProtocol]
. Your generic type T
is a specific type, which has to implement MyProtocol
, but that’s not the same thing.
To see why you can’t just cast from any type to any other type, try this code:
protocol P { }
struct S: P { }
let a: [P] = [S(),S()]
let b = a as? [S]
This will generate a runtime error: "fatal error: can't unsafeBitCast between types of different sizes". This gives a hint as to why you can only cast an array from containing one reference type to a subtype – it’s because what is happening is just a bit cast from one pointer type to another. This will work for super/subtype classes, but not for arbitrary classes, structs, or protocols, as they have different binary representations.
Swift Generic Array 'not identical' error
Arrays in Swift are value types. That means that data
is copied when passed into your exchange
method, but you are trying to modify the copy to affect the original version. Instead you should do one of two things:
1. Define data
as an inout
parameter:
func exchange<T>(inout data:[T], i:Int, j:Int)
Then when calling it you have to add an &
before the call:
var myArray = ["first", "second"]
exchange(&myArray, 0, 1)
2. Return a copy of the Array (recommended)
func exchange<T>(data:[T], i:Int, j:Int) -> [T]
{
var newData = data
newData[i] = data[j]
newData[j] = data[i]
return newData
}
I recommend this way over the in-out parameter because in-out parameters create more complicated state. You have two variables pointing to and potentially manipulating the same piece of memory. What if exchange
decided to do its work on a separate thread? There is also a reason that Apple decided to make arrays value types, using in-out subverts that. Finally, returning a copy is much closer to Functional Programming which is a promising direction that Swift can move. The less state we have in our apps, the fewer bugs we will create (in general).
Extending array for object with generic type gives error
What you need is to move the constrain from the extension to the method, create a generic Equatable
type and add a where clause constraining the collection Element
to Foo
extension Collection {
func updateElement<T: Equatable>(element: Element) where Element == Foo<T> {
}
}
Swift function taking generic array
To start, you're using the ternary operator (?:
) in quite a complex situation. If you first modify your code to use an if
statement instead, the compiler error that was appearing in the call to outputArray.append(data)
- Generic parameter 'Element' could not be inferred
- appears in a much more sensible place (namely, the line of the if statement).
That error is relatively easy to solve - simply replace Array
with Array<Any>
, giving us this:
func flatten(input: [Any]) -> [Any] {
var outputArray = [Any]()
for i in 0..<input.count {
let data = input[i]
if data is Array<Any> {
outputArray += flatten(input: [data])
} else {
outputArray.append(data)
}
}
return outputArray
}
At this point, the original problem still occurs, because the value that's passed in to flatten(input:)
is [data]
. We know, due to the fact that the if
condition was true
, that data
is really of type Array<Any>
, and so the value [data]
must be an Array<Array<Any>>
. Instead, we want to pass in data
, which is already an array.
You say that the reason you wrote [data]
is because the argument has to be an array, and so you were "forced to" by the compiler. In fact, the only thing the compiler is forcing you to do is pass in an argument whose type is declared as Array<Any>
. We've made sure that data
is an array using the if
statement, but data
is still declared as an Any
(because it was an element of input
, which is an Array<Any>
), so the compiler has no idea that it's really an array.
The solution is - instead of using if data is Array<Any>
to determine momentarily whether data
is an array but immediately throw that information away - to convert data
to an Array<Any>
.
The new if
statement becomes if let dataArray = data as? Array<Any>
. The statement data as? Array<Any>
attempts to convert data
to an array, returning a value of type Array<Any>
if successful or nil
otherwise. Then the if let dataArray = ...
statement stores the value in dataArray
and returns true
if given a non-nil
value, or returns false
if given a nil
value (this is called conditional binding).
By doing that, in the true
case of the if
statement we have access to a value dataArray
that is of type Array<Any>
- unlike data
, which is only declared as Any
. Then dataArray
can be passed in to flatten(input:)
, and won't be nested inside another Array
.
func flatten(input: [Any]) -> [Any] {
var outputArray = [Any]()
for i in 0..<input.count {
let data = input[i]
if let dataArray = data as? Array<Any> {
outputArray += flatten(input: dataArray)
} else {
outputArray.append(data)
}
}
return outputArray
}
A couple of other notes:
Array<Any>
is of course equivalent to [Any]
, so the if
statement could be written with that (generally preferred) syntax, like so:
if let dataArray = data as? [Any] {
outputArray += flatten(input: dataArray)
}
Also, there's no need to go through the whole for i in 0..<input.count { let data = input[i] ...
ordeal if you just iterate over the array instead, like so:
func flatten(input: [Any]) -> [Any] {
var outputArray = [Any]()
for data in input {
if let dataArray = data as? [Any] {
outputArray += flatten(input: dataArray)
} else {
outputArray.append(data)
}
}
return outputArray
}
Generic Extension to Array Not Working
Give this a shot
extension Array where Element: Equatable {
func replaced (each valueToReplace: Element, with newValue: Element) -> [Element] {
var newArray = [Element]()
newArray.reserveCapacity(self.count)
for element in self {
let newElement = (element == valueToReplace) ? newValue : element
newArray.append(newElement)
}
return newArray
}
mutating func replace(each valueToReplace: Element, with newValue: Element) {
for (i, element) in self.enumerated() {
if element == valueToReplace { self[i] = newValue }
}
}
}
var j = [1,2,3,4,3,6,3,8,9]
var newArray = j.replaced(each: 3, with: 0)
It would be better to remove the redundancy by just making replaced
delegate to replace
:
extension Array where Element: Equatable {
func replaced(each valueToReplace: Element, with newValue: Element) -> [Element] {
var copy = self
copy.replace(each: valueToReplace, with: newValue)
return copy
}
mutating func replace(each valueToReplace: Element, with newValue: Element) {
for (i, element) in self.enumerated() {
if element == valueToReplace { self[i] = newValue }
}
}
}
Related Topics
Saving a Codable Struct to Userdefaults with Swift
Why Are Uiscreen.Bounds Incorrect in iOS11
Argument of '#Selector' Does Not Refer to an '@Objc' Method, Property or Initializer
How to Get Frame Data in Apprtc iOS App for Video Modifications
Swift: Specialize Method of Generic Class for Function Types
Change the Font of a Datepicker
Generic and (Early) Binding in Swift 1.2
Why Use Class Only Protocols in Swift
Swift - How to Resize a Uiview Based on Its Uilabel Size (That Is Inside)
Reading Data from Excel Document in a Swift App
Swift Cannot Assign to Self in a Class Init Method
Implementing Swift Protocol Methods in a Base Class
Type Check Operator (Is) for Check VS Homogenous Protocol: Why Can This Be Done for Optionals
Why Is Deinit Not Called Until Uiview Is Added to Parent Again
Sf Symbols Hierarchical, Palette, and Multicolor Rendering Mode Colors