Why (Array<Character> = []) is Array<Bool> == true - arrays

If the defined array is empty, why the data type inside the array does not work
I create a empty Character Array, and I confused that why the result of a is Array<Bool> is true.
In fact, i tried a is Array<T> (T: Bool, String, Int...), all of them is true.
var a: Array<Character> = []
a is Array<Bool>
The result is:
true

After some digging, I found that this is the intended behavior.
Swift checks whether an array can be downcast by attempting to downcast all of the source elements to the destination element type. Since an empty array contains no elements, that means down casting it to another array with a different element type is always successful.
Below are swift's code for handling the as? operator. I doubt that swift handles the is operator differently.
public func _arrayConditionalCast<SourceElement, TargetElement>(
_ source: [SourceElement]
) -> [TargetElement]? {
var successfulCasts = ContiguousArray<TargetElement>()
successfulCasts.reserveCapacity(source.count)
for element in source {
if let casted = element as? TargetElement {
successfulCasts.append(casted)
} else {
return nil
}
}
return Array(successfulCasts)
}
https://github.com/apple/swift/blob/main/stdlib/public/core/ArrayCast.swift (line 74)
Sources
A similar bug report an swift.org: SR-7738
Some convincing explanation: SR-6192

Related

How to check that object of type `Any` is an array of concrete class implementing some protocol

I have faced an issue.
Consider I have one protocol and two classes implementing it:
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
then at some point I'm creating an array and passing it to function:
var array = [A]()
func update(object: Any){
}
update(object: array)
from that function update I would like to pass object to another function if it mets another function's conditions:
func process<T: Initiatable>(array: T){
/* ... */
}
So how can I check that object of type Any is an array of concrete class implementing Initiatable protocol? I would like to write something like
func update(object: Any){
if let array = object as Array<T: Initiatable>{
process(array: array)
}
}
But that's doesn't work. Code like:
func update(object: Any){
if let array = object as [Initiatable]{
process(array: array)
}
}
func process(array: [Initiatable]){ }
Compiles fine, but that's not what I want – process function should receive an array of concrete implementation of Initiatable so at some point it could use:
func process<T: Initiatable>(array: [T]){
/* other code */
T.init()
}
So is there any way to do this? Thank you very much in advance!
There's a few parts to this question:
Generating the array of types
Your array declaration is expecting an array of A objects rather than A types. To generate an array with A types, you could pass in a Postfix self expression: (link)
var array = [ A.self ]
This would define array as an array of A.Type, called the Metatype Type (same link).
You could also generate an empty array with this metatype type:
var array:[A.Type] = []
If you wanted an array with both A.self and B.self you could either specify it as [Any]...
var array:[Any] = [A.self,B.self]
...or, make use of the Initiatable protocol you created:
var array:[Initiatable.Type] = [A.self,B.self]
Downcasting an array to an array of types in your update method
You were having trouble downcasting an Any object to an array of types.
Here's my updated update method:
func update(object: Any){
if let array = object as? [Initiatable.Type] { //1
process(array: array)
}
}
You can now perform an optional downcast on the array that is being past into your update method. Downcast it to an array of metadata types of Initiatable: (This is the only line I modified from your method)
Receiving a type as a parameter in the process method
I'm assuming that you just want your process method to receive an array of types, and instantiate a variable based on one of those types. You didn't mention which element in the array, so I've just gone with the first.
func process(array: [Initiatable.Type]){ //1
if let firstType = array.first { //2
let firstObject = firstType.init() //3
}
}
The process method can receive an array of types that adopt the Initiatable protocol.
I'm using optional binding to get at the value of the first element in the array. This should be a type.
Instantiate an object based on the first element in the array.
If your array is nonempty, then you can solve this by grabbing the runtime type of one element from the array:
func update(object: Any){
if let array = object as? [Initiatable]{
process(array: array)
} else {
// Not initiatable
}
}
func process(array: [Initiatable]) { // no need for generics here
guard let first = array.first else {
// Unable to determine array type
// throw or return or whatever
}
// type(of: first) gives A or B
type(of: first).init()
}
If the array is empty, then I don’t know of a way to do this in Swift (as of Swift 3, anyway).
The static type of object is Any, so you need to use the runtime type of object in order to get anywhere.
However, Swift (and most languages with generics) only resolve type parameters like T based on the static types of the values you pass. Therefore your whole approach with process<T: Initiatable>(array: [T]) is a dead end: T can only take on a type the compiler already knew when you called process().
Swift does give you access to runtime types: type(of: object) will return (for example) Array<A>. So close! But:
AFAIK, there is currently no way to extract type parameters from a metatype, i.e. to get A from Array<A>, unless the compiler already knows the type parameter statically.
You can get the string "Array<A>" and extract the string A from it, but AFAIK there is no way to map strings back to types in Swift unless the type is an Objective-C class.
In short, I think you’ve just hit a limit of Swift’s metatype / reflection capabilities.
Unfortunately I can't think of a solution in a given constraints. It seems like you are trying to achieve compile-time polymorphism at runtime.
Using generics implies that compiler understands what class will be called. But using Any implies that at compile time it could be anything and it would be known only at runtime.
The closest solutions that I can propose are:
// Either use generic `update` function
func updateGeneric<T: Initiatable>(array: Array<T>){
process(array: array)
}
// or explicitly convert `Any` to the class,
// so compiler understands what implementation of `process` to call
func updateExplicit(object: Any) {
if let array = object as? [A] {
process(array: array)
}
if let array = object as? [B] {
process(array: array)
}
}
Is this help you,
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
class Test<T : Initiatable>
{
func update(object: Any){
if let array = object as? [T]{
process(array: array)
}
}
func process(array: [T]){
}
}
I found a workaround to solve this problem:
func classTypeFrom(_ className: String) -> AnyClass!{
if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
let classStringName = "_TtC\(appName.characters.count)\(appName)\(className.characters.count)\(className)"
return NSClassFromString(classStringName)
}
return nil;
}
func update(object: Any){
if let array = object as? [Initiatable]{
let arrayTypeName = "\(type(of: ar))"
let objectTypeName = arrayTypeName.substringFrom(index: 6, length: arrayTypeName.characters.count - 7)
if let arrayType = classTypeFrom(objectTypeName) as? Initiatable.Type{
process(array: array, type: arrayType)
}
}
}
func process(array: [Initiatable], type: Initiatable.Type){
var ar = array
let newObj = type.init()
ar.append(newObj)
}

