Optional chaining and Array in swift
Arrays in Swift are structs and structs are value types.
Optionals in Swift are actually enums (Optional<T>
or ImplicitlyUnwrappedOptional<T>
).
When you are unwrapping an optional (implicitly or explicitly) of a value type, what you get is actually a constant copy of the struct. And you can't call mutating
methods on a constant struct.
Executing objects?.insert(Object(), atIndex:0)
basically means this:
if let tmp = objects {
tmp.insert(Object(), atIndex:0)
}
As a workaround, you need to assign the unwrapped value to a variable and then assign the variable back to your optional property. That's how value types work.
This is reproducible for any struct, not only Arrays:
struct S {
var value: Int = 0
}
var varS: S = S()
varS.value = 10 //can be called
let constS: S = S()
constS.value = 10 //cannot be called - constant!
var optionalS: S? = S()
optionalS?.value = 10 //cannot be called, unwrapping makes a constant copy!
//workaround
if optionalS {
var tmpS = optionalS!
tmpS.value = 10
optionalS = tmpS
}
Some relevant discussion here: https://devforums.apple.com/thread/233111?tstart=60
Swift: Optional chaining for optional subscripts
let value = key.flatMap { map[$0] }
would to the trick, using the
/// Returns `nil` if `self` is nil, `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
method from struct Optional
.
Alternatively, you can wrap that into a custom subscript method
extension Dictionary {
subscript(optKey : Key?) -> Value? {
return optKey.flatMap { self[$0] }
}
}
and the simply write
let value = map[key]
To avoid confusion with the "normal" subscript method, and to make
the intention more clear to the reader of your code, you can define
the subscript method with an external parameter name:
extension Dictionary {
subscript(optional optKey : Key?) -> Value? {
return optKey.flatMap { self[$0] }
}
}
let value = map[optional: key]
Swift 3 use optional chaining in map or filter
You should use flatMap
(compactMap
in Swift 4) to filter out nils:
let sectionNames = pois.flatMap { $0.IconName }
optional chaining in Swift 3: why does one example work and not the other?
The problem in a nutshell ? the function for fractions reports a fault whereas the function for decimal numbers fails to detect bad input.
The function for decimal numbers does detect “bad” input. However, "700"
does not contain "."
, and you only call processDecimal(s:)
if the string does contain "."
. If the string doesn't contain "."
and also doesn't contain "/"
, doubleFromDecimalOrFraction(s:)
doesn't call any function to parse the string.
Safe (bounds-checked) array lookup in Swift, through optional bindings?
Alex's answer has good advice and solution for the question, however, I've happened to stumble on a nicer way of implementing this functionality:
extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
Example
let array = [1, 2, 3]
for index in -20...20 {
if let item = array[safe: index] {
print(item)
}
}
Swift Optional Array Index Error
There are 3 possible things here that you might be conflating:
- An optional of an array – that is, a variable that might contain an array of integers:
[Int]?
- An array of optionals – that is, an array that contains variables that might be integers:
[Int?]
- Subscripts that return an optional because maybe that subscript isn't valid - that is, a subscript that might return an integer if the index is valid:
subscript(index) -> Int?
.
What you've got in your example is the first case – an optional of an array. That is, either testArray
is nil
, or it's a valid array containing zero or more elements.
To access anything inside an optional you have to first unwrap it. You can either force-unwrap it with !
(Try not to do this unless you have a really good reason – no not that reason. No not that one either). Or you can conditionally unwrap it, either using if let
, or using one of the operators like ?
, ?.
, ??
etc. In this case, testArray?[9]
means, "if testArray is nil
, then nil
, otherwise {Some whatever is at position 9}
. But there is no value at position 9 – this is only a 3-element array. So you get a runtime assertion.
(Another way of writing testArray?[9]
would be testArray.map { $0[9] }
, where inside the block the array is unwrapped and valid, but if it’s nil
the block never gets executed. But the result is still that you try to access the 9th element of the array, and that's not allowed and you get a runtime error)
The second case, an array of optional integers, would mean that you could access testArray[1]
and you'd get back {Some 2}
, because the array holds optionals. But again, you can't access the 9th element, because there is no 9th element.
Finally the third case is where calling subscript gives you back an optional value, and if it's not a valid index, you get back a nil
. This seems to be what you were expecting. Swift arrays do not do this. Swift dictionaries do, if you are looking up by key. If the key has a value, you get back {Some value}
, if not, you get nil
. The shorthand justification for this is that dictionaries contain sparse data, whereas arrays contain dense data.
If you're interested, I wrote posts about both the pro and anti argument for making arrays return optionals from subscript. But for now, like the other answers say, you have to check your bounds before accessing elements. You might want to try some of the helper methods like first
, last
, find
, as well as map
and filter
etc rather than accessing elements directly, as this makes many of these problems go away.
(another interesting thing to think about – all 3 cases above can be combined together. So you could have an optional dictionary of optionals, that returned optionals when you accessed it. If you're still hazy on all this, try playing around with that in a playground :)
How to loop through an optional collection
You have a couple of options. You can provide an empty array as the default value for elements
in a for ... in
loop:
let elements: [Element]?
for element in elements ?? [] {
}
Or you can use forEach
and optional chaining on elements
elements?.forEach { element in
}
Bear in mind that optional collections are an anti-pattern in Swift. You can represent the lack of value using an empty collection, so wrapping a collection in an optional doesn't provide any extra value, while complicating the interface.
Clarification for Accessing Subscripts of Optional Type in Swift
I think the confusion here is that the dictionary testScores
is non-optional, but the value testScores["Dave"]
is optional. The reason is that any time you ask for a value from a dictionary, it might be there...or it might not. Returning from a dictionary is an inherently optional operation. Consider if you had said, testScores["Fred"]
--this would have returned nil
. Since it is possible to return an object, or possible to return nil
, subscripting a Dictionary
of Array
s returns an optional Array
. The return type ([Int]?
), therefore, differs from the value type ([Int]
).
The second example you give is subtly different. Rather than the return type being optional, in your second example, the element itself is optional. This means that you could have something like this:
let array1 = [0, 1, 2]
let array2: [Int]? = nil
let dict = ["Fred": array1, "Wilma": array2] // [String: [Int]?]
In that case, you actually have two layers of optionals (an optional optional array of ints, [Int]??
, and would need to access an element like this:
let x = dict["Fred"]??[0]
Sort an array of optional items that holds yet another optional
Your sort function could use a combination of optional chaining and the nil
coalescing operator:
sort(&array) {
(item1, item2) -> Bool in
let t1 = item1?.dateCompleted ?? NSDate.distantPast() as! NSDate
let t2 = item2?.dateCompleted ?? NSDate.distantPast() as! NSDate
return t1.compare(t2) == NSComparisonResult.OrderedAscending
}
This would sort the items on the dateCompleted
value, and all items that
are nil
and items with dateCompleted == nil
are treated as "in the distant past"
so that they are ordered before all other items.
Update for Swift 3 (assuming that dateCompleted
is a Date
):
array.sort { (item1, item2) -> Bool in
let t1 = item1?.dateCompleted ?? Date.distantPast
let t2 = item2?.dateCompleted ?? Date.distantPast
return t1 < t2
}
Related Topics
Swift Casting Generic to Optional with a Nil Value Causes Fatalerror
Apple Mach-O Linker Error (Static, Not Ld)
Swift 3: Convert a String to an Array
Cast While Looping Over Dictionary in Swift
Xcode 9.3 Watchkit Crash on Wkinterfacebutton Tap
List Inside Scrollview Is Not Displayed on Watchos
Swift: Convert Byte Array into Ciimage
Bar Button Item Tint Color Not Working
How to Insert a Row in Tableview Without Using Reloaddata Function in Cocoa
How to Get Unsaferawpointer on The Swift Object
How to Provide Default Implementation of an Objective-C Protocol in a Swift Protocol Extension
Aws Cognito Credentialsprovider.Login Always Shows Nil (Swift)
Force Refresh on Another Viewcontroller Component with Swift
Problem with Frameworks in Command Line Tool
Siblings Relationship Between Same Models in Vapor
Why Are Implicitly Unwrapped Variables Now Printing Out as Some(...) in Swift 4.1
Could Not Find an Overload for "Init" That Accepts The Supplied Arguments (Swift)