11

I'm having a problem, which I can't figure out for the life of me. I've searched the internet, trying to understand Swifts's EXC_BAD_ACCESS, but nothing seemed to help.


The following code is quite long, but most of the time the comments are all the information needed to understand the item of relevance.

I have a class CalculatorController, which contains the following relevant methods and properties:

import UIKit    

class CalculatorController: UIViewController {

    // the actual `@IBOutlet` which is never accessed directly
    @IBOutlet private weak var _mainDisplay: UILabel!
    
    // an instance of `MainDisplayMicroController`
    // holds a reference to `_mainDisplay`
    // is used to manipulate `_mainDisplay` in a controlled way
    private var mainDisplay: MainDisplayMicroController!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // connects `mainDisplay` with `_mainDisplay`
        mainDisplay = MainDisplayMicroController(label: _mainDisplay)
        
        // sets `_mainDisplay`'s `text` property to "0"
        mainDisplay.content = .Number(0)
    
        //...
    }

    //...
}

In order to manage _mainDisplay in a certain way, I have created a class MainDisplayMicroController, which on the one hand contains a reference to the the UILabel itself, and on the other hand contains methods and properties, which perform actions on the UILabel:

import UIKit

class MainDisplayMicroController {
    
    // used to express what `label.text` is currently showing
    private enum DisplayState {
        case ShowingNumber
        case ShowingConstant
        case ShowingErrorMessage
        case Unknown
    }

    // holds the current state of what `label.text` is showing
    private var state = DisplayState.Unknown

    // used to pass different types of values in and out of this class
    enum ContentType {
        case Number(Double)
        case Constant(String)
        case ErrorMessage(String)
        case Unknown(Any?)
    }

    // holds the reference to the label which is being manipulated/managed
    private var label: UILabel?

    // makes `label`'s `text` property directly accessible, as `label` is `private`
     var text: String? {
        get {
            return label?.text
        }
        set {
            label?.text = newValue
            removeLeadingZeros()
            transformToInteger()
        }
    }

    // a property to allow controlled retrieval and manipulation of `label.text`
    // uses `ContentType` to make clear what the information in `label.text` is/ is supposed to be
    var content: ContentType {
        get {
            switch state {
            case .ShowingNumber:
                if let string = text {
                    if let value = NSNumberFormatter().numberFromString(string)?.doubleValue {
                        return .Number(value)
                    }
                }
            case .ShowingConstant:
                if let symbol = text {
                    return .Constant(symbol)
                }
            case .ShowingErrorMessage:
                if let message = text {
                    return .ErrorMessage(message)
                }
            default:
                break
            }

            state = .Unknown
            return .Unknown(text)
        }
        set {
            switch newValue {
            case .Number(let value):
                text = "\(value)"
                state = .ShowingNumber
                removeLeadingZeros()
                transformToInteger()
            case .Constant(let symbol):
                text = symbol
                state = .ShowingConstant
            case .ErrorMessage(let message):
                text = message
                state = .ShowingErrorMessage
            case .Unknown(let thing):
                text = "Error: Passed unknown value: \(thing)"
                state = .ShowingErrorMessage
            }
        }
    }

    // removes the ".0" from `label.text`, if it is a whole number
    private func transformToInteger() {
        if state == .ShowingNumber {
            switch content {
            case .Number(let value):
                if round(value) == value {
                    var doubleString = "\(value)"

                    if doubleString.rangeOfString("e") == nil {
                        dropLast(doubleString)
                        dropLast(doubleString)
                    }

                    text = doubleString
                }
            default:
                break
            }
        }
    }

    // removes leading "0"s from `label.text` if they are redundant
    private func removeLeadingZeros() {
        if state == .ShowingNumber {
            switch content {
            case .Number(let displayedValue):
                content = .Number(displayedValue)
            default:
                break
            }
        }
    }

    //...
}

Now, when I run the code I get the following error:

Error 1

From what I've read on EXC_BAD_ACCESS, the error often occurs when trying to call methods on released objects. I've tried using NSZombieto check the issue, but I didn't find anything (probably due to my incompetence when using NSZombie).


