Reputation: 1352
Since the release of Xcode 6.1 and iOS 8.1, one of my apps stopped functioning. I managed to reproduce the problem only if I did "RUN" on my device with a scheme of "Release" instead of "Debug".
Now for the problem. This works fine in Debug mode:
import Foundation
class CategoryParser {
var categoriesSettingsDictionary : [String: AnyObject]?
init() {
let categoriesURL = NSBundle.mainBundle().URLForResource("CategoriesSettings", withExtension: "plist")
categoriesSettingsDictionary = NSDictionary(contentsOfURL: categoriesURL!) as? Dictionary<String, AnyObject>
}
}
But it crashes in "Release" mode when I instantiate an Object of the CategoryParser type. After many trials and errors, I figured that to stop it from doing the problem I could place the dictionary initialisation between two println() statements. Why would those make any difference?
import Foundation
class CategoryParser {
var categoriesSettingsDictionary : [String: AnyObject]?
init() {
let categoriesURL = NSBundle.mainBundle().URLForResource("CategoriesSettings", withExtension: "plist")
println("_")
categoriesSettingsDictionary = NSDictionary(contentsOfURL: categoriesURL!) as? Dictionary<String, AnyObject>
println("_")
}
}
Upvotes: 2
Views: 427
Reputation: 51911
It must be a bug around optimizations in Swift compiler. I think, it's around bridging NSDictionary
to Dictionary<String,AnyObject>
.
I reproduced the problem with following setup.
Environment: Xcode 6.1 (6A1052d) / iPhone 6 / iOS 8.1
Template: Single View Application
CategoriesSettings.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ct1</key>
<string>test</string>
</dict>
</plist>
AppDelegate.swift
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let result = loadPlist()
println("result: \(result)")
return true
}
func loadPlist() -> [String: AnyObject]? {
let categoriesURL = NSBundle.mainBundle().URLForResource("CategoriesSettings", withExtension: "plist")
let dict = NSDictionary(contentsOfURL: categoriesURL!)
println(dict)
let result = dict as? [String:AnyObject]
return result
}
}
// EOF
outputs (with -O
):
Optional({
ct1 = test;
})
result: nil
outputs (with -Onone
):
Optional({
ct1 = test;
})
result: Optional(["ct1": test])
I don't know the best workaround, though.
Maybe this works:
class CategoryParser {
var categoriesSettingsDictionary : [String: AnyObject]?
init() {
let categoriesURL = NSBundle.mainBundle().URLForResource("CategoriesSettings", withExtension: "plist")
categoriesSettingsDictionary = NSDictionary(contentsOfURL: categoriesURL!) as? Dictionary<String, AnyObject>
if categoriesSettingsDictionary == nil {
// NOTICE: to other developers about this workaround
println("_")
println("_")
}
}
}
Encapsulating them in autoreleasepool
also works:
class CategoryParser {
var categoriesSettingsDictionary : [String: AnyObject]?
init() {
autoreleasepool {
let categoriesURL = NSBundle.mainBundle().URLForResource("CategoriesSettings", withExtension: "plist")
self.categoriesSettingsDictionary = NSDictionary(contentsOfURL: categoriesURL!) as? Dictionary<String, AnyObject>
}
}
}
But, as of now, I think, you should use NSDictionary
as is, because as long as you only read from it, there is almost no practical difference between NSDictionary
and Dictionary<String,AnyObject>
in most cases.
class CategoryParser {
var categoriesSettingsDictionary : NSDictionary?
init() {
let categoriesURL = NSBundle.mainBundle().URLForResource("CategoriesSettings", withExtension: "plist")
categoriesSettingsDictionary = NSDictionary(contentsOfURL: categoriesURL!)
}
}
OR, this may be too aggressive, but you can implement your own NSDictionary
→ Dictionary
converter.
extension Dictionary {
init?(nsDictionaryOrNil:NSDictionary?) {
if let dict = nsDictionaryOrNil? {
self = [Key:Value](minimumCapacity: dict.count)
for (k,v) in dict {
if let key = k as? Key {
if let val = v as? Value {
self[key] = val
continue
}
}
return nil
}
}
else {
return nil
}
}
}
class CategoryParser {
var categoriesSettingsDictionary : [String:AnyObject]?
init() {
let categoriesURL = NSBundle.mainBundle().URLForResource("CategoriesSettings", withExtension: "plist")
let dict = NSDictionary(contentsOfURL: categoriesURL!)
categoriesSettingsDictionary = [String:AnyObject](nsDictionaryOrNil: dict)
}
}
Upvotes: 1