I have a realm model that stores time line (i am making video editing app) and quite frequently it crushes on accessing it's RMArray property. The app is already shipped and I haven't experienced it myself but my crushlytics notifies me about this crash quite oftenly. Here is the crash log:
Fatal Exception: RLMException
Object has been deleted or invalidated.
Thread : Fatal Exception: RLMException
0 CoreFoundation 0x2614d45f __exceptionPreprocess + 126
1 libobjc.A.dylib 0x3407ec8b objc_exception_throw + 38
2 VideoEditor 0x00293919 RLMGetArray(RLMObjectBase*, unsigned int, NSString*) (RLMRealm_Private.hpp:38)
3 VideoEditor 0x0018a1b4 VideoEditor.RLMProject.setTimeLineModel (VideoEditor.RLMProject)(VideoEditor.TimeLineModel, beginWriteTransaction : Swift.Bool) -> () (RealmModels.swift:147)
4 VideoEditor 0x0025eb9c VideoEditor.VideoEditorAPI.saveProject (VideoEditor.VideoEditorAPI)(Swift.Optional<VideoEditor.IProject>, timeLine : VideoEditor.TimeLineModel, name : Swift.String, filterID : Swift.Int, image : ObjectiveC.UIImage) -> Swift.ImplicitlyUnwrappedOptional<VideoEditor.IProject> (VideoEditorAPI.swift:42)
5 VideoEditor 0x00164754 @objc VideoEditor.ProjectEditorViewController.saveProject (VideoEditor.ProjectEditorViewController)(Swift.ImplicitlyUnwrappedOptional<ObjectiveC.NSNotification>) -> () (ProjectEditorViewController.swift:514)
6 CoreFoundation 0x26105e31 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
7 CoreFoundation 0x260616cd _CFXNotificationPost + 1784
8 Foundation 0x26db7dd9 -[NSNotificationCenter postNotificationName:object:userInfo:] + 72
9 UIKit 0x296cae2d -[UIApplication _deactivateForReason:notify:] + 528
10 UIKit 0x298d2dd7 -[UIApplication _handleNonLaunchSpecificActions:forScene:withTransitionContext:] + 1846
11 UIKit 0x298caafd -[UIApplication workspace:didReceiveActions:] + 80
12 FrontBoardServices 0x2ca180a9 __31-[FBSSerialQueue performAsync:]_block_invoke + 12
13 CoreFoundation 0x26113fe5 __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
14 CoreFoundation 0x261132a9 __CFRunLoopDoBlocks + 216
15 CoreFoundation 0x26111de3 __CFRunLoopRun + 1714
16 CoreFoundation 0x2605f3b1 CFRunLoopRunSpecific + 476
17 CoreFoundation 0x2605f1c3 CFRunLoopRunInMode + 106
18 GraphicsServices 0x2d5bf201 GSEventRunModal + 136
19 UIKit 0x296c943d UIApplicationMain + 1440
20 MerryVideoEditor 0x0028c88f main (main.m:16)
21 libdyld.dylib 0x3460aaaf start + 2
Here is the RLMProject code:
protocol IProject{
var name: String { get set }
var filterID: Int { get set }
var filterIntensity: CGFloat { get set }
/// duration in seconds
var duration: Int { get set }
var dateCreated: NSDate { get }
func setTimeLineModel(timeLine: TimeLineModel, beginWriteTransaction: Bool)
// should be done by ProjectImporter
func getTimeLineModel() -> TimeLineModel
var videoAssets: RLMArray { get }
var soundtracks: RLMArray { get }
}
class RLMProject: RLMObject, IProject, Printable {
dynamic var videoAssets: RLMArray = RLMArray(objectClassName: RLMMediaAsset.className())
dynamic var soundtracks: RLMArray = RLMArray(objectClassName: RLMMediaAsset.className())
dynamic var name: String = ""
dynamic var filterID: Int = 0
dynamic var filterIntensity: CGFloat = 1
dynamic var duration: Int = 0
dynamic var dateCreated: NSDate = NSDate()
dynamic var idValue: Int = 0
func setTimeLineModel(timeLine: TimeLineModel, beginWriteTransaction: Bool = true) {
func updateArray(array: RLMArray, withAssetsArray assetsArray: [MediaAsset], type: MediaType){
array.removeAllObjects()
for asset in assetsArray{
let model = RLMMediaAsset()
model.setMediaAsset(asset)
model.setType(type)
array.addObject(model)
RLMRealm.defaultRealm().addObject(model)
}
}
if beginWriteTransaction { RLMRealm.defaultRealm().beginWriteTransaction() }
if videoAssets.invalidated { videoAssets = RLMArray(objectClassName: RLMMediaAsset.className()) }
if soundtracks.invalidated { soundtracks = RLMArray(objectClassName: RLMMediaAsset.className()) }
updateArray(videoAssets, withAssetsArray: timeLine.videoAssets, .Video)
updateArray(soundtracks, withAssetsArray: timeLine.soundtracks, .Soundtrack)
duration = Int(CMTimeGetSeconds(timeLine.totalDuration))
dateCreated = NSDate()
if beginWriteTransaction { RLMRealm.defaultRealm().commitWriteTransaction() }
}
func getTimeLineModel() -> TimeLineModel {
let timeLine = TimeLineModel()
timeLine.videoAssets = videoAssets.map { ($0 as RLMMediaAsset).getMediaAsset() }
timeLine.soundtracks = soundtracks.map { ($0 as RLMMediaAsset).getMediaAsset() }
return timeLine
}
}
extension RLMArray {
func map<U>(transform: (RLMObject) -> U) -> [U]{
var array: [U] = []
for object in self{
array.append(transform(object))
}
return array
}
}
Does anybody has an idea what is wrong with my code?
When the
RLMProject
itself is invalidated, it won't be possible to check forvideoAssets.invalidated
orsoundtracks.invalidated
, which is why your stack trace shows that an uncaught exception is being thrown inRLMGetArray
.This implies that the object has been deleted from the realm before you call
setTimeLineModel
. Please look forrealm.deleteObject(_:)
in your code to see where this might be happening.Finally, a few general tips to make your code a little safer and simpler:
Instead of
RLMRealm.defaultRealm()
everywhere inside yourRLMObject
, you should use itsrealm
property. This way, if you decide to change the location of your realm at some point, your code inside yourRLMObject
will continue to work.Also, rather than create an extension on
RLMArray
to add map, you could use Swift's freemap
function.