realm / realm-swift

Realm is a mobile database: a replacement for Core Data & SQLite
https://realm.io
Apache License 2.0
16.3k stars 2.14k forks source link

Upgrading from Realm 0.98.1 to 0.98.3 causes EXC_BAD_ACCESS in Swift #3281

Closed maciekish closed 8 years ago

maciekish commented 8 years ago

Goals

Upgrade Realm from 0.98.1 to 0.98.3

Expected Results

App should work as before since there should be no breaking changes between the two. Nothing else has changed in the project except for the two .Frameworks. I have tried it twice.

Actual Results

App crashes with EXC_BAD_ACCESS

thread #1: tid = 0x1c375c, 0x0000000000000010, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x10)
frame #0: 0x0000000000000010
frame #1: 0x00000001010669eb AppName`AppName.SleepStepRow.completed.getter : Swift.Bool(self=0x000060d0000814e0) + 59 at SleepSteps.swift:95
frame #2: 0x00000001010905a3 AppName`AppName.SleepStepsCheckboxTableViewCell.update (self=0x0000618000047480)() -> () + 1459 at SleepStepsCheckboxTableViewCell.swift:32
frame #3: 0x0000000101062a87 AppName`AppName.SleepStepsTableViewCell.rowContent.didset : Swift.Optional<AppName.SleepStepRow>(oldValue=nil, self=0x0000618000047480) + 39 at SleepStepsTableViewCell.swift:16
frame #4: 0x0000000101062b42 AppName`AppName.SleepStepsTableViewCell.rowContent.setter : Swift.Optional<AppName.SleepStepRow>(newValue=0x000060d0000814e0, self=0x0000618000047480) + 114 at SleepStepsTableViewCell.swift:0
frame #5: 0x00000001010b188b AppName`AppName.SleepStepsTableViewController.tableView (tableView=0x000061c000010880, indexPath=0xc000000000000016, self=0x0000617000063580)(__ObjC.UITableView, cellForRowAtIndexPath : __ObjC.NSIndexPath) -> __ObjC.UITableViewCell + 539 at SleepStepsTableViewController.swift:85
frame #6: 0x00000001010b1bcf AppName`@objc AppName.SleepStepsTableViewController.tableView (AppName.SleepStepsTableViewController)(__ObjC.UITableView, cellForRowAtIndexPath : __ObjC.NSIndexPath) -> __ObjC.UITableViewCell + 79 at SleepStepsTableViewController.swift:0
frame #7: 0x0000000103a504b3 UIKit`-[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 508
frame #8: 0x0000000103a2ffb1 UIKit`-[UITableView _updateVisibleCellsNow:isRecursive:] + 2846
frame #9: 0x0000000103a45e3c UIKit`-[UITableView layoutSubviews] + 213
frame #10: 0x00000001039d2973 UIKit`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 521
frame #11: 0x0000000102495de8 QuartzCore`-[CALayer layoutSublayers] + 150
frame #12: 0x000000010248aa0e QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 380
frame #13: 0x000000010248a87e QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 24
frame #14: 0x00000001023f863e QuartzCore`CA::Context::commit_transaction(CA::Transaction*) + 242
frame #15: 0x00000001023f974a QuartzCore`CA::Transaction::commit() + 390
frame #16: 0x00000001023f9db5 QuartzCore`CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 89
frame #17: 0x0000000102f5edc7 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
frame #18: 0x0000000102f5ed20 CoreFoundation`__CFRunLoopDoObservers + 368
frame #19: 0x0000000102f54b53 CoreFoundation`__CFRunLoopRun + 1123
frame #20: 0x0000000102f54486 CoreFoundation`CFRunLoopRunSpecific + 470
frame #21: 0x000000010753b9f0 GraphicsServices`GSEventRunModal + 161
frame #22: 0x0000000103959420 UIKit`UIApplicationMain + 1282
frame #23: 0x000000010106b56d AppName`main + 109 at AppDelegate.swift:15
frame #24: 0x000000010555e145 libdyld.dylib`start + 1

Steps to Reproduce

Replace Realm.Framework and RealmSwift.Framework and run your app.

Code Sample

