Why is it that I cannot mutate an implicitly unwrapped optional variable?
Here is a short example the reproduces the issue:
With Array
var list: [Int]! = [1]
list.append(10) // Error here
Immutable value of type '[Int]' only has mutating members named 'append'
With Int
var number: Int! = 1
number = 2
number = 2 + number
number += 2 // Error here
Could not find an overload for '+=' that accepts the supplied arguments
Because the way you are trying to mutate them is by mutating the values (which are immutable) instead of mutating the var.
In Swift value types are immutable. All and always.
Mutation is not a mutation of the value, it's a mutation of the variable that contains the value.
In the case of the Int, the += operator gets a structure on the left and an Int on the right, and it cannot add a structure to an int.
In the case of the Array the append is a mutating member. But it's being invoked on an immutable value that is not directly stored in a variable. It can only operate on values that are directly stored in a variable (which is what makes them mutable: the fact that they are stored in a variable. They are not really mutable, the variable is).
Update:
This has been fixed in Xcode Beta 5 with one small caveat:
var list: [Int]! = [1]
list.append(10)
var number: Int! = 1
number! += 2
number += 2 // compile error
The array works as expected, but it seems that right now the integer still requires an explicit unwrap to allow using +=
Currently, this is just the nature of Optionals (whether implicitly unwrapped or not). The unwrap operator returns an immutable value. This is likely to be fixed or a better solution will be provided in the future.
The only way around it for now is to wrap the array in a class:
class IntArray {
var elements : [Int]
}
Related
I am building a Tic Tac Toe game with an AI using Xcode 8 and Swift. Here are the relevant variables I am using that are contributing to the error:
var allSpaces: Set<Int> = [1,2,3,4,5,6,7,8,9]
var playerOneMoves = Set<Int>()
var playerTwoMoves = Set<Int>()
var nextMove: Int? = nil
Inside a function defining how the AI will play there are these variables:
var count = 0
let spacesLeft = allSpaces.subtract(PlayerOneMoves.union(playerTwoMoves))
The latter results in the compiler warning:
Constant 'spacesLeft" inferred to have type '()', which may be unexpected
There is an if statement just below that says:
if allSpaces.subtract(playerOneMoves.union(playerTwoMoves)).count > 0 {
nextMove = spacesLeft[spacesLeft.startIndex.advancedBy(Int(arc4random_uniform(UInt32(spacesLeft.count))))]
}
The condition gives the following error:
Value of tuple type '()' has no member 'count'
The statement gives the following error:
Type '()' has no subscript members
I am struggling to find a solution.
subtract modifies Set in place and doesn't return a value, you want to use subtracting
For the first warning, subtract returns Void, so use subtracting:
let spacesLeft = allSpaces.subtracting(playerOneMoves.union(playerTwoMoves))
For the second error, advancedBy is deprecated, you may change like this:
if spacesLeft.count > 0 {
nextMove = spacesLeft[spacesLeft.index(spacesLeft.startIndex, offsetBy: Int(arc4random_uniform(UInt32(spacesLeft.count))))]
}
Set.subtract is a mutating function, so it modifies the Set in place and its return value is Void, which is just a type alias for an empty tuple, (), hence the warning.
You should call Set.substracting, which is the non-mutating version of subtract and returns Set<Set.Element>.
The subtract(_:) function is a mutating function so it will mutate the Set your using to call the function.
From Apple Docs:
subtract(_:)
Removes the elements of the given set from this set.
The reason you're getting the errors is because this function returns Void which in Swift is a typealias for an empty tuple(from Swift's source code). Since Void has no subscripts nor count property/variable you get those errors.
Maybe you should take a look at the subtracting(_:) function, which returns a different Set.
From Apple Docs:
subtracting(_:)
Returns a new set containing the elements of this set that do not occur in the given set.
I am attempting to sort a mutable array in Swift 3.1.1, but get the same error every time:
No 'sort' candidates produce the expected contextual result type 'NSMutableArray'.
Is there a way to sort a mutable array (Ints only) in ascending order?
In my code, elements from options are being removed. Removed (the array) is adding the removed elements. At the end of the code I am attempting to add the elements from the removed array back to options and sort it.
// set up tiles
var options = NSMutableArray()
var removed = NSMutableArray()
for i in 1...49 {
options.add(i as Int)
print("options\(options.count)")
}
for i in 1...49 {
print("i = \(i)")
options.remove(i)
let tilea: Int = options[Int(arc4random_uniform(UInt32(options.count)))] as! Int
options.remove(tilea)
let tileb: Int = options[Int(arc4random_uniform(UInt32(options.count)))] as! Int
options.remove(tileb)
removed.add([i, tilea, tileb])
print(options.count)
if options.count < 20 {
options.add(removed)
options = options.sort {
$0 < $1
}
}
}
As already mentioned, in Swift you should really be using the Array<T> for this (aka, [T]) instead of NSMutableArray. For instance:
var options = [Int]()
when adding elements to it, use append (and, by the way, you can drop the type cast as well):
options.append(i)
options.append(contentsOf: [i, j, k])
finally, when sorting the array, use the sort function (it doesn't return a value; the array is sorted in-place):
options.sort()
and you don't need even to provide a comparation function since integers conform to the Comparable protocol.
NSMutableArray, among other Objective C types, was implicitly bridged to/from its Swift counterparts. In a move to lessen peoples (usually unnecessary) reliance on these Objective C types, this implicit bridging has been changed in Swift 3, and now needs an explicit type coercion (e.g nsArray as [Int])
I am creating at launch Dictionaries with var because I will modify them later when user does something. Dictionaries are added inside an Array in a singleton class to be used in multiple places but I get the warning "Variable 'variableName' was never mutated, consider...."
in the place I am creating them
If I make them with let and when I get object form array to modify it if I take it from array with var, no crash, no warning, no nothing...
What is the explanation for this?
UPDATE:
My Singleton Class:
class Config {
static let sharedInstance = Config()
var array_shapes: Array<Dictionary<NSObject,AnyObject>> = Array()
func createInitialShapeArray(){
var avion = createShapeDictionaryFor("Avion", objectName: "Avion", badgeStatus: "0", shapeImageName: "shape_avion");
//.......more objects like avion
array_shapes = [avion,//.....the other objects];
}
func createShapeDictionaryFor(objectID:String, objectName:String, badgeStatus:String, shapeImageName:String) -> Dictionary<NSObject,AnyObject>{
var dict: Dictionary<NSObject,AnyObject> = [:]
dict["objectID"] = objectID
dict["objectName"] = objectName
dict["badgeStatus"] = badgeStatus
dict["shapeImageName"] = shapeImageName
return dict;
}
}
And when I am mutating dictionaries (In main class):
#IBAction func btnPressed_done(sender:UIButton){
pennyPincherGestureRecognizer.recognize();
screenShotMethod()
var dict = Config.sharedInstance.array_shapes[Config.sharedInstance.currentShapeIndex] as Dictionary<NSObject,AnyObject>
dict["badgeStatus"] = "1"
self.initNextShape()
}
var avion has the warning "Variable 'variableName' was never mutated, consider...."
It is not an error trough, it's a warning and I was curious if I could silence them or what can I do to make them dissappear
Facts
You are declaring avion as a local variable of the method createInitialShapeArray
You are not mutating avion in the scope where it is defined
avion is a Dictionary therefore a Struct (value type rules are applied)
Conclusion
There is no need to declare avion as a variable, it should be a constant.
Please note that where you write
array_shapes = [avion, ...]
you are creating a copy of avion (because it's a Dictionary).
So if you change the value inside array_shapes you are changing another value.
Therefore, at the end of the day, you are not mutating avion... and the compiler is right, it should be a constant.
Example
Please consider the following code
func foo() {
var dict = [1: "One"] // <-- Compiler warning
var anotherDict = dict
anotherDict[2] = "Two"
}
Here I am getting the same compiler warning
Variable 'dict' was never mutated; consider changing to 'let' constant
This happens because I am changing anotherDict that is not just another reference to the same value, it is actually a totally different value. This is the rule with Struct(s) and Enum(s) because they are Value Types.
Hope this helps.
In Swift arrays and dictionaries are declared as struct so when you pass them to other function or use them in assignments their value is copied and not passed as reference the same way it's done for classes, this means that when you pass avion to the append() function of your array you pass a copy of the dictionary so the original variable is never mutated.
The same things happens when you try to modify on dictionary in the array thus copying the dictionary of your interest in dict: you aren't modifying the array inside your shared instance but the local variable dict.
I am declaring a array:
var array:[String] = []
assigning values:
array.append(uniqueId as String)
and then pass it to a function:
static var levels_array : [String] = []
class func setLevelsArray(arr:[String])
{
GamePrefrences.levels_array = arr
println(GamePrefrences.levels_array)
}
My problem is that when i print the array i get:
[Optional(88868658), Optional(112051080), Optional(95274974)]
Why is that optional? i only want Strings in the array so why is "optional" added?
I come from Java can someone explain why optional is created and how to remove it
Very likely the actual strings are "Optional(88868658)", etc. You have probably created this strings from an optional accidentally. In the above code, you definitely have an array of strings, not an array of optionals, and when you print an array of strings, they don't include the double-quotes (which can be confusing).
It may is happening here:
array.append(uniqueId as String)
What type is uniqueId? as is not used to convert types. It's just used to cast them if possible (though this should really fail if that's the case; so I'm suspecting you're doing a string interpolation somewhere).
The Strings themselves are not optional -- the uniqueId is. Basically, optionals mean that a stored value may or may not exist.
I'd recommend using an if let statement to unwrap your optional uniqueID before converting it into a String so the program in fact knows that the optional exists before storing it as a String, ex:
if let uniqueId = uniqueId {
array.append(uniqueId as String)
}
As i found in documentation, arrays declared by var are mutable by default
var A: Int[] = [1,2,3]
A += 4
A //[1,2,3,4]
But i never found reason, why are optional arrays immutable (should they be and why). These two examples illustates my concern:
var A: Int[]? = [1,2,3]
A! += 4 // Error: could not find operator += etc. - .append does not work as well
A
this gives error, but this one does not
var A: Int[]? = [1,2,3]
var B = A!
B += 4
A = B
A // [1,2,3,4]
and performs as expected. Is there any particular reason, why it cannot be done in one line, like in previous example? As far as i see from that, underlaying array is like constructed by let, but it does not makes sense to me yet.
Your var A is of type Int[]? whilst your var B is not - it is implicitly of type Int[], since you set it equal to the unwrapped value of A.
B can therefore be appended to (it's a var array), whilst A cannot be (it's a var, but not an array - it's an optional wrapping an array), but, as A is Int[]? you can assign the modified B to it. To append to an optional array you would have to "unwrap" it as an LHS like
A! += 4 // Doesn't work