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 :)
What's up with the weird breakpoint exception in Swift?
That exception happens because arc4random()
returns UInt32
and you are subtracting a value of that, which possibly causes a negative value, which is not representable as an unsigned integer.
To fix this you may want to cast the expression to Int32 before subtracting:
var y = CGFloat(self.size.height / 3) + CGFloat((Int32)(arc4random() % 100) - 50)
Regarding the x
value above - you create CGFloat
and only subtract after that and therefore not encounter the above situation. Therefore a different fix would be the following
var y = CGFloat(self.size.height / 3) + CGFloat(arc4random() % 100) - 50
You basically have to ensure that at the point where you subtract something you have no unsigned type present.
The annoying thing about this is that the compiler does not warn you and the code actually works sometimes, every time when arc4random
returns something larger than 50 and will therefore not drift into the negative values...
Incorporating the feedback and suggestion that you should not use arc4random % something
the best solution would be to use arc4random_uniform
.
arc4random % something
will not yield a proper distribution of the random values, while arc4random_uniform(something)
does - this has already been discussed on SO before.
A final note: you probably should choose 101 as upper bound instead of 100 because both arc4random
as well as arc4random_uniform
produce a value between 0 and (upper-1), e.g. 0 and 99. If you subtract 50 you get a value between -50 and +49. Choosing 101 will yield the desired range of -50 to +50.
var y = CGFloat(self.size.height / 3) + CGFloat(arc4random_uniform(101)) - 50
Related Topics
iOS How to Get a List of Already Purchased Products
How to Check Bitfields (Scnetworkreachabilityflags in Particular) for Flags in Swift
Why [Uiscreen Mainscreen].Bounds] Is Not Returning Full Screen Size
Problems with Layout of Some Rows in Swiftui List
Removing Parentheses from The String in iOS
Creating a First Launch Viewcontroller
Detect Array with Issue (Debug Mode)
Applying Different Attributes for Different Portions of an Nsattributedstring
Screenshot on Swift Programmatically
Get All Ranges of a Substring in a String in Swift
Set Custom Font for UItableview Swipe Action (Uicontextualaction)
Stop Overscroll When Using "-Webkit-Overflow-Scrolling: Touch"
Restkit Request Not Sending Parameters
iOS Framework Does Not Work on Simulator
Form Nspredicate from String That Contains Id's
How Has The Nsindexpath Initialization Changed in Swift3