问题
I created my first Cocoa App for OS X and was happy to finally submit it to iTunes connect. Just to get not approved because the application is crashing. I had a hard time to figure out all things until here and now I'm finally stuck and don't know how to solve the following issue:
First of all, the application works perfectly fine when simply built and run. The mysterious crash will only happen when the app is archived. That's why I did not notice the cash before and submitted the not working app to iTunes Connect.
I symolicated the crash log and the app crashes at a point where it just doesn't make any sense (for me). Here is the relevant (stripped) code.
I have a class Countries
which does not do much more than loading a set of items from Core Data (SQLite):
//
// Countries.swift
//
import Cocoa
let appCountries = Countries()
class Countries {
lazy var items: [Country] = {
if let list = self.load() {
return list
}
else {
return []
}
}()
func load() -> [Country]? {
if let countryList = coreDataHelper.fetchEntitiesForClass("Country") as? [Country] {
return countryList
}
else {
return nil
}
}
}
Country
is an entity of my data model. The coreDataHelper
class simply holds all relevant methods for creating the data store, managed object context and fetching entities. The code is not relevant, works for sure in a dozen of other use cases in the same app. Just assume coreDataHelper.fetchEntitiesForClass()
returns all items of an entity.
Now I have a modal window (beginSheet
, which I implemented by following this tutorial) which holds a NSPopUpButton
and I am populating all the Country
elements to this PopUp:
//
// ModalWindow.swift
//
import Cocoa
class ModalWindow: NSWindowController {
var mainW: NSWindow = NSWindow()
let locale = NSLocale.currentLocale()
@IBOutlet weak var countries: NSPopUpButton!
override init() {
super.init()
}
override init(window: NSWindow!) {
super.init(window: window)
//Initialization code here.
}
required init?(coder: (NSCoder!)){
super.init(coder: coder);
}
override func windowDidLoad() {
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
//method called to display the modal window
func beginSheet(mainWindow: NSWindow){
self.mainW = mainWindow
NSApp.beginSheet(self.window!, modalForWindow: mainWindow, modalDelegate: self, didEndSelector:nil, contextInfo: nil)
if let countryList = appCountries.items as NSArray? {
for country in countryList as [Country] {
if let localized = self.locale.displayNameForKey(NSLocaleCountryCode, value: country.name) {
self.countries!.addItemWithTitle(localized)
}
}
}
}
//method called to slide out the modal window
func endSheet(){
NSApp.endSheet(self.window!)
self.window!.orderOut(mainW)
}
}
The symbolicated crash log tells me, the crash did happen in the line where I loop over the countryList
:
for country in countryList as [Country] {
Crash-log excerpt:
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: EXC_I386_GPFLT
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libobjc.A.dylib 0x00007fff8b863064 objc_retain + 20
__TFC8MyApp18Modalwindow13windowDidLoadfS0_FT_T_ ModalWindow.swift:35
2 com.apple.AppKit 0x00007fff95dcee07 -[NSWindowController _windowDidLoad] + 586
From what I understand, this is a memory access issue. But I don't understand why. The thing is, when I NSLog
the countryList
it starts working:
if let countryList = appCountries.items as NSArray? {
NSLog("[TEST] %@", countryList)
for country in countryList as [Country] {
//...
}
}
I tried to interact in other ways with countryList
but everything else will cause a crash when archived, unless I NSLog
it first exactly like above. Even this would crash: NSLog("[TEST] %@", "\(countryList)")
, even though it should be the exact same from my understanding. And again, this only happens when the application is archived in preparation for sending it to iTunes connect. Zero issues when simply built and run.
I am tempted to simply leave the NSLog
in there and ship it that way, but I would like to understand what is going on an how to solve it appropriate.
Edit: Just installed the update Xcode 6.2 and have the same problem as with 6.1.1.
回答1:
I experimented with all the Build Settings which differed between Debug
and Release
. It looks like this is a Swift compiler issue. The problem disappears when I change the Optimization Level
from Fastest
to None
. Both Fastest
and Fastest, Unchecked
produce above error. None
works.
Now that I have identified the problem, I was able to search and find a better workaround. The problem is somehow related to Arrays in general and I found several mentions of it online. A simple workaround is to cast the array elements to AnyObject
, like so:
for country in countryList as [AnyObject] as [Country] {
//...
}
instead of
for country in countryList as [Country] {
//...
}
Found this in another SO question: Swift optimization level breaks converting NSArray to Array
来源:https://stackoverflow.com/questions/29003323/memory-access-crash-only-in-archived-cocoa-application-exc-bad-access-exc-i386