Using generic arrays in swift - arrays

This should be pretty simple. I have a data source that always gives me UInt16s. I derive different data sets from this raw data and plot the results. Some of the derived data sets are Floats, some are UInt8s, and some are UInt16s.
I queue the derived data where it is later retrieved by my graphing classes.
Queues are arrays of arrays and look like this: [[UInt16]], [[Float]], or [[UInt8]].
I'm trying to make use of generics, but I get a compiler error when I try to append a generic-typed array to an array that is declared to be [[AnyObject]].
As I'm learning Swift, I keep bumping into this AnyObject / generic problem quite a bit. Any help/insight is appreciated.
class Base: NSObject {
var queue : [[AnyObject]] = Array()
func addtoQueue<T>(dataSet: [T]) {
queue.append(dataSet)
}
func removeFromQueue() -> [AnyObject]? {
return queue.removeAtIndex(0)
}
}
class DataSet1 : Base {
func getSomeData(rawData: [UInt16]) {
var result : [Float] = processRawData(rawData)
addToQueue(result)
}
}

It may be that you don't understand what AnyObject is. It is the protocol type automatically adopted by all classes. But Float, UInt16, and UInt8 are not classes; they are structs.
It may be that you meant [[Any]] as the type of your array. In that case, you don't need a generic. This works:
var queue : [[Any]] = Array()
func addToQueue(dataSet:[Any]) {
queue.append(dataSet)
}
let f = Float(1)
let i1 = UInt8(2)
let i2 = UInt16(3)
addToQueue([f])
addToQueue([i1])
addToQueue([i2])
If you insist on [[AnyObject]], you have two problems:
Your generic is too generic. Not everything in the universe is an AnyObject, so how can the compiler know that this thing will be an AnyObject? Write your generic like this:
func addToQueue<T:AnyObject>(dataSet:[T]) {
queue.append(dataSet)
}
Now the compiler knows that only something conforming to AnyObject will be used when calling this method. At that point, however, it is a little hard to see what your generic is for; you are not using T elsewhere, so just write a normal function:
func addToQueue(dataSet:[AnyObject]) {
queue.append(dataSet)
}
The second problem is that you will still have to convert (as Drew's answer tells you), because there is no automagic bridging between, say, a UIInt16 and an AnyObject. Float is _ObjectiveCBridgeable, but UInt16 and UInt8 are not.
var queue : [[AnyObject]] = Array()
func addToQueue(dataSet:[AnyObject]) {
queue.append(dataSet)
}
let f = Float(1)
let i1 = UInt8(2)
let i2 = UInt16(3)
addToQueue([f])
addToQueue([NSNumber(unsignedChar: i1)])
addToQueue([NSNumber(unsignedShort: i2)])

What you'll need to do is 'box' up the underlying value. You could accomplish this using a protocol which exposed a setter/getter and an enum property of the underlying type that you can 'switch' on. Alternatively, you could see if you can make Foundation's built in NSNumber work for you. It's doing exactly that: boxes up any number of numerical types for you to store and retrieve later on:
var queue : Array<NSNumber> = []
queue.append(NSNumber(int: 1))
queue.append(NSNumber(double: 2.5))
queue.append(NSNumber(float: 3.5))
var types : Array<String> = []
for item in queue{
println("number type: \(item)")
}

Related

Swift: subclass NSMutableArray

I'm trying to subclass NSMutableArray in Swift to provide bridge functionality for a Swift array to some old Objective-C codebase. However, I can't find a functional example and all my attempts have failed so far. This is the closest I got to a functional example:
import UIKit
class MyArray: NSMutableArray {
var array: [Int]?
// Adding an initializer triggers the error: 'required' initializer 'init(arrayLiteral:)' must be provided by subclass of 'NSArray'
// override init() {
// self.array = []
// super.init()
// }
static func makeArray() -> MyArray {
let array = MyArray()
array.array = []
return array
}
override var count: Int {
get {
if let count = array?.count {
return count
}
return 0
}
}
override func insert(_ anObject: Any, at index: Int) {
print("insert called")
}
override func removeObject(at index: Int) {
print("removeObject called")
}
override func add(_ anObject: Any) {
print("Trying to add \(anObject)")
if let newInt = anObject as? Int {
array?.append(newInt)
}
}
override func removeLastObject() {
print("removeLastObject called")
}
override func replaceObject(at index: Int, with anObject: Any) {
print("replaceObject called")
}
}
func append42(array: NSMutableArray) {
array.add(42)
}
let myArray = MyArray.makeArray()
print("Before: \(myArray.array)")
myArray.array?.append(42)
// The following two lines generate a SIGABRT
//myArray.add(42)
//append42(array: myArray)
print("After: \(myArray.array)")
However, using the add method always triggers a SIGABRT. I'm using XCode 12.4 on Catalina.
EDIT (for clarifications): I know that Swift bridges Arrays to NSArrays. However, I need to pass a reference to a Swift array to some Objective-C code accepting an NSMutableArray and possibly making updates to the array.
Essentially I'm adding some Swift code to an existing project, and I'd like to use Swift typed arrays in the new code, but I still to pass these arrays to the old code. I know that I can make an NSMutableArray copy of the Swift array, pass this copy to the old code, then copy it back to the Swift array but it's rather convoluted and hardly maintainable. What I'm tried to do is encapsulating a reference to a Swift array into an 'NSMutableArray' subclass so I can transparently use this subclass with the legacy code.
Are you aware that:
When importing the Foundation framework, the Swift overlay provides value types for many bridged reference types. Many other types are renamed or nested to clarify relationships.
And specifically that :
Class clusters that include immutable and mutable subclasses, like NSArray and NSMutableArray, are bridged to a single value type.
So you can use Array in place of NSArray and if you want a NSMutableArray is also a Swift Array (but a var).
Same thing applies for Dictionary and NSDictionary.
Disclaimer
I would probably not use that in production code and although sublassing NSArray and NSMutableArray is not forbidden, documentation says :
There is typically little reason to subclass NSMutableArray.
And this should be reason enough to consider another solution IMHO
Edit
After reading this answer
I decided to check NSMutableArray documentation:
Methods to Override
NSMutableArray defines five primitive methods:
insert(_:at:)
removeObject(at:)
add(_:)
removeLastObject()
replaceObject(at:with:)
In a subclass, you must override all these methods. You must also override the primitive methods of the NSArray class.
So I checked NSArray documentation too:
Any subclass of NSArray must override the primitive instance methods count and object(at:). These methods must operate on the backing store that you provide for the elements of the collection. For this backing store you can use a static array, a standard NSArray object, or some other data type or mechanism. You may also choose to override, partially or fully, any other NSArray method for which you want to provide an alternative implementation.
and now, with:
#objc class MyArray: NSMutableArray {
var array: [Int] = []
static func makeArray() -> MyArray {
let array = MyArray()
array.array = []
return array
}
override var count: Int {
array.count
}
override func object(at index: Int) -> Any {
array[index]
}
override func insert(_ anObject: Any, at index: Int) {
print("insert called")
if let newInt = anObject as? Int {
array.insert(newInt, at: index)
}
}
override func removeObject(at index: Int) {
array.remove(at: index)
print("removeObject called")
}
override func add(_ anObject: Any) {
print("Trying to add \(anObject)")
if let newInt = anObject as? Int {
array.append(newInt)
}
}
}
func append42(array: NSMutableArray) {
array.add(42)
}
I have :
let myArray = MyArray.makeArray()
print("Before: \(myArray.array)")
myArray.array.append(42) // [42]
// The following two lines generate a SIGABRT
myArray.add(42) // [42, 42]
append42(array: myArray)
print("After: \(myArray.array)")
// Before: []
// Trying to add 42
// Trying to add 42
// After: [42, 42, 42]
Fun facts
I tried your code in a playground and reproduced the crash. I tried in a XCTestCase and it did not crash
calling super.add(_:) in add(_:) will trigger insert(_:at:)
NSMutableArray conformance to ExpressibleByArrayLiteral is probably written in an extension (which makes perfect sense since NSMutableArray is an Objective-C class) and so overriding init() forces you to override init(arrayLitteral:) and the compiler says Overriding declarations in extensions is not supported. :
Automatic Initializer Inheritance
Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.
We should generally follow the Open-Closed Principle, OCP, namely that classes are open for extension and closed for modification. This attempt to subclass NSMutableArray, and replace a few key methods, violates OCP. It is likely to manifest all sorts of weird behaviors.
If you really want to subclass, you will want to override all of the primitive methods of both NSArray and NSMutableArray. Theoretically you could examine your Objective-C code and see what methods it is calling, but this is a very fragile approach. I would not advise this pattern.
Personally, if I was trying to integrate with some Objective-C code that required NSMutableArray, I would stick with Swift arrays and create a NSMutableArray before I called my Objective-C library, and then copy it back to an array (consistent with the value semantics of Swift arrays).
Consider:
#implementation Foo
- (void)bar:(NSMutableArray *)array {
[array addObject:#42];
}
#end
I would shim this for a Swift Array:
extension Foo {
func bar(_ values: inout [Int]) {
let array = NSMutableArray(array: values)
bar(array)
values = array as! [Int]
}
}
Then I could do:
var values = [1, 2]
values.append(3)
let foo = Foo()
foo.bar(&values)
print(values) // 1, 2, 3, 42
But I would avoid avoid subclassing NSMutableArray simply because I happened to be calling some Objective-C routine that used NSMutableArray. I would only do that if benchmarking (or other considerations) deemed that absolutely necessary.
Let us assume for a second that you absolutely needed to stick with NSMutableArray (e.g., you had millions of records and are calling this Objective-C routine constantly). In that case, I still would not advise subclassing NSMutableArray, but rather just use NSMutableArray directly in my Swift code. If I wanted to retrieve Int values from my NSMutableArray and didn't want to cast all over the place, I'd add an extension to NSMutableArray:
extension NSMutableArray {
func append(integer value: Int) {
add(value)
}
func intValue(at index: Int) -> Int {
return self[index] as! Int
}
}
Then you could do:
let values: NSMutableArray = [1, 2]
values.append(integer: 3)
let foo = Foo()
foo.bar(values)
print(values) // 1, 2, 3, 42
let value = values.intValue(at: 3)
print(value) // This is `Int` value of 42
Now, I know this is not the answer you were hoping for, namely that you did not want to “lose the benefits of Swift typed Arrays” while still using this Objective-C legacy code. But at least these two methods give you type-safe setting and retrieval of integer values.

sorting array of objects swift 4

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.

Type 'Any' has no subscript members - NSMutableArray

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...

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)
}

