Optional Chaining and Array in Swift

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:

  1. An optional of an array – that is, a variable that might contain an array of integers: [Int]?
  2. An array of optionals – that is, an array that contains variables that might be integers: [Int?]
  3. 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 Arrays 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



Leave a reply



Submit