If I try to follow what is happening by logic, I come to following conclusion:

  1. mainDisplay is set successfully in viewDidLoad()
  2. mainDisplay.content is called
  3. in the content's setter the switch-statement executes the .Number case
  4. text and state are successfully set
  5. removeLeadingZeros() is called
  6. the switch-statement accesses content's getter
  7. the switch-statement in content's getter executes the .ShowingNumber case
  8. the if-statements resolve to true, finally trying to evaluate the NSNumberFormatter expression
  9. the EXC_BAD_ACCESS occurs

Does anyone know why this is happening? Does it have to do with me manipulating an @IBOutlet in a different class?
Any help is greatly appreciated!


Here are links to the complete CalculatorController and MainDisplayMicroController.


Update #1:

As @abdullah suggested I have tried directing the NSNumberFormatter expression in to multiple expressions. I still get the error though:

Error 2


Update #2:

I've removed all references and external classes, to make it as simple as possible, while maintaining the same functionality.
All of the methods and properties defined in MainDisplayMicroController have been moved to CalculatorModel.
These methods and properties now access the original @IBOutlet, not any reference to it.

But still when trying to run it I get EXC_BAD_ACCESS(code=2) at the same line of code.
I'm just super confused, as it can't have anything to do with weird references, or objects being released too soon anymore.

Here's the complete code for the new CalculatorController.


Update #3:

I've removed the NSNumberFormatter line, by changing it to:

Change from NSNumberFormatter

Now I get the following error though:

New Error

I assume there's some fundamental problem with the code, so I'm scrapping it. But thanks for all the help, and attempts at figuring this out.


Update #4:

This is what I get when adding a breakpoint on throw for all exceptions:

Exception

Community
  • 1
  • 1
