Realm - Why are notification blocks triggered when a write transaction begins? - ios

I noticed today that Realm notification blocks are triggered when a write transaction begins. Intuitively I would have thought that ending a write transaction would trigger collection notifications with the changes that just happened, but I tracked down a crash in my code today resulting from a notification block being called when a write transaction begins.
Relevant stack frames before my notification handling is invoked:
frame #17: 0x0000000102535b98 Realm`RLMNotificationToken* RLMAddNotificationBlock<realm::Results>(this=0x00000001742974f0, changes=0x000000016fdf9de0, err=<unavailable>) block_pointer, bool)::'lambda'(realm::CollectionChangeSet const&, std::exception_ptr)::operator()(realm::CollectionChangeSet const&, std::exception_ptr) const + 608 at RLMCollection.mm:345
frame #18: 0x0000000102535700 Realm`realm::CollectionChangeCallback::Impl<RLMNotificationToken* RLMAddNotificationBlock<realm::Results>(objc_object*, realm::Results&, void (objc_object*, RLMCollectionChange*, NSError*) block_pointer, bool)::'lambda'(realm::CollectionChangeSet const&, std::exception_ptr)>::after(this=0x00000001742974e8, change=0x000000016fdf9de0) + 56 at collection_notifications.hpp:157
frame #19: 0x000000010248723c Realm`realm::CollectionChangeCallback::after(this=0x000000016fdf9dd0, c=0x000000016fdf9de0) + 64 at collection_notifications.hpp:122
frame #20: 0x0000000102487198 Realm`auto realm::_impl::CollectionNotifier::after_advance(this=0x000000016fdf9f90, lock=0x000000016fdf9ef0, callback=0x0000000103b62520)::$_9::operator()<std::__1::unique_lock<std::__1::mutex>, realm::_impl::CollectionNotifier::Callback>(std::__1::unique_lock<std::__1::mutex>&, realm::_impl::CollectionNotifier::Callback&) const + 156 at collection_notifier.cpp:326
frame #21: 0x0000000102479780 Realm`void realm::_impl::CollectionNotifier::for_each_callback<realm::_impl::CollectionNotifier::after_advance()::$_9>(this=0x00000001049d3e18, fn=0x000000016fdf9f90)::$_9&&) + 236 at collection_notifier.cpp:367
frame #22: 0x0000000102479688 Realm`realm::_impl::CollectionNotifier::after_advance(this=0x00000001049d3e18) + 28 at collection_notifier.cpp:315
frame #23: 0x000000010247ba3c Realm`realm::_impl::NotifierPackage::after_advance(this=0x000000016fdfa5e8) + 352 at collection_notifier.cpp:474
frame #24: 0x00000001026c8de4 Realm`void (anonymous namespace)::advance_with_notifications<realm::_impl::transaction::begin(context=0x0000000174221480, sg=0x0000000104020200, func=0x000000016fdfa540, notifiers=0x000000016fdfa5e8)::$_1>(realm::BindingContext*, realm::SharedGroup&, realm::_impl::transaction::begin(realm::SharedGroup&, realm::BindingContext*, realm::_impl::NotifierPackage&)::$_1&&, realm::_impl::NotifierPackage&) + 1152 at transact_log_handler.cpp:674
frame #25: 0x00000001026c8958 Realm`realm::_impl::transaction::begin(sg=0x0000000104020200, context=0x0000000174221480, notifiers=0x000000016fdfa5e8) + 56 at transact_log_handler.cpp:702
frame #26: 0x00000001024de620 Realm`realm::_impl::RealmCoordinator::promote_to_write(this=0x0000000103b0e108, realm=0x0000000103b0e498) + 328 at realm_coordinator.cpp:741
frame #27: 0x00000001026766b4 Realm`realm::Realm::begin_transaction(this=0x0000000103b0e498) + 552 at shared_realm.cpp:483
frame #28: 0x000000010262df3c Realm`::-[RLMRealm beginWriteTransaction](self=0x00000001740a9fc0, _cmd="beginWriteTransaction") + 48 at RLMRealm.mm:437
In a particular use case in my code, one of my notification callbacks creates a new set of RLMResults to display in a table and adds a notification block to it. Adding the notification block raises the expected exception in that case: Cannot create asynchronous query while in a write transaction.
That rule is easy to understand, I'm really just curious about why beginning a write transaction would trigger collection notifications, instead of waiting until after the transaction.

