Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EXC_BAD_ACCESS crash when using NSOutlineView #1734

Closed
enricenrich opened this issue Apr 6, 2015 · 23 comments

Comments

Projects
None yet
@enricenrich
Copy link

commented Apr 6, 2015

I'm creating an NSOutlineView populated by objects saved on Realm. The object is called Person. And this is how it looks:

@interface Person : RLMObject

@property NSString *name;
@property NSInteger indent;
@property NSInteger order;

// Ignored properties
@property NSArray *contacts;

The contacts array returns a query. Those contacts are child of the person, but they also have the same object class, Person.

And this is the view controller:

#import "MainWindowController.h"
#import "Person.h"

@interface MainWindowController () <NSOutlineViewDataSource, NSOutlineViewDelegate>

@property (strong) IBOutlet NSOutlineView *outlineView;

@property (nonatomic, strong) RLMResults *array;
@property (nonatomic, strong) RLMNotificationToken *notification;

@end

@implementation MainWindowController

- (id)init
{
    self = [super initWithWindowNibName:@"MainWindow"];

    if (self) {
        __weak typeof(self) weakSelf = self;

        self.notification = [RLMRealm.defaultRealm addNotificationBlock:^(NSString *note, RLMRealm *realm) {
            [weakSelf.outlineView reloadData];
        }];

        self.array = [[Person objectsWhere:@"indent == 1"] sortedResultsUsingProperty:@"order" ascending:YES];
    }

    return self;
}

- (void)windowDidLoad
{
    [super windowDidLoad];

    [self.outlineView setRowHeight:50];
}

#pragma mark - NSOutlineView

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
    if (item == nil) {
        return self.array[index];
    }
    else if ([item isKindOfClass:[Person class]]) {
        return [item contacts][index];
    }

    return nil;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    if (item == nil) {
        return NO;
    }
    else if ([item isKindOfClass:[Person class]]) {
        return [item contacts].count > 0;
    }

    return NO;
}

- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    if (item == nil) {
        return self.array.count;
    }
    else if ([item isKindOfClass:[Person class]]) {
        return [item contacts].count;
    }

    return 0;
}

- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
    NSView *cellView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 250, 0)];

    NSTextField *titleTextField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 15, 250, 18)];
    [titleTextField setEditable:NO];
    [titleTextField setBordered:NO];
    titleTextField.backgroundColor = [NSColor clearColor];
    titleTextField.textColor = [NSColor blackColor];
    titleTextField.focusRingType = NSFocusRingTypeNone;
    titleTextField.stringValue = [item name];
    [cellView addSubview:titleTextField];

    return cellView;
}

The "root" cells are displayed correctly, but then, when I click the arrow to expand one of those root cells, the app crashes with an EXC_BAD_ACCESS error. I used NSZombie to know more about this crash, and that's what it tells me:

-[RLMAccessor_v0_Person retain]: message sent to deallocated instance 0x60800016ba00

I debugged all the data source methods of the NSOutlineView when tapping the arrow to expand the row, but no method is called.

Any idea of what's happening?

@segiddins

This comment has been minimized.

Copy link
Contributor

commented Apr 7, 2015

Hi @enricenrich, it's hard to tell if this is actually a Realm issue or not from the code snippet you shared. I'm not super familiar with AppKit, but I'd recommend double-checking that you're not storing RLMObjects in an unsafe_unretained variable?

@segiddins segiddins added the pending label Apr 7, 2015

@segiddins

This comment has been minimized.

Copy link
Contributor

commented Apr 27, 2015

@enricenrich just checking in to see if you ever made any progress of figuring out what was going on here?

@jpsim

This comment has been minimized.

Copy link
Contributor

commented May 1, 2015

Closing as this is likely not a Realm issue.

@jpsim jpsim closed this May 1, 2015

@jpsim jpsim removed the pending label May 1, 2015

@lm2s

This comment has been minimized.

Copy link

commented Nov 23, 2015

For future reference of anyone having this problem, like I did.

Instead of using the RLMResults array directly in - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item.

Copy the objects into a NSArray to make sure that they are not released and then use the NSArray.

Using the above code as an example:

  • Declare a NSArray *objectsArray
  • Implement a setter for RLMResults *array.
  • In this setter copy the objects in RLMResults array into the objectsArray.
  • Use the objectsArray instead of array
@gf3

This comment has been minimized.

Copy link

