title: Swift string manipulation causing EXC_BAD_ACCESS
body: <p>I'm working on a time calculator to try to learn Swift, and I'm running into an issue where formatting the user's input is causing bad access errors.</p>
  
  <p>Here is the code that's causing the crash (originally contained in a loop but broken up to try to find the problem):</p>
  
  <pre><code>var outputString : String = ""
  let prefixString : String = "00"
  
  let combinedString1 : String = prefixString + self.timeIntervalInput[0]
  let combinedString1a : String = combinedString1.substringFromIndex(countElements(combinedString1) - 2)
  outputString += combinedString1a
  
  let combinedString2 : String = prefixString + self.timeIntervalInput[1]
  let combinedString2a : String = combinedString2.substringFromIndex(countElements(combinedString2) - 2)
  outputString += combinedString2a    // CRASH USUALLY HAPPENS HERE
  
  let combinedString3 : String = prefixString + self.timeIntervalInput[2]
  let combinedString3a : String = combinedString3.substringFromIndex(countElements(combinedString3) - 2)
  outputString += combinedString3a
  </code></pre>
  
  <p>The user's input is stored in an array of strings (hours/minutes/seconds/milliseconds).  The above code is supposed to fill in the gaps with 0s (so 2 becomes 02, an empty string becomes 00, etc), and is run each time the user's input is updated.</p>
  
  <p>The above code crashes after the first or second character is entered.  Changing the prefixString from let to var causes it to work correctly for slightly longer, so that it crashes after the third character instead.</p>
  
  <p>Am I doing something wrong here, or is this a bug in Swift?</p>
  
  <p><strong>Edit (additional info):</strong></p>
  
  <p>Here's the entire struct:</p>
  
  <pre><code>struct TimeCalcInput
  {
      var timeIntervalInput : String[] = ["", "", "", ""]
      var timeIntervalIndex : Int = 0
      var decimalInput : String = ""
      var timeType : TimeTypeEnum = TimeTypeEnum.AM
      var potentialInput : String = ""
  
      var label : String {
          get {
  
              if (self.timeType == TimeTypeEnum.Decimal) {
                  return self.decimalInput
              } else {
  //              var outputString : String = ""
  //              for (var i : Int = 0; i &lt; 3; i++) {
  //                  let prefixString : String = "00"
  //                  var combinedString : String = ""
  //                  combinedString += prefixString + self.timeIntervalInput[i]
  //                  combinedString = combinedString.substringFromIndex(countElements(combinedString) - 2)
  //                  outputString = outputString + "\(combinedString)"
  //              }
                  var outputString : String = ""
                  let prefixString : String = "00"
  
                  let combinedString1 : String = prefixString + self.timeIntervalInput[0]
                  let combinedString1a : String = combinedString1.substringFromIndex(countElements(combinedString1) - 2)
                  outputString += combinedString1a
  
                  let combinedString2 : String = prefixString + self.timeIntervalInput[1]
                  let combinedString2a : String = combinedString2.substringFromIndex(countElements(combinedString2) - 2)
                  outputString += combinedString2a    // CRASH USUALLY HAPPENS HERE
  
                  let combinedString3 : String = prefixString + self.timeIntervalInput[2]
                  let combinedString3a : String = combinedString3.substringFromIndex(countElements(combinedString3) - 2)
                  outputString += combinedString3a
  
                  return ""
                  let test : String = "000"
                  var asd : String = test + self.timeIntervalInput[3]
                  asd = asd.substringFromIndex(countElements(asd) - 3)
                  outputString += asd
  
                  if (self.timeType == TimeTypeEnum.AM) {
                      outputString += " AM"
                  } else if (self.timeType == TimeTypeEnum.PM) {
                      outputString += " PM"
                  }
  
                  println(outputString)
  
                  return outputString
              }
          }
      }
  
  
      mutating func validateNewInput(inputString : String) -&gt; Bool
      {
          switch (self.timeType)
              {
          case TimeTypeEnum.Decimal:
              return self.validateNewInputForDecimal(inputString)
          case TimeTypeEnum.Interval:
              //                  return self.validateNewInputForInterval(inputString)
              return false
          case TimeTypeEnum.AM, TimeTypeEnum.PM:
              var temp : Bool = self.validateNewInputForRawTime(inputString, isMilitaryTime:false)
              println(self.timeIntervalInput)
              return temp
          case TimeTypeEnum.Military:
              var temp : Bool =  self.validateNewInputForRawTime(inputString, isMilitaryTime:true)
              println(self.timeIntervalInput)
              return temp
          default:
              return false
          }
      }
  
  
      mutating func validateNewInputForDecimal(inputString : String) -&gt; Bool
      {
          let combinedString : String = self.decimalInput + inputString
  
          // note: this regex fails on letters-only strings.  not an issue now but something to watch
          let predicate : NSPredicate = NSPredicate(format: "SELF MATCHES '^[0-9]*(\\.[0-9]*)'")
          let stringTest : Bool = predicate.evaluateWithObject(combinedString)
  
          if (stringTest) {
              self.decimalInput = combinedString
              return true
          }
          return false
      }
  
      // used to evaluate an incoming string when the string is assumed to represent a raw time (e.g. 5:30am)
      // since the string is being built one character at a time, each character needs to be evaluated separately
      // to ensure that the final time is valid
      mutating func validateNewInputForRawTime(inputString : String, isMilitaryTime : Bool) -&gt; Bool
      {
          let currentString = self.timeIntervalInput[self.timeIntervalIndex]
  
          // if the incoming value is a ":" assume the user wants to move on to the next section
          // (e.g. hours --&gt; minutes).  Fill in any gaps in the current section, then advance to the next section
          if (inputString == ":") {
              // check to make sure there is a "next" section
              if (self.timeIntervalIndex &lt; self.timeIntervalInput.count - 1)
              {
                  // if the current section is incomplete, fill any gaps with 0s
                  let prefixString = "00"
                  var combinedString = prefixString + currentString
                  combinedString = combinedString.substringFromIndex(countElements(combinedString) - 2)
                  self.timeIntervalInput[self.timeIntervalIndex] = combinedString
  
                  // go to the next section
                  self.timeIntervalIndex++
                  return true
              }
              // if the incoming value is a number, evaluate it to make sure it's valid, and if so, add it
          } else {
  
              let combinedString = currentString + inputString
  
              // each section (hours, minutes, etc, has its own rules for whether the input is valid
              switch (self.timeIntervalIndex)
              {
                  // hours
                  case 0:
                      // if empty, accept any number
                      if (countElements(currentString) == 0) {
                          self.timeIntervalInput[self.timeIntervalIndex] = combinedString
                          return true
                          // if one digit exists, make sure total value is less than 12/24 (format depending)
                      } else if (countElements(currentString) == 1) {
                          if ((combinedString.toInt() &lt; 24 &amp;&amp; isMilitaryTime) || (combinedString.toInt() &lt; 12)) {
                              self.timeIntervalInput[self.timeIntervalIndex] = combinedString
                              return true
                          }
                          // if both digits exist, skip to the next section and add it
                      } else {
                          self.timeIntervalIndex++
                          self.timeIntervalInput[self.timeIntervalIndex] = inputString
                          return true
                      }
                  // minutes, seconds
                  case 1, 2:
                      // if empty, accept any number
                      if (countElements(currentString) == 0) {
                          self.timeIntervalInput[self.timeIntervalIndex] = combinedString
                          return true
                          // if one digit exists, make sure total value is less than 60
                      } else if (countElements(currentString) == 1) {
                          if (combinedString.toInt() &lt; 60) {
                              let combinedString = currentString + inputString
                              self.timeIntervalInput[self.timeIntervalIndex] = combinedString
                              return true
                          }
                          // if both digits exist, skip to the next section and add it
                      } else {
                          self.timeIntervalIndex++
                          self.timeIntervalInput[self.timeIntervalIndex] = inputString
                          return true
                      }
                  // milliseconds
                  case 3:
                      // accept any combined total less than 1000
                      if (combinedString.toInt() &lt; 1000) {
                          let combinedString = currentString + inputString
                          self.timeIntervalInput[self.timeIntervalIndex] = combinedString
                          return true
                      }
                  default:
                      break
              }
          }
          return false
      }
  }
  </code></pre>
  
  <p>And here's where it's being used (inputs is an array of the above struct):</p>
  
  <pre><code>// -----------------------------------------------------------------------------------------------------------------
      @IBAction func addInputCharacter(#sender : UIButton)
      {
          let inputChar : String = String(sender.tag)
          self.inputs[self.currentInput].validateNewInput(inputChar)
          self.updateLabelsAfterInput()
      }
  
      // -----------------------------------------------------------------------------------------------------------------
      func updateLabelsAfterInput()
      {
          self.lblMainLabel.text = self.inputs[self.currentInput].label
          if (self.currentInput == 0) {
              self.lblSecondaryLabel.text = ""
          } else {
              self.lblSecondaryLabel.text = self.inputs[0].label
          }
      }
  </code></pre>
  
  <p>The crash is happening the first time the user enters a number.  I've tested it without updating the label, and the timeIntervalInputs array is getting set perfectly (after entering "1", the array reads [1, , , ], then entering 0 it becomes [10, , , ])</p>
  
  <p>The error message on crash is </p>
  
  <blockquote>
    <p>Thread 1: EXC_BAD_ACCESS (code=1, address=0x12aff2cf0)</p>
  </blockquote>
  
  <p>combinedString2a, immediately before the crash, is storing the correct value ("00").  After the crash, Quick Look says the value is "(None)" and printing the description gives the following message: </p>
  
  <blockquote>
    <p>(String) combinedString2a = DW_OP_piece for offset 8: top of stack is
    not a piece</p>
  </blockquote>