Reputation: 22559
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
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
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