Moshe
Moshe

Reputation: 58097

Modify Swift Array in a Dictionary<String, AnyObject>

I've got a Dictionary that has AnyObjects in it. One of those AnyObjects is actually a Swift Array. I'm trying to modify the array, like so:

class MyClass {
    internal(set) var dictionary : Dictionary<String, AnyObject>

   init() {
       self.dictionary = [
          "displayName": "Foo",  //String : String
          "elements" : []  // String : Array
       ]
   }

    // ...

   func addNumber(number : Int) {
     // Something here
   }
}

Here's my unit test for this code:

func testAddNumber() {

  let foo = MyClass()

  let beforeCount = foo.dictionary["elements"].count

  foo.addNumber(10)

  let afterCount = foo.dictionary["elements"].count

  XCTAssert(beforeCount + 1 == afterCount, "Count was \(afterCount)")

}

I've tried pulling out the array and assigning it to a temporary variable, but then the item isn't actually added to the array keyed to elements.

var elements = (self.dictionary["elements"] as! [Int])
elements.append(10)

This code runs, but when I run my unit tests to check if the element was added, the test fails.

If I don't use an intermediate assignment, I get a compiler error about mutability:

Cannot use mutating member on immutable value of type [FormGroup]

Here's the code that caused it:

(self.dictionary["elements"] as! [Int]).append(10)

I tried using NSMutableArray instead of a Swift Array, and that works, but I'd like to know if there's a "pure" Swift solution. There have been posts that talk about Dictionaries with the <String, Array> type, but this requires that cast in the middle of it all, so I don't think they solve my problem.

What's the correct way to modify the array?

Upvotes: 0

Views: 1542

Answers (3)

Earl Grey
Earl Grey

Reputation: 7466

Get rid of the force-unwrapping. It will either expose an invalid argument in the method, or some other problem in related logic.

guard var elements = self.dictionary["elements"] as? [Int] else { return }
elements.append(10)

or

if var elements = self.dictionary["elements"] as? [Int] {
    elements.append(10)
}

I don't see how you can avoid doing optional un-wrapping here simply because you are trying to go from [AnyObject] to [Int] and there is no way to know if this will work in advance in runtime.

UPDATE:

You have added the full code so her's my update. Basically there are two major problems. 1) You are trying to avoid dealing with optionals. In swift you have to deal with them. Because methods dealing with Dictionary and Array types simply return optional values. 2) Your unit test is almost useless as it is. You should check many other things besides "something being not empty" to be sure the method works as intended.

Here's my playground take on your problem.

let elementsKey = "elements"

class MyClass {
    var dictionary : Dictionary<String, AnyObject>

    init() {
        self.dictionary = [
            "displayName": "Foo",  //String : String
            elementsKey : []  // String : Array
        ]
    }

    func addNumber(number : Int) {

        guard var mutableArray = self.dictionary[elementsKey] as? [Int] else { return }

        mutableArray.append(number)
        self.dictionary[elementsKey] = mutableArray
    }
}

func testAddNumber() {

    let number: Int = 10
    let foo = MyClass()

    let before = foo.dictionary[elementsKey] as? [Int]
    foo.addNumber(number)
    let after = foo.dictionary[elementsKey] as? [Int]

   print(before != nil)
   print(after != nil)
   print(before! != after!)
   print(before?.isEmpty)
   print(after?.isEmpty)
   print(after?.contains(number))
}

testAddNumber()

Upvotes: 1

Michael
Michael

Reputation: 427

The intention of "let" is that the thing is not changeable. Use var for the member variable if you want to change something "inside" it on any level

The reason you'll find old Objective-C stuff "working" here is that they couldn't change all those semantics and keep compatibility.

Upvotes: 0

Natan R.
Natan R.

Reputation: 5181

You are getting the warning when you don't use the assignment, because it considers it immutable. By declaring it var elements = ... it lets you call append(). If you change it to let, you will probably get the same warning.

I was going to suggest you to create the array again, and reassign the elements object of your dictionary, then I saw you declared it immutable. Then I got to ask myself: if you declared the dictionary itself as immutable, why are you trying to change one of its objects?

Another question is: why it even lets you declare elements using var and not enforcing let?

Upvotes: 0

Related Questions