circuitlego
circuitlego

Reputation: 3479

Swift Optional sometimes crashes on device but not on simulator when checking nil

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)
    }
}

Edit: As requested

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

Answers (2)

irq033
irq033

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

circuitlego
circuitlego

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

Related Questions