commented Feb 22, 2016

i ran into the same issue and ended up solving it in a similar way as @lm2s—however this doesn't seem like a very performant solution.

are there any examples of using realm properly with NSOutlineView?

@jpsim

This comment has been minimized.

Copy link
Contributor

commented Feb 22, 2016

@gf3 I we have yet to receive confirmation that this is a realm issue, and if so, how we can reproduce this to identify the cause. If you care to provide enough information for us to do this, we'll be happy to take a closer look.

@gf3

This comment has been minimized.

Copy link

commented Feb 22, 2016

i've created a demo project with instructions on how to reproduce the issue as well as the solution both @lm2s and i have implemented. maybe you guys can recommend a better approach?

@jpsim jpsim added the T:Help label Feb 22, 2016

@jpsim jpsim reopened this Feb 22, 2016

@jpsim

This comment has been minimized.

Copy link
Contributor

commented Feb 22, 2016

Thanks for the sample project, we'll take a look!

@bdash

This comment has been minimized.

Copy link
Contributor

commented Feb 23, 2016

There are two factors at play here:

  1. -[RLMResults objectAtIndex:] returns a new instance of your RLMObject subclass each time it is called. It does not retain the existing instances.
  2. NSOutlineView does not retain the objects returned by the -outlineView:child:ofItem: data source method.

The result is that by the time NSOutlineView calls other data source or delegate methods (in @gf3's test case this is often -outlineView:isGroupItem:), the object returned by -outlineView:child:ofItem: has been deallocated.

@bdash

This comment has been minimized.

Copy link
Contributor

commented Feb 23, 2016

Due to the two factors mentioned in my previous comment, you do need to explicitly store the objects returned by RLMResults somewhere to ensure they live long enough for NSOutlineView. Eagerly storing the objects into an array as you describe is certainly the simplest way to achieve this. There are alternatives with different tradeoffs (e.g., you could reduce the up front cost at the expense of more complex code by only storing the objects as they're first retrieved by NSOutlineView).

@gf3

This comment has been minimized.

Copy link

commented Feb 23, 2016

@bdash (if you have time) could you provide an example of better solution to this problem. i'm hoping to use Realm to build a chat application where users could receive thousands of messages per room and it seems like the current array-wrap solution would be very expensive

@harryworld

This comment has been minimized.

Copy link

commented Feb 26, 2016

I am having EXC_BAD_ACCESS when expandItem is called (similar to expand group in UI)

When I try to use debugger to po item in viewForTableColumn delegate method, it shows as [Deleted Object], but it is the Realm Object if I log it using dump(item)

My solution to this, is copying the array, and call expandItem as late as possible (in my case viewDidAppear)

Hope this provides an idea about the issue.

@jpsim

This comment has been minimized.

Copy link
Contributor

commented Mar 9, 2016

To hold a strong reference to Realm objects when getting them from a Results (which does not hold strong references to all its returned objects, unlike NSArray), you can place them in any collection that strongly hold their contents such as NSArray.

So you could have a wrapper to -[RLMResults objectAtIndex:] which places the objects in an array before returning them:

@@ -7,6 +7,7 @@

 @property (nonatomic, strong) RLMResults *array;
 @property (nonatomic, strong) RLMNotificationToken *notification;
+@property (nonatomic, strong) NSMutableArray *stronglyHeldRealmObjects;

 @end

@@ -24,6 +25,7 @@
         }];

         self.array = [[Person objectsWhere:@"indent == 1"] sortedResultsUsingProperty:@"order" ascending:YES];
+        self.stronglyHeldRealmObjects = [NSMutableArray array];
     }

     return self;
@@ -40,14 +42,19 @@

 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
 {
+    id returnItem = nil;
     if (item == nil) {
-        return self.array[index];
+        returnItem = self.array[index];
     }
     else if ([item isKindOfClass:[Person class]]) {
-        return [item contacts][index];
+        returnItem = [item contacts][index];
     }

-    return nil;
+    if (returnItem != nil) {
+        [self.stronglyHeldRealmObjects addObject:returnItem];
+    }
+
+    return returnItem;
 }

Ideally, you'd also be removing objects from the array when they're no longer used, but that would be a bit trickier to do. One way to do this would be to use associated objects with a RETAIN association, to make the outline view strongly reference the Realm object:

@@ -1,5 +1,8 @@
 #import "MainWindowController.h"
 #import "Person.h"