How can I add a dictionary to an array using Swift?

I'm trying to add a dictionary containing properties of people into an array, but it's not working. I'm getting the following error:
[(Any)] is not identical to UInt8
Here is my code:
var people = [Any]()
class Database {
class func addPerson(dict: [String : Any]) -> Void {
people += dict
}
}
Database.addPerson(["name" : "Fred"])
The += operator on Array corresponds to Array.extend which takes a SequenceType and not an individual element. If you wrap dict in an Array, then += will work:
people += [dict]
However, it's simpler and probably more efficient to use the append function instead:
people.append(dict)
Side note:
I'm not sure why you're using Any as the Array's element type and the Dictionary's value type (and maybe you have a good reason), but you should typically avoid that if at all possible. In this case I'd declare dict as [String: String] and people as [[String: String]]:
var people = [[String: String]]()
class Database {
class func addPerson(dict: [String : String]) -> Void {
people.append(dict)
}
}
Database.addPerson(["name" : "Fred"])
If you need to be able to store multiple types in your Dictionary, there are a few ways you can do that.
Use an NSDictionary directly.
Declare the Dictionary as [String: AnyObject].
Use an enum with associated values as the value type (this is usually the best option in Swift if you only need to support a few types because everything stays strongly typed).
Quick example of using an enum (there are quite a few examples of this technique in other SO questions):
enum DictValue {
case AsString(String)
case AsInt(Int)
}
var people = [[String: DictValue]]()
class Database {
class func addPerson(dict: [String : DictValue]) -> Void {
people.append(dict)
}
}
Database.addPerson(["name" : DictValue.AsString("Fred")])
Database.addPerson(["name" : DictValue.AsInt(1)])
There is not built in operator for that I guess. You can use:
people.append(dict)
// or
people += [dict as Any]

Resources