Why does using dynamicType on a force unwrapped nil optional value type work?
This is indeed a bug, which has been fixed by this pull request, which should make it into the release of Swift 4.2, all things going well.
If anyone’s interested in the seemingly bizarre requirements to reproduce it, here’s a (not really) brief overview on what was happening...
Calls to the standard library function type(of:)
are resolved as a special case by the type-checker; they’re replaced in the AST by a special “dynamic type expression“. I didn't investigate how it's predecessor dynamicType
was handled, but I suspect it did something similar.
When emitting an intermediate representation (SIL to be specific) for a such an expression, the compiler checks to see if the resulting metatype is “thick” (for class and protocol-typed instances), and if so emits the sub-expression (i.e the argument passed) and gets its dynamic type.
However, if the resulting metatype is “thin” (for structs and enums), the compiler knows the metatype value at compile time. Therefore the sub-expression only needs to be evaluated if it has side effects. Such an expression is emitted as an “ignored expression”.
The problem was with the logic in emitting ignored expressions that were also lvalues (an expression that can be assigned to and passed as inout
).
Swift lvalues can be made up of multiple components (for example, accessing a property, performing a force unwrap, etc.). Some components are “physical”, meaning that they produce an address to work with, and other components are “logical”, meaning that they comprise of a getter and setter (just like computed variables).
The problem was that physical components were incorrectly assumed to be side-effect free; however force unwrapping is a physical component and is not side effect free (a key-path expression is also a non-pure physical component).
So ignored expression lvalues with force unwrap components will incorrectly not evaluate the force unwrapping if they’re only made up of physical components.
Let’s take a look at a couple of cases that currently crash (in Swift 4.0.3), and explain why the bug was side-stepped and the force unwrap was correctly evaluated:
let foo: String? = nil
print(type(of: foo!)) // crash!
Here, foo
is not an lvalue (as it’s declared let
), so we just get its value and force unwrap.
class C {} // also crashes if 'C' is 'final', the metatype is still "thick"
var foo: C? = nil
let x = type(of: foo!) // crash!
Here, foo
is an lvalue, but the compiler sees that the resulting metatype is “thick”, and so depends on the value of foo!
, so the lvalue is loaded, and the force unwrap is therefore evaluated.
Let’s also take a look at this interesting case:
class Foo {
var bar: Bar?
}
struct Bar {}
var foo = Foo()
print(type(of: foo.bar!)) // crash!
It crashes, but it won’t if Foo
is marked as final
. The resulting metatype is “thin” either way, so what difference does Foo
being final
make?
Well, when Foo
is non-final, the compiler cannot just refer to the property bar
by address, as it may be overridden by a subclass, which may well re-implement it as a computed property. So, the lvalue will contain a logical component (the call to bar
’s getter), therefore the compiler will perform a load to ensure the potential side effects of this getter call are evaluated (and the force unwrap will also be evaluated in the load).
However when Foo
is final
, the property access to bar
can be modelled as a physical component, i.e it can be referred to by address. Therefore the compiler incorrectly assumed that because all the lvalue's components are physical, it could skip evaluating it.
Anyway, this issue is fixed now. Hopefully someone finds the above ramble useful and/or interesting :)
Why can't I call Optional methods on an optional-chained type?
This is an operator precedence issue. You need to add parentheses to change the order:
let bar = (foo?.count).map { $0 * 2 }
Consider the case of a Collection like String rather than an Int:
let foo: String? = "ABC"
let bar = foo?.uppercased().map(\.isLowercase) // [false, false, false]
In this case, it's sensible that the .map
applies to the String rather than to the Optional. If the precedence were the other way, then you'd need a ?.
at every step (or a lot of parentheses :D)
Why does Combine assign crash when writing to an optional property?
Definitely a bug in Combine, it’s accessing the value when assigning it. If you change it to be a regular Optional rather than an implicitly unwrapped one (change !
to ?
) it’ll work fine. For the most part, you should avoid implicitly unwrapped Optionals except if they’re absolutely necessary for design constraints.
swift type(of:) - show nonoptional type of optional
You can unwrap the Optional with the !
operator:
print(String(describing: type(of: check!)))
When should I compare an optional value to nil?
It is almost always unnecessary to check if an optional is not nil
. Pretty much the only time you need to do this is if its nil
-ness is the only thing you want to know about – you don’t care what’s in the value, just that it’s not nil
.
Under most other circumstances, there is a bit of Swift shorthand that can more safely and concisely do the task inside the if
for you.
Using the value if it isn’t nil
Instead of:
let s = "1"
let i = Int(s)
if i != nil {
print(i! + 1)
}
you can use if let
:
if let i = Int(s) {
print(i + 1)
}
You can also use var
:
if var i = Int(s) {
print(++i) // prints 2
}
but note that i
will be a local copy - any changes to i
will not affect the value inside the original optional.
You can unwrap multiple optionals within a single if let
, and later ones can depend on earlier ones:
if let url = NSURL(string: urlString),
data = NSData(contentsOfURL: url),
image = UIImage(data: data)
{
let view = UIImageView(image: image)
// etc.
}
You can also add where
clauses to the unwrapped values:
if let url = NSURL(string: urlString) where url.pathExtension == "png",
let data = NSData(contentsOfURL: url), image = UIImage(data: data)
{ etc. }
Replacing nil
with a default
Instead of:
let j: Int
if i != nil {
j = i
}
else {
j = 0
}
or:
let j = i != nil ? i! : 0
you can use the nil-coalescing operator, ??
:
// j will be the unwrapped value of i,
// or 0 if i is nil
let j = i ?? 0
Equating an optional with a non-optional
Instead of:
if i != nil && i! == 2 {
print("i is two and not nil")
}
you can check if optionals are equal to non-optional values:
if i == 2 {
print("i is two and not nil")
}
This also works with comparisons:
if i < 5 { }
nil
is always equal to other nil
s, and is less than any non-nil
value.
Be careful! There can be gotchas here:
let a: Any = "hello"
let b: Any = "goodbye"
if (a as? Double) == (b as? Double) {
print("these will be equal because both nil...")
}
Calling a method (or reading a property) on an optional
Instead of:
let j: Int
if i != nil {
j = i.successor()
}
else {
// no reasonable action to take at this point
fatalError("no idea what to do now...")
}
you can use optional chaining, ?.
:
let j = i?.successor()
Note, j
will also now be optional, to account for the fatalError
scenario. Later, you can use one of the other techniques in this answer to handle j
’s optionality, but you can often defer actually unwrapping your optionals until much later, or sometimes not at all.
As the name implies, you can chain them, so you can write:
let j = s.toInt()?.successor()?.successor()
Optional chaining also works with subscripts:
let dictOfArrays: ["nine": [0,1,2,3,4,5,6,7]]
let sevenOfNine = dictOfArrays["nine"]?[7] // returns {Some 7}
and functions:
let dictOfFuncs: [String:(Int,Int)->Int] = [
"add":(+),
"subtract":(-)
]
dictOfFuncs["add"]?(1,1) // returns {Some 2}
Assigning to a property on an optional
Instead of:
if splitViewController != nil {
splitViewController!.delegate = self
}
you can assign through an optional chain:
splitViewController?.delegate = self
Only if splitViewController
is non-nil
will the assignment happen.
Using the value if it isn’t nil
, or bailing (new in Swift 2.0)
Sometimes in a function, there’s a short bit of code you want to write to check an optional, and if it’s nil
, exit the function early, otherwise keep going.
You might write this like this:
func f(s: String) {
let i = Int(s)
if i == nil { fatalError("Input must be a number") }
print(i! + 1)
}
or to avoid the force unwrap, like this:
func f(s: String) {
if let i = Int(s) {
print(i! + 1)
}
else {
fatalErrr("Input must be a number")
}
}
but it’s much nicer to keep the error-handling code at the top by the check. This can also lead to unpleasant nesting (the "pyramid of doom").
Instead you can use guard
, which is like an if not let
:
func f(s: String) {
guard let i = Int(s)
else { fatalError("Input must be a number") }
// i will be an non-optional Int
print(i+1)
}
The else
part must exit the scope of the guarded value, e.g. a return
or fatalError
, to guarantee that the guarded value will be valid for the remainder of the scope.
guard
isn’t limited to function scope. For example the following:
var a = ["0","1","foo","2"]
while !a.isEmpty {
guard let i = Int(a.removeLast())
else { continue }
print(i+1, appendNewline: false)
}
prints 321
.
Looping over non-nil items in a sequence (new in Swift 2.0)
If you have a sequence of optionals, you can use for case let _?
to iterate over all the non-optional elements:
let a = ["0","1","foo","2"]
for case let i? in a.map({ Int($0)}) {
print(i+1, appendNewline: false)
}
prints 321
. This is using the pattern-matching syntax for an optional, which is a variable name followed by ?
.
You can also use this pattern matching in switch
statements:
func add(i: Int?, _ j: Int?) -> Int? {
switch (i,j) {
case (nil,nil), (_?,nil), (nil,_?):
return nil
case let (x?,y?):
return x + y
}
}
add(1,2) // 3
add(nil, 1) // nil
Looping until a function returns nil
Much like if let
, you can also write while let
and loop until nil
:
while let line = readLine() {
print(line)
}
You can also write while var
(similar caveats to if var
apply).
where
clauses also work here (and terminate the loop, rather than skipping):
while let line = readLine()
where !line.isEmpty {
print(line)
}
Passing an optional into a function that takes a non-optional and returns a result
Instead of:
let j: Int
if i != nil {
j = abs(i!)
}
else {
// no reasonable action to take at this point
fatalError("no idea what to do now...")
}
you can use optional’s map
operator:
let j = i.map { abs($0) }
This is very similar to optional chaining, but for when you need to pass the non-optional value into the function as an argument. As with optional chaining, the result will be optional.
This is nice when you want an optional anyway. For example, reduce1
is like reduce
, but uses the first value as the seed, returning an optional in case the array is empty. You might write it like this (using the guard
keyword from earlier):
extension Array {
func reduce1(combine: (T,T)->T)->T? {
guard let head = self.first
else { return nil }
return dropFirst(self).reduce(head, combine: combine)
}
}
[1,2,3].reduce1(+) // returns 6
But instead you could map
the .first
property, and return that:
extension Array {
func reduce1(combine: (T,T)->T)->T? {
return self.first.map {
dropFirst(self).reduce($0, combine: combine)
}
}
}
Passing an optional into a function that takes an optional and returns a result, avoiding annoying double-optionals
Sometimes, you want something similar to map
, but the function you want to call itself returns an optional. For example:
// an array of arrays
let arr = [[1,2,3],[4,5,6]]
// .first returns an optional of the first element of the array
// (optional because the array could be empty, in which case it's nil)
let fst = arr.first // fst is now [Int]?, an optional array of ints
// now, if we want to find the index of the value 2, we could use map and find
let idx = fst.map { find($0, 2) }
But now idx
is of type Int??
, a double-optional. Instead, you can use flatMap
, which “flattens” the result into a single optional:
let idx = fst.flatMap { find($0, 2) }
// idx will be of type Int?
// and not Int?? unlike if `map` was used
Why does Swift's enumerateSubstringsInRange callback take an optional string?
(The following information is from the response to my question
https://forums.developer.apple.com/thread/28272 in the
Apple Developer Forum.)
You can pass the .SubstringNotRequired
option to enumerateSubstringsInRange()
, and then the closure will be called withsubstring == nil
. This option is documented as
NSStringEnumerationSubstringNotRequired
A way to indicate that the block does not need substring, in which
case nil will be passed. This is simply a performance shortcut.
Example:
let str = "Hello, playground"
str.enumerateSubstringsInRange(str.characters.indices,
options: [.ByWords, .SubstringNotRequired]) {
substring, substringRange, _, _ in
print(substring, substringRange)
}
Output:
nil 0..<5
nil 7..<17
I think it is safe to assume that substring != nil
if the.SubstringNotRequired
option is not given.
unable to use optional int possibleNumber in optional binding
The result of
let possibleNumber = Int("123")
is an optional Int - Int?
Then you're trying to create another Int
with
Int(possibleNumber)
which does not work because the initializer expects a non-optional type.
The error message is related to the initializer rather than to the optional binding.
Try this to get the same error message.
let possibleNumber = Int("123")
let x = Int(possibleNumber)
In your second example when you initialize an Int
with an implicit unwrapped Int!
argument you get a non-optional Int
and the compiler complains about the missing optional.
Why does Swift Array contains(_:) method accept nil argument when it must be non-optional?
The compiler can automatically wrap a value into an optional, if required by the context. That is what makes simple assignments like
let value: Int? = 123
possible. In your first example, the return type of the closure is inferred as String?
from the context, so that the map
returns [String?]
, and .contains(nil)
can be applied to it. I.e. the compiler understands the code as
let array = [1, 2, 3, 4, 5].filter { _ in
[1, 2, 3].map { smallNumber -> String? in
"\(smallNumber)"
}
.contains(nil)
}
In the second example the compiler does not have that context, and mappedNumbers
has the type [String]
. You can make it compile by specifying the closure return type String?
explicitly:
let array = [1, 2, 3, 4, 5].filter { _ in
let mappedNumbers = [1, 2, 3].map { smallNumber -> String? in
"\(smallNumber)"
}
return mappedNumbers.contains(nil)
}
Dynamic Type in Swift 3
The compiler is telling you that you can't use an if let
because it's totally unnecessary. You don't have any optionals to unwrap.if let
is used exclusively to unwrap optionals.
public func huntSuperviewWithClassName(className: String) -> UIView?
{
var foundView: UIView? = nil
var currentVeiw:UIView? = self
while currentVeiw?.superview != nil{
let classString = NSStringFromClass((currentVeiw?.classForCoder)!)
if let classNameWithoutPackage = classString.components(separatedBy:".").last{
print(classNameWithoutPackage)
if classNameWithoutPackage == className{
foundView = currentVeiw
break
}
}
}
currentVeiw = currentVeiw?.superview
}
return foundView
}
works Fine!
Related Topics
How to Hash a String to Sha512 in Swift
Safari App Extension Crashes After a Few Seconds for Hello World Project
Xcode Error - Undefined Symbols for Architecture X86_64
Using Custom Cifilter on Calayer Shows No Change to Calayer
What Does "Constrain to Margins" Mean in Interface Builder in Xcode 6.0.1
Typecast Unsafemutablepointer<Void> to Unsafemutablepointer<#Struct Type#>
How to Make Embedded View Controller Part of the Responder Chain
How to Sort JSON Coming from Alamofire and Return Final JSON Object (Swiftyjson)
Generate Avaudiopcmbuffer with Avaudiorecorder
How to Pass One Swiftui View as a Variable to Another View Struct
Nspopover to Start in a Detached State
Identify Mkpointannotation in Mapview
Callback Url Not Approved Despite Being Provided Twitter API
How to Make JSON Data Persistent for Offline Use (Swift 4)
Adding a Custom Font to MACos App Using Swift
What Might Be Causing This Animation Bug with Swiftui and Navigationview