+#import <objc/runtime.h>
+
+static char kRealmAssociatedObjectKey;

 @interface MainWindowController () <NSOutlineViewDataSource, NSOutlineViewDelegate>

@@ -40,14 +43,19 @@

 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
 {
+    id returnItem = nil;
     if (item == nil) {
-        return self.array[index];
+        returnItem = self.array[index];
     }
     else if ([item isKindOfClass:[Person class]]) {
-        return [item contacts][index];
+        returnItem = [item contacts][index];
+    }
+
+    if (returnItem != nil) {
+        objc_setAssociatedObject(outlineView, kRealmAssociatedObjectKey, returnItem, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
     }

-    return nil;
+    return returnItem;
 }

I haven't fully tested this code, so if you run into issues with it written exactly as-is, please let me know and I'll spend more time to fully work out the kinks.

@jhoughjr

This comment has been minimized.

Copy link

commented Mar 10, 2016

I just thought I would chime in on this as I've ran into a similar issue in RealmSwift regarding this I think.
I've been searching for a solution for two days now :(
I'll keep looking a bit more. The error in swift is fairly useless, but it does seem to be related to NSOutlineView.
When I changed one to NSTableView, the issue resolved. But when i refactored the outline view I actually needed I ran into the same issue.
I've tried copying the Results I'm using to drive my outline into an Array but that doesn't seem to work.

@jpsim

This comment has been minimized.

Copy link
Contributor

commented Mar 10, 2016

@jhoughjr have you tried applying the strategies suggested in this thread? You mention you've tried copying the Realm objects into an Array and that not working. Can you please elaborate? How about the associated object approach?

@jhoughjr

This comment has been minimized.

Copy link

commented Mar 10, 2016

I'm going to take a closer look at the project that was shared here now that I'm fresh. I don't know if setAssociatedObject is available in swift or not.

@jpsim

This comment has been minimized.

Copy link
Contributor

commented Mar 12, 2016

I don't know if setAssociatedObject is available in swift or not.

It sure is! See http://nshipster.com/swift-objc-runtime/#associated-objects

@jhoughjr

This comment has been minimized.

Copy link

commented Mar 12, 2016

Thanks for the info, I'm getting ready to take a stab at it again with a fresh mind.
Been busy with other things the last couple of days. I hope it works as I really need NSOutlineView for this project.

@jhoughjr

This comment has been minimized.

Copy link

commented Mar 14, 2016

I've gotten things working by maintaining an Array I update with the Results I need to display.
It's somewhat optimized in that I dump it before I reload.
I use a second Array to hold the objects for an expanded cell's children since it is a different type.
I am a bit curious as to why this isn't an issue for NSTableView, since NSOutlineView is a subclass of it.

@gf3

This comment has been minimized.

Copy link

commented Mar 14, 2016

@jhoughjr it's because the NSTableViewDelegate protocol doesn't pass around objects, you get a row index as an Int and optionally an NSTableColumn

@jpsim

This comment has been minimized.

Copy link
Contributor

commented Mar 17, 2016

I think we've sufficiently addressed the questions raised here, and provided a handful of potential solutions, so I'm closing this issue. If anyone has any further questions or issues related to this, please file a new GitHub issue referencing this one.

@jpsim jpsim closed this Mar 17, 2016

@cliftonlabrum

This comment has been minimized.

Copy link

commented Nov 24, 2017

@harryworld I know this post is old, but I ran into the issue of getting EXC_BAD_ACCESS when using expandItem. It happened because inside isItemExpandable I was allowing it to return true for the lowest-level items that didn't have any children. It turned out to not be related to Realm.

@duncangroenewald

This comment has been minimized.

Copy link

commented Jun 7, 2019

I have just run into the same issue when trying to delete items from the outline view. It seems this is not possible because outlineView seems to try and access properties of the objects during the delete.

      ```

realm.beginWrite()
realm.delete(item)

        do {
            try realm.commitWrite()

            self.items?.remove(at: selectedRow). // Note this is an Array of items

            self.outlineView?.beginUpdates()
            self.outlineView?.removeItems(at: IndexSet(integer: selectedRow), inParent: nil, withAnimation: NSTableView.AnimationOptions.slideUp)
            self.outlineView?.endUpdates()
            
        } catch {...

Has anyone been able to use outlineView with Realm objects at all? 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.