If a write was made on a different thread between when the Realm was last refreshed and when you begin a write transaction, beginning the write transaction will implicitly refresh the Realm first. If this results in anything changing, any applicable notifications will be sent immediately to notify you of the change.

Related

AudioKit SynthOne crashes after returning from background

We're using a "skinless" version of AK SynthOne in our app (i.e., just the engine, with a collection of presets), and seeing a consistent crash when returning from background. In order to spare the user's battery when the sequence is not playing, if the user goes to background, we call AudioKit.stop(). This allows the CPU usage to fall to zero (happy customers!), but on returning we see a consistent crash when SynthOne tries to handle its first note:
void S1NoteState::startNoteHelper(int noteNumber, int velocity, float frequency) {
oscmorph1->freq = frequency; // <-- EXC_BAD_ACCESS here!
oscmorph2->freq = frequency;
subOsc->freq = frequency;
fmOsc->freq = frequency;
...
The backtrace:
* thread #13, stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
* frame #0: 0x0000000100fc5d78 Spliqs`S1NoteState::startNoteHelper(this=0x0000000108345500, noteNumber=56, velocity=54, frequency=207.652328) at S1NoteState.mm:105:21
frame #1: 0x0000000100fd6764 Spliqs`S1DSPKernel::turnOnKey(this=0x00000001088a3790, noteNumber=56, velocity=54, frequency=207.652328) at S1DSPKernel+toggleKeys.mm:85:14
frame #2: 0x0000000100fcabd0 Spliqs`S1DSPKernel::startNote(this=0x00000001088a3790, noteNumber=56, velocity=54, frequency=207.652328) at S1DSPKernel+startStopNotes.mm:45:9
frame #3: 0x0000000100fc1298 Spliqs`::-[S1AudioUnit startNote:velocity:frequency:](self=0x00000001088a3600, _cmd="startNote:velocity:frequency:", note='8', velocity='6', frequency=207.652328) at S1AudioUnit.mm:98:13
frame #4: 0x00000001014cf86c Spliqs`AKSynthOne.play(noteNumber=56, velocity=54, frequency=207.65233610331066, channel=0, self=0x0000000280de4a00) at AKSynthOne.swift:223:21
frame #5: 0x00000001016dbda4 Spliqs`AudioKit.AKPolyphonicNode.play(noteNumber: Swift.UInt8, velocity: Swift.UInt8, channel: Swift.UInt8) -> () + 328
frame #6: 0x000000010135a120 Spliqs`Conductor.handleMIDI(data1=146, data2=56, data3=54, self=0x000000010820efe0) at Conductor.swift:392:23
frame #7: 0x00000001013597a4 Spliqs`Conductor.handle(event=AudioKit.AKMIDIEvent # 0x000000016f488ba0, self=0x000000010820efe0) at Conductor.swift:374:26
frame #8: 0x0000000101358de4 Spliqs`closure #1 in Conductor.setUpMIDIHandler(packetList=0x000000016f489e98, _0=nil, self=0x000000010820efe0) at Conductor.swift:326:30
frame #9: 0x0000000101157f48 Spliqs`thunk for #escaping #callee_guaranteed (#unowned UnsafePointer<MIDIPacketList>, #unowned UnsafeMutableRawPointer?) -> () at <compiler-generated>:0
frame #10: 0x00000001d42467b8 CoreMIDI`LocalMIDIReceiverList::HandleMIDIIn(unsigned int, unsigned int, void*, MIDIPacketList const*) + 164
frame #11: 0x00000001d4246618 CoreMIDI`MIDIProcess::RunMIDIInThread() + 132
frame #12: 0x00000001d425d1e8 CoreMIDI`XThread::RunHelper(void*) + 28
frame #13: 0x00000001d4262624 CoreMIDI`CAPThread::Entry(CAPThread*) + 92
frame #14: 0x00000001c3eef908 libsystem_pthread.dylib`_pthread_body + 132
frame #15: 0x00000001c3eef864 libsystem_pthread.dylib`_pthread_start + 48
frame #16: 0x00000001c3ef7dcc libsystem_pthread.dylib`thread_start + 4
We've tried everything from just starting AudioKit, through to completely re-initializing SynthOne, but it's always the same.
We've been stuck on this one for a while, so any thoughts greatly appreciated.
UPDATE: Actually, I'm afraid I have to resuscitate this question. Although the .pause() approach in my answer below worked for going to/from background we also have a use-case where we move to offline render mode to export audio. That process definitely does require us to stop the engine (in order to switch modes), and we're getting the same SynthOne-related crash when handling the first event after AudioKit.start(). Does anybody understand why we'd hit an EXC_BAD_ACCESS when returning? I'm guessing that memory has been incorrectly reallocated/reclaimed(?) somehow, but how can I prevent it?
I suppose, more broadly, the question would be: how do I safely start/stop the AudioKit engine when using SynthOne.
Erm... yikes... how about .stopEngine() and .startEngine()...? an rtfm moment, for sure.
For anyone who might face a similar problem, these use AudioKit.engine.pause() and AudioKit.engine.start(), as opposed to using stop().

Swift Static Array - pointer being freed was not allocated

I have a static array metricsTransactionData in ServiceRequest class. I am invoking multiple request continuously and sometimes I get this error "error for object 0x10b874cb0: pointer being freed was not allocated" on this line ServiceRequest.metricsTransactionData.append(samp). Pls help
public func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
for mem in metrics.transactionMetrics {
var samp = MetricsData()
samp.requestDate = mem.requestStartDate
samp.responseDate = mem.responseEndDate
samp.url = mem.request.url?.absoluteString
ServiceRequest.metricsTransactionData.append(samp)
}
}
Stack trace:
* thread #3, queue = 'NSOperationQueue 0x6000001edb20 (QOS: UNSPECIFIED)', stop reason = signal SIGABRT
frame #0: 0x0000000106897b66 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00000001068d1080 libsystem_pthread.dylib`pthread_kill + 333
frame #2: 0x0000000106644c45 libsystem_c.dylib`abort + 127
frame #3: 0x00000001067986e4 libsystem_malloc.dylib`malloc_vreport + 545
frame #4: 0x00000001067988d5 libsystem_malloc.dylib`malloc_report + 152
frame #5: 0x00000001049bd910 libswiftCore.dylib`_swift_release_dealloc + 16
frame #6: 0x0000000104980017 libswiftCore.dylib`assignWithTake value witness for Swift.Array + 23
frame #7: 0x00000001046f3a5a libswiftCore.dylib`(extension in Swift):Swift._ArrayBufferProtocol._arrayOutOfPlaceUpdate<A where A1: Swift._PointerFunction, A.Element == A1.Element>(inout Swift._ContiguousArrayBuffer<A.Element>, Swift.Int, Swift.Int, A1) -> () + 1146
frame #8: 0x000000010470aa11 libswiftCore.dylib`Swift.Array._copyToNewBuffer(oldCount: Swift.Int) -> () + 209
frame #9: 0x000000010470aaf0 libswiftCore.dylib`Swift.Array._makeUniqueAndReserveCapacityIfNotUnique() -> () + 192
frame #10: 0x000000010470ad98 libswiftCore.dylib`Swift.Array.append(A) -> () + 24
The error tells you that it is a very bad idea to use a static/global array that will be updated from various background tasks. Swift arrays are not thread-safe.
Means that your array gets resized by one task while another might already do the same, resulting in a bad memory access. Likely calling (at least) append in a DispatchQueue.main.async would fix the problem if you really have to do it that way, but if you want to maintain the order, you would need to handle synchronization yourself.
But in any way i strongly recommend that you find another solution that prevents that kind of write access to the array directly. Globally writable arrays are bad.

