Reputation: 3479
The strangest thing. I have two methods that use an optional enum in the exact same way. One function always works, both in device and simulated. However, the second method only works on the simulator and sometimes on the device. I'm pretty sure that "when" it works is not related to the value from testing that I have done. For example, I always call the method first with a nil value, and only sometimes does it crash.
The error is an EXC_ARM_BREAKPOINT, prints no messages, and the stacktrace is of no help. The debugger shows the optional's value as "Some" even after checking for nil, which is weird because I can't print it (it crashes when trying).
The failing code:
class LevelElevenState: GameState {
init(count: Int, var goalNumber: GameNumber?) {
super.init(stateType: .Normal)
self.transition = {
(actionDone: GameAction) -> GameState? in
switch actionDone {
case .Pressed(let number):
if goalNumber == nil { //FAILS HERE
goalNumber = number //OR HERE
}
if goalNumber == number {
self.delegate?.switchIndicators(true, lights: [GameNumber(rawValue: count)!])
if count < 5 {
let direction = GameDirection(rawValue: Int(arc4random()) % 2)!
let amount = Int(arc4random_uniform(98)) + 1
var nextRaw = number.rawValue
switch direction {
case .Up:
nextRaw = (nextRaw + amount) % 5
case .Down:
nextRaw = ((nextRaw - amount) % 5) + 5
}
let nextNumber = GameNumber(rawValue: nextRaw)
let phrase = "\(direction), \(amount)"
self.delegate?.speakPhrase(phrase)
return LevelElevenState(count: count + 1, goalNumber: nextNumber)
} else {
return GameState.goal()
}
}else {
return GameState.mistake()
}
default:
return nil
}
}
}
}
class LevelEleven: GameStateLevel, GameStateLevelDelegate {
override init() {
super.init()
levelDelegate = self
index = 10
}
func initialState() -> GameState {
return LevelElevenState(count: 1, goalNumber: nil)
}
}
The following code NEVER crashes. I personally don't see the difference.
class LevelSevenState: GameState {
var count = 0
init(goal: Int, var goalNumber: GameNumber?) {
super.init(stateType: .Normal)
self.transition = {
(actionDone: GameAction) -> GameState? in
switch actionDone {
case .Pressed(let number):
if goalNumber == nil {
goalNumber = number
}
if goalNumber == number {
self.count++
if self.count == goal {
if let indicator = GameNumber(rawValue: goal) {
self.delegate?.switchIndicators(true, lights: [indicator])
}
if goal < 5 {
return LevelSevenState(goal: goal + 1, goalNumber: goalNumber)
} else {
return GameState.goal()
}
}else {
return nil
}
}else {
return GameState.mistake()
}
default:
return nil
}
}
}
}
class LevelSeven: GameStateLevel, GameStateLevelDelegate {
override init() {
super.init()
levelDelegate = self
index = 6
}
func initialState() -> GameState {
return LevelSevenState(goal: 1, goalNumber: nil)
}
}
enum GameNumber: Int {
case One = 1
case Two = 2
case Three = 3
case Four = 4
case Five = 5
init?(myRaw: Int) {
if let fromRaw = GameNumber(rawValue: myRaw) {
self = fromRaw
}else {
return nil
}
}
init(color: GameColor) {
switch color {
case .Blue:
self = .One
case .Green:
self = .Two
case .Yellow:
self = .Three
case .Orange:
self = .Four
case .Red:
self = .Five
}
}
}
Upvotes: 0
Views: 951
Reputation: 69
Yes, that's the piece Int(arc4random())
that's crashing. The reason is that arc4random()
returns UInt32
, which might (randomly!) exceed Int.max
and cause Int(...)
to crash -- as answered here.
There is a number of ways to avoid this, for instance, you can first calc the remainder, and then pass it to Int(...)
:
Int(arc4random() % 2)
Or you can use
Int(arc4random_uniform(2))
Just make sure the argument of Int(...)
is within limits!
Cheers)
Upvotes: 1
Reputation: 3479
I found the problem. The reason it was so difficult to debug was that the error has nothing to do with what I thought. XCode was breaking on the wrong line. This was the line that was making the error:
Int(arc4random()) % 2
I don't know exactly why that makes swift crash, but that's definitely it. If anyone has any insight to the problem, I will gladly accept an answer that addresses that.
Upvotes: 1