Mazyod
Mazyod

Reputation: 22559

NSManagedObject changes memory address?

I have a transient, transformable property set for my MO subclass [FeedItem], and in a category, I provide lazy loaded access:

- (id)images
{
    if (!self.sImages) {
        self.sImages = [[self.imageEntitiesClass alloc] initWithModel:self];
    }
    return self.sImages;
}

- (void)setImages:(id)images
{
    self.sImages = images;
}

Now, within -[FeedItem.managedObjectContext performBlock:] I call -[FeedItem prefetchImages]. What that does, is performs the following call stack:

-[FeedItem prefetchImages]
-[FeedItemImages avatar]
-[FeedItem avatarURL]
- MULTI-THREAD ASSERTION

Within -[FeedItemImages avatar] method, I call self.model.avatarURL, but by checking the debugger, self.model.managedObjectContext is different from the encapsulating MOC, so it makes sense that the assertion is triggered .. but, why is the MOC different? I explicitly pass self in the -[FeedItemImages init], so they should be the same object?

To confirm this issue, I have disabled the caching, and returned a new object every time, and the app worked great:

- (id)images
{
#warning TODO - underlying object is changing randomly?
    /** For some weird reason, when we cache image entities, then attempt to
     *  retrieve an image, we sometimes trigger a multithreading assertions 
     *  breakpoint. Debugger shows the owner of the image entity is different
     *  from the model the image entity is referencing ¯\_(ツ)_/¯
     *
     *  Possible solutions:
     *  The Bad:
     *  Current solution. Easy, but very ineffecient.
     *  The Ugly:
     *  Cache the image entities object privately, and expose a different 
     *  property that reassigns self every time.
     *  The Good:
     *  Firgure out when the object mutates (awake from fetch, or some other
     *  callback of CoreData) and invalidate the object there.
     */
    return [[self.imageEntitiesClass alloc] initWithModel:self];
}

This was working perfectly when we had the root MOC as main, and created children MOC on the fly to perform object mapping.


backtrace:

    frame #0: [...] CoreData`+[NSManagedObjectContext __Multithreading_Violation_AllThatIsLeftToUsIsHonor__] + 4
    frame #1: [...] CoreData`_sharedIMPL_pvfk_core + 221
  * frame #2: [...] Telly`Telly.TLYUserImages.feedAction.getter : Telly.TLYImageEntity(self=0x00007f84ca5cf6c0) + 416 at TLYUserImages.swift:26
    frame #3: [...] Telly`@objc Telly.TLYUserImages.feedAction.getter : Telly.TLYImageEntity + 34 at TLYUserImages.swift:0
    frame #4: [...] Foundation`-[NSObject(NSKeyValueCoding) valueForKey:] + 251
    frame #5: [...] Foundation`-[NSArray(NSKeyValueCoding) valueForKey:] + 437
    frame #6: [...] Foundation`-[NSObject(NSKeyValueCoding) valueForKeyPath:] + 245
    frame #7: [...] Foundation`-[NSArray(NSKeyValueCoding) valueForKeyPath:] + 435
    frame #8: [...] Foundation`-[NSObject(NSKeyValueCoding) valueForKeyPath:] + 261
    frame #9: [...] Foundation`-[NSArray(NSKeyValueCoding) valueForKeyPath:] + 435
    frame #10: [...] Foundation`-[NSObject(NSKeyValueCoding) valueForKeyPath:] + 261
    frame #11: [...] Foundation`-[NSArray(NSKeyValueCoding) valueForKeyPath:] + 435
    frame #12: [...] Telly`Telly.TLYMappingMeta.prefetch (target=AnyObject at 0x000000011e8ac858, self=0x00007f84ca423040)(forTarget : Swift.AnyObject) -> () + 361 at TLYMappingMeta.swift:75
    frame #13: [...] Telly`@objc Telly.TLYMappingMeta.prefetch (Telly.TLYMappingMeta)(forTarget : Swift.AnyObject) -> () + 54 at TLYMappingMeta.swift:0
    frame #14: [...] Telly`-[TLYMapTask _tag:with:using:in:](self=0x00007f84cecd64f0, _cmd=0x000000010aa12ee9, items=0x00007f84ca6d12e0, feedId=0x00007f84ce81ddf0, mapMeta=0x00007f84ca423040, moc=0x00007f84c9c89500) + 179 at TLYMapTask.m:42
    frame #15: [...] Telly`__39-[TLYMapTask _map:toMOC:sync:callback:]_block_invoke(.block_descriptor=<unavailable>) + 1920 at TLYMapTask.m:127
    frame #16: [...] CoreData`developerSubmittedBlockToNSManagedObjectContextPerform + 201
    frame #17: [...] libdispatch.dylib`_dispatch_client_callout + 8
    frame #18: 0x00000001107a76a7 libdispatch.dylib`_dispatch_queue_drain + 2176
    frame #19: 0x00000001107a6cc0 libdispatch.dylib`_dispatch_queue_invoke + 235
    frame #20: 0x00000001107aa3b9 libdispatch.dylib`_dispatch_root_queue_drain + 1359
    frame #21: 0x00000001107abb17 libdispatch.dylib`_dispatch_worker_thread3 + 111
    frame #22: 0x0000000110b2d637 libsystem_pthread.dylib`_pthread_wqthread + 729
    frame #23: 0x0000000110b2b40d libsystem_pthread.dylib`start_wqthread + 13

ImageEntities.swift

import Foundation

/** Each model object is composed of an imageEntities subclass that
 *  holds the image entities associated with that model.
 */
class TLYImageEntities: NSObject {

    unowned let model: AnyObject

    init(model: AnyObject) {
        self.model = model
    }
}

Example subclass of ImageEntities. Notice how self.user.avatarURL access the MO subclass property:

TLYUserImages:

import Foundation

class TLYUserImages: TLYImageEntities {

    var user: TVUser {
        return model as! TVUser
    }

    lazy var profileHeader: TLYImageEntity = TLYImageEntity(
        listItem: self.user,
        imageURL: self.user.avatarURL,
        formatName: TLYImageFormatUserProfileHeader,
        processor: TVImageProcessor.avatarProcessor()
    )

    ...

}

TVUser+Aggregator, which provides the image entities class:

@implementation TVUser (Aggregator)

- (Class)imageEntitiesClass
{
    return [TLYUserImages class];
}

...

@end

Upvotes: 0

Views: 181

Answers (2)

Mundi
Mundi

Reputation: 80265

Because initWithModel:self is called on an instance of a managed object subclass, it seems logical that the address changes. self refers to the instance variable, so whenever you call this with a different variable, the content of the arguments of your cryptic method will also be different. Perhaps your clever method to just insert a new object is a little bit too clever.

Maybe you should dispense with this hardly readable and evidently buggy method and do something more intuitive and straight-forward, like a class method that takes a context and returns a new object inserted into that context.

I would concur with the time-honored quote of the previous poster, Marcus S. Zarra:

The mantra is the same. Keep the code simple. Do not be clever, do not be lazy. Do it right and it will be easy to maintain. Be clever or lazy and you are only punishing yourself and your users.

Upvotes: 0

Marcus S. Zarra
Marcus S. Zarra

Reputation: 46718

What does [[self.imageEntitiesClass alloc] initWithModel:self] do? Specifically was does -initWithModel: do? It is very strange to be calling an alloc init like that.

Looking into that method will help to determine what this interesting issue is.

Upvotes: -1

Related Questions