Marcus Rossel
  • 3,196
  • 1
  • 26
  • 41
  • Much code , very complex. Try ripping it apart . See what happens when you eliminate `MainDisplayMicroController` and dump its methods into `CalculatorController` . See what happens when you eliminate the `text` get/set methods. Im willing to bet somewhere you have a pass by weak reference. You made your spaghetti, now you get to eat it. – Warren Burton May 12 '15 at 13:52
  • Try to dissect the statement NSNumberFormatter().numberFromString(string)?.doubleValue into parts. Like var nf = NSNumberFormatter() if nf != nil { nf.numberFromString(string) } etc. Check on which statement it fails. – Abdullah May 12 '15 at 13:56
  • @WarrenBurton: I actually find it quite simple and structured. In fact that's why I created a separate class, to handle all of the methods and properties for `_mainDisplay`. I have tried different variants though. For example I dumped the `MainDisplayMicroController` class in the same file as `CalculatorController` and accessed `_mainDisplay` directly, without any separate reference. This allowed the build to succeed, but the code didn't work then (the UI didn't respond to anything). – Marcus Rossel May 12 '15 at 13:56
  • @abdullah: I'll try that. – Marcus Rossel May 12 '15 at 13:56
  • What does `println(string)` show (immediately before the number formatter is used) ? – Martin R May 12 '15 at 14:19
  • It prints "0.0", exactly as expected – Marcus Rossel May 12 '15 at 14:21
  • @MarcusRossel can you compress the project folder into a zip file and post it? Where is your class calculatorModel defined ? – Leo Dabus May 15 '15 at 21:33
  • Print EVERYTHING for those two classes, even things you don't think are relevant. Then go through your code line by line and see where the discrepancies are. – Adam Evans May 21 '15 at 17:30
  • @Kijug What do you mean "Print Everything"? – Marcus Rossel May 21 '15 at 17:33
  • @MarcusRossel Print the values of all your variables, print the results of every single statement, print which methods are being called, and print anything else you can think of. The more data you have about your program's execution, the easier it will be to find the bug. Don't worry about flooding the screen with information. It will be worth it to find your bug. – Adam Evans May 21 '15 at 17:36
  • @MarcusRossel Did you end up finding a solution? I seem to be having almost the exact same issue! – Lahav Jun 13 '16 at 03:35

4 Answers4

6

I don't really see anything in that line that can cause a crash. I suggest you do the following:

  1. Make a clean build (clean, nuke your derived data folder, then build) and see if the crash persists
  2. If the crash persists, set a breakpoint on throw for all exceptions to see which operation in the callstack caused the crash, and take it from there
roop
  • 1,291
  • 8
  • 10
  • Where can I find the "derived data folder"? And does the breakpoint need to be set at a certain line, as I don't seem to have that option? – Marcus Rossel May 22 '15 at 10:53
  • 1
    By "nuking derived data", I meant the "Clean Build Folder" menu item (go to Products, press option key). More info on derived data here: http://useyourloaf.com/blog/2011/09/14/xcode-4-deriveddata-and-cleaning-the-build-directory.html – roop May 23 '15 at 07:17
  • 1
    By setting a breakpoint on throw of all exceptions, we ask Xcode to stop execution whenever an exception is raised (an EXC_BAD_ACCESS in your case) on the statement that caused the exception. So, it needn't be set on a certain line. – roop May 23 '15 at 07:37
  • "Clean Build Folder" didn't do anything for me. Setting a breakpoint for all exceptions didn't give me any information either. It still just gives me the same error message. – Marcus Rossel May 23 '15 at 10:24
  • It should give you the same error message. But I expected the callstack (on the column on the left) to give you some clue. – roop May 23 '15 at 12:47
  • Update #4 shows the result of following your steps. – Marcus Rossel May 23 '15 at 13:15
  • If you widen the column, you'll see a callstack (or backtrace, i.e. which method called which method called which method called ...). Check if the backtrace is as you expect it to be. If not, maybe there's a stray recursion somewhere that's causing a, uhm, stack overflow :) – roop May 24 '15 at 02:32
  • For me it was a stupid Xcode bug. After cleaning the error went away. – zirinisp Jan 26 '16 at 16:41
0

@WarrenBurton is on to something.

Take your line that crashes out of your big class and run it in the playground and it works fine:

let string = "1.213"
if let value = NSNumberFormatter().numberFromString(string)?.doubleValue 
{
  println("value = \(value)")
}

Displays the result

value = 1.213

Where is your variable "string" defined in your class?

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • `string` is defined in the same switch case, as the result of optional binding of `MainDisplayMicroController`'s `text` property. – Marcus Rossel May 12 '15 at 14:01
  • You've created a big complex class that's tightly coupled with a view controller and makes direct access to it's view properties. That's bad design. It's all but impossible to untangle it and see what's going wrong. – Duncan C May 12 '15 at 14:06
  • How should I best manage the `_mainDisplay` property in a controlled manner. Where should I put the needed properties and methods? – Marcus Rossel May 12 '15 at 14:07
  • I have no idea. If it were me I would scrap the whole idea and start over with a simpler approach. – Duncan C May 12 '15 at 14:08
  • I have scrapped my previous attempts :D The whole thing just gets very messy if I don't have the properties and methods defined in `MainDisplayMicroController` :/ – Marcus Rossel May 12 '15 at 14:09
0

Notice that string is blue, like a keyword, not black, like other local variables.

I'd try local variable string ==> myString just to know for sure.

donjuedo
  • 2,475
  • 18
  • 28
0

Just 'cuz I was seeing the same thing and noticed no one had commented past your last edit (and maybe a fellow Googler for this issue will see this someday):

For both of our situations the issue is infinite recursion - we're calling a method from itself infinitely. That's the bug. The implication in the crash of NSNumberFormatter is a red herring.

Rocksaurus
  • 53
  • 5
  • Which method are we calling infinitly? – Marcus Rossel Oct 15 '16 at 09:48
  • I can't read the text in your screenshot but look at your call stack (in the left pane under "Thread 1") on "Update #4" in the original post. Whatever method is filling the call stack in your screenshot is the method you're calling infinitely. Squinting it looks like Calculator.MainDisplay something. – Rocksaurus Oct 19 '16 at 19:07