This is the code at line 94, line 95 is the crashing one (second line in this snippet)

get {
    if Night.currentNight.completedSteps.contains(StringWrapper(string: hashString)) {
        return true
    } else {
        return false
    }
}

Night.currentNight just returns the last object from a Realm List<Night> and Night.currentNight.completedSteps is just a List<StringWrapper>

At the time of the crash there was a Night.currentNight object present and the Night.currentNight.completedsteps List was empty but not nil.

It's worth noting that I'm new to Realm and I may be doing something fundamentally wrong.

Version of Realm and Tooling

0.98.1 before and 0.98.3. Xcode 7.2.1.

mrackwitz commented 8 years ago

Where and how do you initialize Night.currentNight? That would need to happen from the main thread. Is that the case?

maciekish commented 8 years ago

It is called on the main thread, thats also where it crashes. I changed the code slighlty to always reuse the same Realm() object after configuring.

This is the implementation for currentNight:

class var currentNight: Night? {
    get {
        let allNights = ConfiguredRealm.realm.objects(Night)

        // Find night in progress
        if let lastNight = allNights.last {
            if !lastNight.completed {
                return lastNight
            }
        }

        return nil
    }
}

And this is ConfiguredRealm.realm:

class ConfiguredRealm {
    static var realm: Realm = {
        let config = Realm.Configuration(
            schemaVersion: 3,

            migrationBlock: { migration, oldSchemaVersion in
                if (oldSchemaVersion < 1) {
                    // Nothing to do!
                }
        })

        // Tell Realm to use this new configuration object for the default Realm
        Realm.Configuration.defaultConfiguration = config

        return try! Realm()
    }()
}

Is the above code the problem? Should i always get a new Realm() or is reuse like this fine?

The reason for this solution is that the configuration must be created and set (?) before Realm is accessed the first time, but the app could be launched in multiple ways (appDidFinishLaunching, state restoration etc) so this is a way to guarantee that.

Thanks for your time!

mrackwitz commented 8 years ago

It is called on the main thread, thats also where it crashes. I changed the code slighlty to always reuse the same Realm() object after configuring.

That's fine. Did that fixed the issue, you were facing?

Is the above code the problem? Should i always get a new Realm() or is reuse like this fine?

That's not necessary. But with the above code, you would neither be able to close the Realm nor get a correctly configured Realm for another thread than the main thread. You could setup the defaultConfiguration instead in your AppDelegate's initialize method. That should be enough. The first access to the Realm can then happen lazily.

maciekish commented 8 years ago

And suddenly as if by magic it started working. Git confirms that the only changes are still the two .Framework folders.

All i did was replace them again (after reverting earlier), enable zombie objects and address sanitizer and run the project in the profiler. After that it just worked, even after disabling zombie objects and address santizier.

I did clean, delete derived data and remove the app from the simulator before reporting the issue.

Any idea what happened here? It's a bit scary to be honest.

mrackwitz commented 8 years ago

Okay, well that's odd behavior. Perhaps something went wrong on the download? If the frameworks didn't even changed since then, I really can't say.

maciekish commented 8 years ago

That's fine. Did that fixed the issue, you were facing?

No, not until i did the above.

That's not necessary. But with the above code, you would neither be able to close the Realm nor get a correctly configured Realm for another thread than the main thread. You could setup the defaultConfiguration instead in your AppDelegate's initialize method. That should be enough. The first access to the Realm can then happen lazily.

Do you mean like this? (In the AppDelegate)

override init() {
    super.init()

    let config = Realm.Configuration(
        schemaVersion: 3,

        migrationBlock: { migration, oldSchemaVersion in
            if (oldSchemaVersion < 1) {
                // Nothing to do!
                // Realm will automatically detect new properties and removed properties
                // And will update the schema on disk automatically
            }
    })

    // Tell Realm to use this new configuration object for the default Realm
    Realm.Configuration.defaultConfiguration = config
}
maciekish commented 8 years ago

What am i missing by not being able to close the Realm?

mrackwitz commented 8 years ago

Yeah, the initializer is probably better than the +initialize method.

What am i missing by not being able to close the Realm?

You won't be able to reset the database contents by deleting it or seed your Realm with a bundled file.