I'm trying to parse a json which has an array with Strings, Ints and Arrays
So I'm to iterate on the json {["item1", 2, ["subItem1", "subitem2"] ]} members in the array:
func parse(json : [Any])
for item in json{
if let arr = item as? Array { //
//do stuff for array
}
}
but I'm getting this compile error:
Generic parameter 'Element' could not be inferred in cast to
'Array<_>'
Optional bind item to the different types
for item in json {
if let stringItem = item as? String {
//do stuff for String
} else if let intItem = item as? Int {
//do stuff for Int
} else if let arrayItem = item as? [String] {
//do stuff for Array
} else {
// it's something else
}
}
You receive the error is because in Swift, Array is a generic container that holds values of a specific type. So you can have an Array<Int> that holds integers, or an Array that holds strings. But you can’t have just an Array. The type of the thing the array contains is the generic parameter, and Swift is complaining because it can’t figure out what that type should be. Sometimes it can infer that type from the context of the code around it, but not always, as in this case.
func parse(json : [Any])
for item in json{
if let arr = item as? Array<Any> { //
//do stuff for array
}
}
Instead of writing Array<Any>, you can write the shorter form, [Any].
You can also solve the problem by using NSArray, as you’ve found. Unlike Array, NSArray doesn’t use generics, since it originates in Objective-C which has a different approach to Swift. Instead, NSArray holds only one kind of thing, an AnyObject. This is is a reference that can point to instances of any class.
Related
I have an array of object, each object contains a discount rate , I need to sort them increasingly by their rate,
struct ShopDetails {
var shopId: Int?
var discountRate: String?
init(with json: Dictionary<String,Any>) {
shopId = json["id"] as? Int
discountRate = json["discount"] as? String
}
I tired to sort them using this method;
func getShopsByDiscount() {
let sortedImages = self.copyOfShops.sorted(by: { (shop1: ShopDetails, shop2: ShopDetails) -> Bool in
return Int(shop1.discountRate) < Int(shop2.discountRate)
})
}
I tried to cast the rate to integer, since its been received form the backend as string, but I got an error:
value of type Any has no member discountRate.
any idea how to do it? if there is a way to do it without casting it will be better
First, you need to verify that the array you're starting with is of type [ShopDetails]. The error indicates that this is probably an Objective-C NSArray, which won't work as well in Swift. If you're unclear about this, I suggest you Google the topic: there's no reason to use NSArray in Swift.
Below, I assume the array is the correct type ([ShopDetails]). From here, you need to do two additional things, because discountRate is of type String?.
You need to check if the string is actually there
You need to check if it can actually be expressed as an Int.
With these things in mind, your sort function can look like this:
let sortedImages = copyOfShops.sorted(by: {
(shop1: ShopDetails, shop2: ShopDetails) -> Bool in
if let shop1String = shop1.discountRate, let shop1Value = Int(shop1String),
let shop2String = shop2.discountRate, let shop2Value = Int(shop2String) {
return shop1Value < shop2Value
}
return true
})
That being said, the best way to handle this is to change the type of discountRate from String? to Int, and do the above checks when you init(with json: Dictionary<String,Any>). If the server giving you the dictionary is something you control, have it switch to passing back Ints instead of Strings, and stop dealing with optionals if you don't have to.
We imported an outdated project, it prompted us to convert it to Swift 3. As individuals who are not highly knowledgeable in Swift, we are having difficulties fixing an error.
import Foundation
class CellDescriptorHelper {
let itemKey = "Items"
let isExpandableKey = "isExpandable"
let isExpandedKey = "isExpanded"
let isVisibleKey = "isVisible"
let titleKey = "title"
let locationKey = "location"
let descriptionKey = "description"
let imageURLKey = "imageURL"
let typeKey = "type"
let cellIdentifierKey = "cellIdentifier"
let additionalRowsKey = "additionalRows"
fileprivate var cellDescriptors: NSMutableArray!
fileprivate var cellsToDisplay: NSMutableArray!
func getCellDescriptors(_ type: String) -> NSMutableArray {
loadCellDescriptors(type)
return cellDescriptors;
}
func loadCellDescriptors(_ type: String) {
cellDescriptors = PlistManager.sharedInstance.getValueForKey(itemKey)! as! NSMutableArray
for i in 0..<(cellDescriptors[0] as AnyObject).count-1 {
let cellType = cellDescriptors[0][i][typeKey] as! String //creates error
if(cellType != type) {
cellDescriptors[0][i].setValue(false, forKey: isVisibleKey) //creates error
}
}
}
}
The reason you are getting this error is because the type of the object that is in your array is ambiguous to the compiler.
This is due to the fact that NSArrays aren't typed concretely.
NSArray is basically the Objective-C bridge of Array<Any> or [Any].
Let me walk you through your code...
let cellType = cellDescriptors[0][i][typeKey] as! String
The compiler knows that cellDescriptors is an NSArray as it is declared as one above. NSArrays can be subscripted to get the value at a given index, as you are doing with cellDescriptors[0]. The value this gives is of type Any, as explained above, so when you try and subscript it again with cellDescriptors[0][i], you are getting your error. As it happens, you can likely cast that Any object to an array, then you'll be able to perform the subscript, like so:
if let newArr = (cellDescriptors[0] as? [Any])[i] { }
However, this really isn't a nice approach and you end up dealing with a load of nasty optionals.
A better approach would be to concrete your declaration of cellDescriptors. I don't know how your type structure lies, but by the looks of things, it's an array of arrays of dictionaries (yuck). So in a raw form, your declaration should be var cellDescriptors = [[[AnyHashable:Any]]]() in order to subscript as you are now.
This said, the code you have in place is messy and I would consider changing the way you model your objects to make it more usable.
These lines have two problems:
fileprivate var cellDescriptors: NSMutableArray!
fileprivate var cellsToDisplay: NSMutableArray!
First, they're ! types (implicitly unwrapped optional or IUOs). They should never have been ! types, but in Swift 3 these work differently than they did in Swift 2, and that is likely breaking you. Get rid of the ! and assign these to an empty array to start. There are very few places that ! types are still useful (#IBOutlet is one of the last hold outs where ! types can be appropriate, at least as a matter of opinion.)
The other problem is that NSMutableArray is a sloppy type, and it creates a ton of headaches in Swift. It should only be used in very rare cases where a bridge to Objective-C requires it (this is exceedingly rare for NSMutableArray).
Convert cellDescriptors and cellsToDisplay to an appropriate Array type rather than NSMutableArray. "Appropriate" means "an array of whatever is actually in it." That means [Any] is not an appropriate type. If cellsToDisplay contains a bunch of Cell objects, then it should be [Cell].
NSMutableArray really doesn't tell much... Avoid force unwrapping and try to think of which types should the array be first. At first the array should have inside next array according to subscripting... so try to OverType it instead of NSMutableArray to type : [[Any]] but this option is really not safe at all!
Please provide more information about what the itemKey value should be... but overally my best tip is this:
func loadCellDescriptors(_ type: String) {
guard let cellDescriptors = PlistManager.sharedInstance.getValueForKey(itemKey)? as? [[String: Any]], let firstDescriptor = cellDescriptors[0]
else { return }
for someKeyInDict in 0..<cellDescriptor.count{
if let cellType = cellDescriptor[i][typeKey] as? String{
if(cellType != type) {
cellDescriptors[0][i].setValue(false, forKey: isVisibleKey)
}
}
}
This won't on 100% work, but will get you closer, I just tip that the value you are seraching is dictionary...
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)
}
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
I'm reading through the Swift documentation, looking at the section regarding type casting.
The documentation talks about getting an array of type [AnyObject] from Foundation frameworks stuff (what would be an NSArray * in Objective-C).
First, the documentation provides this example:
for object in someObjects {
let movie = object as Movie
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
Now, I want to change the example slightly, to a case where I don't know all the objects are of type Movie, so I'd do this:
for object in someObject {
if let movie = object as? Movie {
println("Movie: '\(movie.name', dir. \(movie.director)")
}
}
The documentation then provides an example of a better way to write the first loop:
for movie in someObjects as [Movie] {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
Where we downcast someObjects from an [AnyObject] to a [Movie] so we don't have to downcast within the loop.
And this got me thinking, can the array be option downcast as a whole?
if let someMovies = someObjects as? [Movie] {
for movie in someMovies {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
}
Does this work? And if so, how bad is this from a performance standpoint? How long would it take to check the type of every object in a 10,000 element array using the optional downcast?
I understand that the implications between this snippet and my previous optional downcast snippet are different. The first will iterate through every object and only attempt to print if the object is a Movie, where the second will only enter the loop if the array can be downcast to a [Movie] array, in which case it will either print all or none, but I can imagine there are situations where this would be preferable.
You've got it -- it works exactly like your example code:
let strings = ["Hi", "Hello", "Aloha"]
let anyObjects: [AnyObject] = strings
if let downcastStrings = anyObjects as? [String] {
println("It's a [String]")
}
// console says "It's a [String]"
No idea about performance, but I wouldn't assume that it will have to iterate through the full array to determine if a downcast is possible.
So I got curious, and ran a quick test with 100,000 simple values in a couple different [AnyObject] configurations, where I'm trying to downcast the array to a [String] vs. downcasting the individual elements:
// var anyObjects: [AnyObject] = [AnyObject]()
// filled with random assortment of Int, String, Double, Bool
Running test with mixed array
downcast array execution time = 0.000522
downcast elements execution time = 0.571749
// var actuallyStrings: [AnyObject] = [AnyObject]()
// filled with String values
Running test with all strings
downcast array execution time = 1.141267
downcast elements execution time = 0.853765
It looks like it's super fast to dismiss the mixed array as non-downcastable, since it just needs to scan until it finds a non-String element. For an array that it can downcast, it clearly has to crunch through the whole array, and takes much longer, although I'm not sure why it's not the same speed as looping through the array and manually checking each element.
Let's try this
var someObjects = [
NSString(),
NSUUID()
]
let uuids = someObjects as? NSUUID[]
uuids is nil
var someOtherObjects = [
NSUUID(),
NSUUID()
]
let all_uuids = someOtherObjects as? NSUUID[]
all_uuids is equal to someOtherObjects
So it looks like it does work. You can use the expression to test if all elements of the array are of the expected type but it will not filter the array to select only the expected type.