CoreData: Object being released in block "error: Mutating a managed object after it has been removed from its context"

This code relies on the CoreData setup from an older forked version of Robbie Hanson's XMPPFramework.
I am retrofitting a data structure to be backed by a managed object CoreData and basically keep a reference to the NSManagedObjectID inside the data structure in question and then have accessors to get/set the data structure properties with the backing data in Core Data.
I am intermittently getting the following error (similar to):
CoreData: error: Mutating a managed object 0xd00000000014000a <x-coredata://90C02501-4756-44F0-ABA6-B725192772A6/ZZXMPPOneToOneInboxItemCoreDataObject/p5> (0x1742971b0) after it has been removed from its context.
I am getting the error both when setting and getting. Obviously the "getting" error is not a "mutating a managed object" error but the stack trace is the same with the release as a culprit.
Here is an example setter:
- (void)setMamUUID:(NSString *)mamUUID
{
ZZXMPPInboxCoreDataStorage *storage = [ZZXMPPInboxCoreDataStorage sharedInstance];
[storage executeBlock:^{
ZZXMPPOneToOneInboxItemCoreDataObject *backingObject = (ZZXMPPOneToOneInboxItemCoreDataObject *)[storage inboxItemInsideBlockWithObjectID:self.itemCoreDataObjectID];
backingObject.mamUUID = mamUUID;
}];
}
and a getter:
- (NSString *)mamUUID
{
ZZXMPPInboxCoreDataStorage *storage = [ZZXMPPInboxCoreDataStorage sharedInstance];
__block NSString *_mamUUID;
[storage executeBlock:^{
ZZXMPPOneToOneInboxItemCoreDataObject *backingObject = (ZZXMPPOneToOneInboxItemCoreDataObject *)[storage inboxItemInsideBlockWithObjectID:self.itemCoreDataObjectID];
_mamUUID = [backingObject mamUUID];
}];
return _mamUUID;
}
The inboxItemInsideBlockWithObjectID: is a helper routine meant to run inside an executeBlock: and looks like this
- (ZZXMPPInboxBaseMemberCoreDataObject *)inboxItemInsideBlockWithObjectID:(NSManagedObjectID *)objectID
{
ZZXMPPInboxBaseMemberCoreDataObject *inboxItem = nil;
inboxItem = [[self managedObjectContext] objectWithID:objectID];
if (nil == inboxItem) {
NSLog(#"..Unable to retrieve an Inbox Item with objectID %#.", objectID);
}
return inboxItem;
}
The "storage" object referenced in both is the XMPPCoreDataStorage object from the XMPPFramework and it's executeBlock: has this as the main code:
dispatch_sync(storageQueue, ^{ #autoreleasepool {
block();
// Since this is a synchronous request, we want to return as quickly as possible.
// So we delay the maybeSave operation til later.
dispatch_async(storageQueue, ^{ #autoreleasepool {
[self maybeSave:OSAtomicDecrement32(&pendingRequests)];
}});
}});
The error is an objc_object::release() (backtrace to follow) and is showing in the debugger at the end of the block in the "setter" code above.
Here is the complete backtrace:
(lldb) bt
* thread #1, queue = 'ZZXMPPRoomHybrid', stop reason = EXC_BAD_ACCESS (code=1, address=0x3a8e9bec8)
frame #0: 0x0000000180459704 libobjc.A.dylib`objc_object::release() + 8
* frame #1: 0x000000010009c1f4 MyApp`__29-[ZZConversation setMamUUID:]_block_invoke((null)=0x000000016fda6838) at ZZConversation.m:280
frame #2: 0x00000001003da11c MyApp`__36-[XMPPCoreDataStorage executeBlock:]_block_invoke((null)=0x000000016fda67b0) at XMPPCoreDataStorage.m:1063
frame #3: 0x0000000103c55218 libdispatch.dylib`_dispatch_client_callout + 16
frame #4: 0x0000000103c61dc8 libdispatch.dylib`_dispatch_barrier_sync_f_invoke + 156
frame #5: 0x0000000103c65adc libdispatch.dylib`_dispatch_barrier_sync_f_slow + 452
frame #6: 0x00000001003da098 MyApp`-[XMPPCoreDataStorage executeBlock:](self=0x00000001702e5100, _cmd="executeBlock:", block=0x000000010009c148) at XMPPCoreDataStorage.m:1061
frame #7: 0x000000010009c0d8 MyApp`-[ZZConversation setMamUUID:](self=0x0000000174223d60, _cmd="setMamUUID:", mamUUID=#"50a35939-f48c-4011-8cfe-b5edf3641e20") at ZZConversation.m:276
frame #8: 0x00000001000a3c80 MyApp`-[ZZConversation mamRequestWasSentWithUUID:](self=0x0000000174223d60, _cmd="mamRequestWasSentWithUUID:", uuid=#"50a35939-f48c-4011-8cfe-b5edf3641e20") at ZZConversation.m:886
frame #9: 0x00000001000af894 MyApp`__58-[ZZXMPPMAMCoreDataStorage requestMessageArchiveWithUser:]_block_invoke((null)=0x00000001742567a0) at ZZXMPPMAMCoreDataStorage.m:73
frame #10: 0x0000000103c55218 libdispatch.dylib`_dispatch_client_callout + 16
frame #11: 0x0000000103c61334 libdispatch.dylib`_dispatch_continuation_pop + 708
frame #12: 0x0000000103c6ff94 libdispatch.dylib`_dispatch_source_latch_and_call + 204
frame #13: 0x0000000103c57300 libdispatch.dylib`_dispatch_source_invoke + 836
frame #14: 0x0000000103c5a05c libdispatch.dylib`_dispatch_main_queue_callback_4CF + 652
frame #15: 0x00000001819b6810 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
frame #16: 0x00000001819b43fc CoreFoundation`__CFRunLoopRun + 1660
frame #17: 0x00000001818e22b8 CoreFoundation`CFRunLoopRunSpecific + 444
frame #18: 0x0000000183396198 GraphicsServices`GSEventRunModal + 180
frame #19: 0x00000001879297fc UIKit`-[UIApplication _run] + 684
frame #20: 0x0000000187924534 UIKit`UIApplicationMain + 208
frame #21: 0x000000010006a688 MyApp`main(argc=5, argv=0x000000016fda7a60) at main.m:16
frame #22: 0x00000001808c55b8 libdyld.dylib`start + 4
(lldb)
I am not sure why the managed object would be released mid-block. The #autoreleasepool surrounds the block and should not release anything while the "sync" block is running.
This happens intermittently and I can run the app several times before it will surface again. Usually, it appears on a "getter" and not a "setter", but since it happens on both and the code for both is similar I don't think that matters.
Why would this managed object instance be released at this point?