Check if my array contains a dictionary

I have an array that contains a mixture of strings, arrays, and dictionaries. I would like to check if it contains a dictionary. I am trying the following, but receiving errors:
if array.contains(Dictionary<String, AnyObject>) {
}
How would I accomplish this?
You don't have to bother with overloading the contains method if you don't want to – there's already a version of it that can take a custom predicate to check against each element to determine whether it should be considered to be 'in the array'.
It has the advantage of returning early as soon as it finds a match, unlike filter. You could still turn this into an extension method if you really want – but in my opinion it's so concise you shouldn't have to.
if array.contains({$0 is [String:AnyObject]}) {
print("contains dictionary")
} else {
print("doesn't contain dictionary")
}
#Ryan, the type does not have to conform to the Equatable protocol.
This would get the job done:
extension Array {
// This is a concise, yet inefficient implementation
func contains<T>(type type : T.Type) -> Bool {
return !filter({ $0 is T }).isEmpty
}
// Here is a more efficient implementation
func contains<T>(type type : T.Type) -> Bool {
var contains = false
for element in self {
if element is T {
contains = true
break
}
}
return contains
}
}
It can be used as such:
if array.contains(type: Dictionary<String, AnyObject>.self) {
// Run code if the array holds a dictionary of the given type
}
This requires that the array elements conform to the Equatable protocol (which Dictionary doesn't).
You would have to extend contains with something like this:
extension Array {
func contains<T where T : Equatable>(obj: T) -> Bool {
return self.filter({$0 as? T == obj}).count > 0
}
}

Array extension, Ambiguous reference to member 'subscript'

I'm trying to write an extension to Array in Xcode's playground. I want to write a function that will modify the array so that it will be filled with 0s when the function is called. The code I'm attempting to use is this:
import Foundation
extension Array {
mutating func zero() {
for i in 0..<self.count {
self[i] = 0 // ERROR - Ambiguous reference to member 'subscript'
}
}
}
This code does not run, because of the error at:
self[i] = 0
However, if I try to 'get' a value from self, I have no issues. For example:
import Foundation
extension Array {
mutating func zero() {
for i in 0..<self.count {
print(self[i])
}
}
}
has no errors and runs as expected.
So my question is; Why can't I modify the array?
Also, Replacing:
self[i] = 0
with,
self.append(0)
also results in an error. (Cannot invoke 'append' with an argument list of type '(Int)')
So it won't let me modify self at all it seems.
It will work if you do the following:
extension Array where Element: IntegerLiteralConvertible {
mutating func zero() {
for i in 0..<self.count {
self[i] = 0
}
}
}
You must constrain the type of the elements allowed, because you can't, for example, zero out an array of Strings.
It's important to remember, when extending something like an Array, that you take into account what types of elements are valid for the method you're adding. If it requires elements of a certain type, you constrain on it and then it will work. That way you can only use your zero() method on Arrays containing ints. You might define a different version for an array of Strings that replaces everything in the array with the string "zero", for example, and that implementation will only be used on the type that you constrain it to as well.

Pass Array containing either class or struct

In Swift I declared a function that differs from Array.count only in that if array == nil the function returns 0. This is related to my UITableViewDataSource, but that's not important here. The problem is, if I declare the function as:
class func countOfItemsInArray(array: [AnyObject]?) -> Int
and then try to pass it an array of structs, it declares that the structs in the array do not conform to AnyObject. I understand why that is (I think), but is there a way to make this work with classes and structs, or should I just give in to copy and paste?
Generics are probably better suited to this problem than relying on covariance of [AnyObject]. A version of countElements that worked on an optional array and returned 0 in case of nil could go like this:
func countElements<T>(array: [T]?) -> Int {
return array?.count ?? 0
}
When you call countElements with any kind of array, the placeholder T is replaced with the type of the element contained in the array.
Note, this version overloads the existing countElements with a version that takes an optional. If you call it with a non-optional or any other kind of collection, the Swift version would be called, if you pass in an optional array, this one will be called. It’s debatable whether this is a good practice (I think it’s fine) or a bad one (some may disapprove :).
A version that works on any collection type would be:
func countElements<C: CollectionType>(col: C?) -> C.Index.Distance {
return col.map { countElements($0) } ?? 0
}
If you use Any instead of AnyObject you can pass any type, so also structs:
class func countOfItemsInArray(array: [Any]?) -> Int
This is kind of weird.
I used this function:
func countOfItemsInArray(array: [Any]?) -> Int {
return array != nil ? array!.count : 0
}
Declared two of your Assignment structs and put them in an array:
let structOne = Assignment(name: "1", dueDate: NSDate(), subject: "1")
let structTwo = Assignment(name: "2", dueDate: NSDate(), subject: "2")
let myArray: [Assignment] = [structOne, structTwo]
But here's the interesting part.
When calling println(countOfItemsInArray(myArray)) it gives the error:
<stdin>:27:33: error: 'Assignment' is not identical to 'Any'
println(countOfItemsInArray(myArray))
^
<stdin>:17:26: note: in initialization of parameter 'array'
func countOfItemsInArray(array: [Any]?) -> Int {
^
So I tested if myArray is of type [Any]:
println(myArray is [Any])
to which swift says:
<stdin>:25:17: error: 'Any' is not a subtype of 'Assignment'
println(myArray is [Any])
^
But when I change the type annotation of myArray to [Any] it works:
let myArray: [Any] = [structOne, structTwo]
And when simply handing the literal to the function it works, too:
countOfItemsInArray([structOne, structTwo])
The whole code example can be seen here.

How to tell if a variable is an array

I have a Swift function that accepts Any and I want it to be able to accept an array of Strings, an array of Ints, a mixed array, or an array of arrays, etc. It also can accept just a String or an Int, etc, not in an array.
So I have this:
private func parse(parameter: Any) {
if parameter is Int {
// Int
} else if (parameter is Float) || (parameter is Double) {
// Double
} else if parameter is String {
// String
} else if parameter is Bool {
// Bool
} else if let array = parameter as? [Any] {
// Should catch all Arrays
} else {
assert(false, "Unsupported type") // [String] ends up here
}
}
But if I call parse(["Strings"]), the assert is raised. How can I catch all types of Arrays?
edit - there was some confusion as to what I'm trying to accomplish. I basically need to return a String based on the type, so Int -> "" and String -> "", so an array would make recursive calls to return "..."
This post is marked as a duplicate, but that other question is about Javascript, not Swift.
I finally found the way to do that, which is to use NSArray for casting.
private func parse(x: Any) {
if let o = x as? [Any] {
println("[Any]")
}
if let o = x as? [AnyObject] {
println("[AnyObject]")
}
if let o = x as? NSArray {
println("NSArray")
}
}
let a: [Any] = ["bar"]
let b: [AnyObject] = ["bar"]
let c = ["foo", 3.14]
parse(a) // ==> [Any]
parse(b) // ==> [AnyObject], and also NSArray
parse(c) // ==> NSArray
It look so that an array containing values of Any internally represented in NSArray.
(But should it be able to cast c to [Any]...? I'm suspecting it's a bug.)
The key to understanding typing and type related issues in Swift is that all roads lead to protocols.
The challenge of this problem is detecting any type of array, not just one concrete type. The OP's example failed because [Any] is not a base class or a generalized pattern of [String], that is to say, that (from what I can tell), in Swift [T] is not covariant on T. Beyond that, you cannot check for SequenceType or CollectionType since they have associated types (Generator.Element).
The idiomatic solution is thus to use a marker protocol to indicate which types you want to match your criteria. As illustrated below, you achieve this by creating an empty protocol, and associating it with the types of interest.
import Foundation
protocol NestedType {}
extension Array: NestedType {}
extension Set: NestedType {}
extension Dictionary: NestedType {}
extension NSSet: NestedType {}
protocol AnyTypeOfArray {}
extension Array: AnyTypeOfArray {}
extension NSArray: AnyTypeOfArray {}
protocol AnyTypeOfDictionary {}
extension Dictionary: AnyTypeOfDictionary {}
func printType(v:Any) {
if v is NestedType {
print("Detected a nested type")
}
if v is AnyTypeOfArray {
print("\t which is an array")
}
else if v is AnyTypeOfDictionary {
print("\t which is a dictionary")
}
}
printType([String:Int]())
printType([Int]())
printType(NSArray())
The output of which is:
Detected a nested type
which is a dictionary
Detected a nested type
which is an array
Detected a nested type
which is an array
One way you can do this is to separate the function out to two separate implementations (with the same name), one that takes anArray and one for everything else. You'll also need to make them generic functions instead of using the Any type. With that setup, Swift can use type inference to figure out the best function to call.
I'd implement it something like this (I'm just printlning the type to show where things end up):
func parse<T>(parameter: T) {
if parameter is Int {
println("Int")
} else if (parameter is Float) || (parameter is Double) {
println("Double")
} else if parameter is String {
println("String")
} else if parameter is Bool {
println("Bool")
} else {
assert(false, "Unsupported type")
}
}
func parse<T>(parameter: Array<T>) {
println("Array")
for element in parameter {
// Recursively parsing...
parse(element)
}
}
Then calling it like this:
parse(1) // Int
parse(0.1) // Double
parse("asdf") // String
parse(true) // Bool
parse(["asdf", "asdf"]) // Array -> String String
Outputs:
Int
Double
String
Bool
Array
String
String
You can use the _stdlib_getTypeName that returns the mangled type name for the given value.
For example:
var myString = "String"
var myInteger = 10
var myArray = [10,22]
var myDictionary = ["one": 1, "two": 2, "three": 3]
println("\(_stdlib_getTypeName(myString))")
println("\(_stdlib_getTypeName(myInteger))")
println("\(_stdlib_getTypeName(myArray))")
println("\(_stdlib_getTypeName(myDictionary))")
The result will be:
_TtSS // for String
_TtSi // for integer
_TtSa // for array
_TtVSs10Dictionary // for dictionary

Resources