Closed enricenrich closed 8 years ago
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 RLMObject
s in an unsafe_unretained
variable?
@enricenrich just checking in to see if you ever made any progress of figuring out what was going on here?
Closing as this is likely not a Realm issue.
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:
NSArray *objectsArray
RLMResults *array
.RLMResults array
into the objectsArray
.objectsArray
instead of array
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?
@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.
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?
Thanks for the sample project, we'll take a look!
There are two factors at play here:
-[RLMResults objectAtIndex:]
returns a new instance of your RLMObject
subclass each time it is called. It does not retain the existing instances.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.
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
).
@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
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.
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.
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
@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?
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.
I don't know if setAssociatedObject is available in swift or not.
It sure is! See http://nshipster.com/swift-objc-runtime/#associated-objects
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.
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.
@jhoughjr it's because the NSTableViewDelegate
protocol doesn't pass around objects, you get a row index as an Int
and optionally an NSTableColumn
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.
@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.
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?
I'm creating an NSOutlineView populated by objects saved on Realm. The object is called
Person
. And this is how it looks:The
contacts
array returns a query. Thosecontacts
are child of theperson
, but they also have the same object class,Person
.And this is the view controller:
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?