I have an app managing a simple stocks portfolio. Amongst other things, it keeps a record of the required exchange rates in a dictionary, like so: [ EURUSD=X : 1.267548 ] This disctionary is a Dictionary property of a singleton called CurrencyRateStore.
When updating the stocks quotations, it checks for an updated exchange rate and updates the dictionary with the following code:
CurrencyRateStore.sharedStore()[symbol] = fetchedRate.doubleValue
That calls:
subscript(index: String) -> Double? {
get {
return dictionary[index]
}
set {
// FIXME: crashes when getting out of the app (Home button) and then relaunching it
dictionary[index] = newValue!
println("CurrencyRateStore - updated rate for \(index) : \(newValue!)")
}
}
The first time the app is started, it works fine. But if I quit the app (with the Home button) and then relaunch it, the currency rates are updated again, but this time, I get a EXC_BAD_ACCESS at the line
dictionary[index] = newValue!
Here is a screenshot:
[EDIT] Here is the thread in the debug navigator:
I tried to update the dictionary without a subscript, like so:
CurrencyRateStore.sharedStore().dictionary[symbol] = fetchedRate.doubleValue
but without more success. Same if I use the function updateValue:forKey: I didn't have the issue in Objective-C.
Thanks for your help !
[EDIT] Here is the whole class CurrencyRateStore:
class CurrencyRateStore {
// MARK: Singleton
class func sharedStore() -> CurrencyRateStore! {
struct Static {
static var instance: CurrencyRateStore?
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
Static.instance = CurrencyRateStore()
}
return Static.instance!
}
// MARK: Properties
/** Dictionary of currency rates used by the portfolio, presented like [ EURUSD=X : 1.3624 ] */
var dictionary = [String : Double]()
/** Returns a sorted array of all the keys on the currency rates dictionary */
var allKeys: [String] {
var keysArray = Array(dictionary.keys)
keysArray.sort {$0 < $1}
return keysArray
}
init() {
if let currencyRateDictionary: AnyObject = NSKeyedUnarchiver.unarchiveObjectWithFile(currencyRateArchivePath) {
dictionary = currencyRateDictionary as [String : Double]
}
}
subscript(index: String) -> Double? {
get {
return dictionary[index]
}
set {
// FIXME: crashes when getting out of the app (Home button) and then relaunching it
// (ApplicationWillEnterForeground triggers updateStocks)
dictionary[index] = newValue!
println("CurrencyRateStore - updated rate for \(index) : \(newValue!)")
}
}
func deleteRateForKey(key: String) {
dictionary.removeValueForKey(key)
}
/** Removes all currency rates from the Currency rate store */
func deleteAllRates()
{
dictionary.removeAll()
}
// MARK: Archive items in CurrencyRateStore
var currencyRateArchivePath: String { // Archive path
var documentDirectories: Array = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
// Get the only document directory from that list
let documentDirectory: AnyObject = documentDirectories.first!
return documentDirectory.stringByAppendingPathComponent("currencyRates.archive")
}
func saveChanges()-> Bool
{
// return success or failure
return NSKeyedArchiver.archiveRootObject(dictionary, toFile: currencyRateArchivePath)
}
}
This looks to me like a concurrency issue. Swift dictionaries aren't thread safe, and using them from a singleton can lead to multiple reader/writer issues.
Edit: I am pretty sure this is the real answer, based on the given source/debugging dump. To correct what I wrote, specifically MUTABLE dictionaries and arrays (as well as NSMutableDictionary and NSMutableArray) aren't thread safe, and problems arise when using them within Singletons that are accessed from multiple threads, and that appears to be what the sample source code is doing, or enabling other parts of the code to do.
I don't have an Apple link discussing Swift collection class thread safety, but I"m pretty sure common knowledge. But the following tutorial on Grand Central Dispatch discusses the problem in depth and how to solve it using GCD.
http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With