UIStoryboardScene deallocates its controller unexpectedly without changing retain count

I am running unit tests against my view controllers. There are two view controllers in this suite, one of them has a segue to the other in the storyboard file.
In tests for both controllers, I create my test subject in same way: i get a storyboard with storyboardWithName:factory:bundle: (Typhoon for dependency injection uses the factory thing). Then, I use storyboard.instantiateViewControllerWithIdentifier: to get the VC.
In tests for my first view controller, all tests pass. Importantly, the view controller's deinit method is called ONLY when the vc itself goes out of scope in the test cases, or a new instance becomes the subject and the old one has its retain count reduced to 0.
however, in tests for the second VC, the tests fail in either of these cases (the VC goes out of scope or a new one becomes the subject) because for some reason there is a call being made to [UIStoryboardScene dealloc] which in turn calls deinit on the controller, even though its retain count should still be 1 (retained by the test case). ARC sends another release message to the VC when I set a new instance as the subject, and I get EXC_BAD_ACCESS (CODE=i386, GPFLT).
The [UIStoryboardScene dealloc] happens in a bunch of compiled code in XCTest framework (copied here), but I believe it's happening because the test case is ending, even though it shouldn't.
* thread #1: tid = 0x57cf91, 0x00000001089953a3 sbprod`Mode2ViewController.__deallocating_deinit(self=0x00007fd2bbdde5d0) + 19 at Mode2ViewController.swift:68, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
* frame #0: 0x00000001089953a3 sbprod`Mode2ViewController.__deallocating_deinit(self=0x00007fd2bbdde5d0) + 19 at Mode2ViewController.swift:68
frame #1: 0x0000000108995492 sbprod`#objc Mode2ViewController.__deallocating_deinit + 34 at Mode2ViewController.swift:0
frame #2: 0x000000010a1ff702 UIKit`-[UIStoryboardScene dealloc] + 36
frame #3: 0x000000010b04dafe libobjc.A.dylib`objc_object::sidetable_release(bool) + 232
frame #4: 0x000000010b04e0b8 libobjc.A.dylib`(anonymous namespace)::AutoreleasePoolPage::pop(void*) + 488
frame #5: 0x0000000115841f37 XCTest`__24-[XCTestCase invokeTest]_block_invoke_2 + 430
frame #6: 0x0000000115876613 XCTest`-[XCTestContext performInScope:] + 190
frame #7: 0x0000000115841d78 XCTest`-[XCTestCase invokeTest] + 169
frame #8: 0x00000001158423a2 XCTest`-[XCTestCase performTest:] + 459
frame #9: 0x000000011583fcf7 XCTest`-[XCTestSuite performTest:] + 396
frame #10: 0x000000011583fcf7 XCTest`-[XCTestSuite performTest:] + 396
frame #11: 0x000000011583fcf7 XCTest`-[XCTestSuite performTest:] + 396
frame #12: 0x000000011582cb10 XCTest`__25-[XCTestDriver _runSuite]_block_invoke + 51
frame #13: 0x000000011584db4c XCTest`-[XCTestObservationCenter _observeTestExecutionForBlock:] + 640
frame #14: 0x000000011582ca55 XCTest`-[XCTestDriver _runSuite] + 453
frame #15: 0x000000011582d7d1 XCTest`-[XCTestDriver _checkForTestManager] + 259
frame #16: 0x0000000115877a9a XCTest`_XCTestMain + 628
So:
Why is the UIStoryboardScene being created at all? I don't want a scene, just a VC, for my tests. It doesn't appear to happen in the previous test case. I think the only differences as far as the storyboard cares between the two VCs is that one of them has a segue into it, and the other out of it.
If the UIStoryboardScene is in fact existing in both cases, why is it deiniting my VC before it ought to?
My test cases are also not very different from one another in scope, however this test case passes some closures to places and I'm not 100% on how closures affect ARC.
EDIT: relevant code shown here.
In my test case, which is made using Quick:
override func spec() {
var subject: Mode2ViewController!
let presentDisplayString = "DesiredString"
describe("Mode2ViewController") {
describe("loading") {
describe("date and location") {
context("when location is available") {
beforeEach {
let system = MockSystemComponents.CreateWith(location: true, groups: nil)
let assembly = ApplicationAssembly().activateWithCollaboratingAssemblies([
system
])
//crash occurs on next line, before the 2nd test case, because the
//old subject has already been deallocated (by UIStoryboardScene)
//but reassigning this var, which had retains it, triggers a release
subject = assembly.mode2ViewController() as! Mode2ViewController
let _ = subject.view
}
it("records the location") {
expect(subject).notTo(beNil())
}
it("displays the location as a meaningful string") {
expect(subject.locationLabel.text).to(equal(presentDisplayString))
}
}
}
}
}
}
Note: the stack trace given above (first edit) is from a breakpoint I inserted in Mode2ViewController.deinit().
Turns out that the problem was that objective c treats things which begin with the keyword new differently with regards to their retain count. Typhoon can't handle methods that begin with new for this reason (see issue).
Solution: rename my typhoon method and the object being generated has the correct retain count.

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
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.
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.

Resources