Here's what's going on: I've got a singleton monitoring the device's event store for changes. I have a property called events
that I've wrapped in an eventsSignal
RACSignal
.
_eventsSignal = [RACAble(self.events) startWith:nil];
When the application finishes launching, it prompts the user for access to the calendars (standard approach) using requestAccessToEntityType:completion:
. The completion block executes on a background queue, so I dispatch back to the main queue:
-(void)promptForAccess {
[_store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self willChangeValueForKey:EKEventManagerAccessibleKeyPath];
_accessible = granted;
[self didChangeValueForKey:EKEventManagerAccessibleKeyPath];
if (_accessible) {
// load events
[_store reset];
[self refresh];
}
});
}];
}
the call to [self refresh]
loads new events from the event store, then calls
[self didChangeValueForKey:@"events"];
At this line, the app crashes.
-[__NSCFString sourceType]: unrecognized selector sent to instance 0x200d6c80
The full stack trace is below. I've tried removing the dispatch call, always scheduling the _eventSignal
on the main thread scheduler, and looking at everywhere that I'm subscribing to the event signal (it all looks good). Anything I could be missing?
EDIT:
I've isolate the problem down the following code. If the nextEventSignal
is subscribed to at all, then the crash happens about half the time. If I remove the second signal from the combineLatest:reduce
call, then it does not crash.
RACSignal *nextEventSignal = [[RACSignal combineLatest:@[eventManager.eventsSignal, eventManager.nextEventSignal, timerSignal] reduce:^id (NSArray *eventArray, EKEvent *nextEvent, NSDate *fireDate){
NSArray *filteredArray = [[eventArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL (EKEvent *event, NSDictionary *bindings) {
return [event.endDate isLaterThanDate:[NSDate date]] && !event.isAllDay;
}]] sortedArrayUsingComparator:^NSComparisonResult (id obj1, id obj2) {
return [[obj1 startDate] compare:[obj2 startDate]];
}];
if (filteredArray.count == 0) {
if (nextEvent.isAllDay) {
return nil;
}
else {
return nextEvent;
}
} else {
return filteredArray[0];
}
}] throttle:0.25f];
Here's the crash log.
thread #1: tid = 0x2503, 0x3b40f944 libobjc.A.dylib`objc_exception_throw, stop reason = breakpoint 2.1
frame #0: 0x3b40f944 libobjc.A.dylib`objc_exception_throw
frame #1: 0x33717f30 CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 184
frame #2: 0x3371664c CoreFoundation`___forwarding___ + 392
frame #3: 0x3366e208 CoreFoundation`__forwarding_prep_0___ + 24
frame #4: 0x33e0442c EventKit`-[EKCalendar type] + 68
frame #5: 0x33e49e10 EventKit`-[EKCalendar description] + 84
frame #6: 0x33f7d204 Foundation`_NSDescriptionWithLocaleFunc + 88
frame #7: 0x336f5430 CoreFoundation`__CFStringAppendFormatCore + 11160
frame #8: 0x3366c8a2 CoreFoundation`_CFStringCreateWithFormatAndArgumentsAux + 74
frame #9: 0x33f7ccfc Foundation`+[NSString stringWithFormat:] + 60
frame #10: 0x33e5427c EventKit`-[EKCalendarItem description] + 228
frame #11: 0x33e4ccae EventKit`-[EKEvent description] + 46
frame #12: 0x336a35d0 CoreFoundation`-[NSArray descriptionWithLocale:indent:] + 680
frame #13: 0x33f7d1f0 Foundation`_NSDescriptionWithLocaleFunc + 68
frame #14: 0x336f5430 CoreFoundation`__CFStringAppendFormatCore + 11160
frame #15: 0x3366c8a2 CoreFoundation`_CFStringCreateWithFormatAndArgumentsAux + 74
frame #16: 0x33f7ccfc Foundation`+[NSString stringWithFormat:] + 60
frame #17: 0x0010102a Upcoming`-[RACTuple description](self=0x1ed6dee0, _cmd=0x391cb2ce) + 182 at RACTuple.m:62
frame #18: 0x33f7d204 Foundation`_NSDescriptionWithLocaleFunc + 88
frame #19: 0x336f5430 CoreFoundation`__CFStringAppendFormatCore + 11160
frame #20: 0x3366c8a2 CoreFoundation`_CFStringCreateWithFormatAndArgumentsAux + 74
frame #21: 0x33f873de Foundation`-[NSString initWithFormat:arguments:] + 26
frame #22: 0x000f9e50 Upcoming`-[RACStream setNameWithFormat:](self=0x1eda2a70, _cmd=0x0012e18b, format=0x001ad984) + 436 at RACStream.m:56
frame #23: 0x000f26a2 Upcoming`+[RACSignal(self=0x001ab424, _cmd=0x0012e129, value=0x1ed6dee0) return:] + 222 at RACSignal.m:165
frame #24: 0x000fa926 Upcoming`__29-[RACStream(.block_descriptor=0x1edf57b0, value=0x1ed52170) map:]_block_invoke + 86 at RACStream.m:91
frame #25: 0x000fa13c Upcoming`__36-[RACStream(.block_descriptor=0x200f3970, value=0x1ed52170, stop=0x2fdc1580) flattenMap:]_block_invoke_2 + 44 at RACStream.m:72
frame #26: 0x000f39f4 Upcoming`__29-[RACSignal(.block_descriptor=0x200f3b70, x=0x1ed52170) bind:]_block_invoke178 + 56 at RACSignal.m:243
frame #27: 0x000ffbb6 Upcoming`-[RACSubscriber sendNext:](self=0x200f3a90, _cmd=0x00124ec2, value=0x1ed52170) + 294 at RACSubscriber.m:69
frame #28: 0x000deb04 Upcoming`__43-[RACSignal(.block_descriptor=0x200f3cc0) combineLatestWith:]_block_invoke_2 + 304 at RACSignal+Operations.m:462
frame #29: 0x000ded76 Upcoming`__43-[RACSignal(.block_descriptor=0x200f3df0, x=0x20196dc0) combineLatestWith:]_block_invoke463 + 282 at RACSignal+Operations.m:469
frame #30: 0x000ffbb6 Upcoming`-[RACSubscriber sendNext:](self=0x200f3d30, _cmd=0x00124ec2, value=0x20196dc0) + 294 at RACSubscriber.m:69
frame #31: 0x000f3694 Upcoming`__29-[RACSignal(.block_descriptor=0x1eda3610, x=0x20196dc0) bind:]_block_invoke_2157 + 76 at RACSignal.m:222
frame #32: 0x000ffbb6 Upcoming`-[RACSubscriber sendNext:](self=0x1edea190, _cmd=0x00124ec2, value=0x20196dc0) + 294 at RACSubscriber.m:69
frame #33: 0x000f2746 Upcoming`__31+[RACSignal(.block_descriptor=0x1edc5dd0, subscriber=0x1edea190) return:]_block_invoke + 106 at RACSignal.m:166
frame #34: 0x000f6a30 Upcoming`__37-[RACSignal(.block_descriptor=0x2fdc1c20) subscribe:]_block_invoke300 + 80 at RACSignal.m:386
frame #35: 0x00100aca Upcoming`-[RACSubscriptionScheduler schedule:](self=0x200c0e00, _cmd=0x0012de2e, block=0x2fdc1c20) + 542 at RACSubscriptionScheduler.m:40
frame #36: 0x000f650c Upcoming`-[RACSignal(self=0x2019a790, _cmd=0x0012de00, subscriber=0x1edea190) subscribe:] + 1300 at RACSignal.m:388
frame #37: 0x000f74ec Upcoming`-[RACSignal(self=0x2019a790, _cmd=0x0012f167, nextBlock=0x2fdc1ee4, errorBlock=0x2fdc1ec8, completedBlock=0x2fdc1ea8) subscribeNext:error:completed:] + 1124 at RACSignal.m:419
frame #38: 0x000f3528 Upcoming`__29-[RACSignal(.block_descriptor=0x200f3fe0, signal=0x2019a790) bind:]_block_invoke154 + 636 at RACSignal.m:230
frame #39: 0x000f3a22 Upcoming`__29-[RACSignal(.block_descriptor=0x200f4130, x=0x200365e0) bind:]_block_invoke178 + 102 at RACSignal.m:246
frame #40: 0x000ffbb6 Upcoming`-[RACSubscriber sendNext:](self=0x200f4050, _cmd=0x00124ec2, value=0x200365e0) + 294 at RACSubscriber.m:69
frame #41: 0x000f3694 Upcoming`__29-[RACSignal(.block_descriptor=0x1ed08f40, x=0x200365e0) bind:]_block_invoke_2157 + 76 at RACSignal.m:222
frame #42: 0x000ffbb6 Upcoming`-[RACSubscriber sendNext:](self=0x1ed077b0, _cmd=0x00124ec2, value=0x200365e0) + 294 at RACSubscriber.m:69
frame #43: 0x000f2746 Upcoming`__31+[RACSignal(.block_descriptor=0x202adf90, subscriber=0x1ed077b0) return:]_block_invoke + 106 at RACSignal.m:166
frame #44: 0x000f6a30 Upcoming`__37-[RACSignal(.block_descriptor=0x2fdc23c0) subscribe:]_block_invoke300 + 80 at RACSignal.m:386
frame #45: 0x00100aca Upcoming`-[RACSubscriptionScheduler schedule:](self=0x200c0e00, _cmd=0x0012de2e, block=0x2fdc23c0) + 542 at RACSubscriptionScheduler.m:40
frame #46: 0x000f650c Upcoming`-[RACSignal(self=0x20021f60, _cmd=0x0012de00, subscriber=0x1ed077b0) subscribe:] + 1300 at RACSignal.m:388
frame #47: 0x000f74ec Upcoming`-[RACSignal(self=0x20021f60, _cmd=0x0012f167, nextBlock=0x2fdc2684, errorBlock=0x2fdc2668, completedBlock=0x2fdc2648) subscribeNext:error:completed:] + 1124 at RACSignal.m:419
frame #48: 0x000f3528 Upcoming`__29-[RACSignal(.block_descriptor=0x200f48e0, signal=0x20021f60) bind:]_block_invoke154 + 636 at RACSignal.m:230
frame #49: 0x000f3a22 Upcoming`__29-[RACSignal(.block_descriptor=0x200f4b40, x=0x200134a0) bind:]_block_invoke178 + 102 at RACSignal.m:246
frame #50: 0x000ffbb6 Upcoming`-[RACSubscriber sendNext:](self=0x200f4950, _cmd=0x00124ec2, value=0x200134a0) + 294 at RACSubscriber.m:69
frame #51: 0x000b8e98 Upcoming`__86+[NSObject(.block_descriptor=0x200f4db0, target=0x200c2a80, observer=0x200c2a80, change=0x200134a0) rac_signalWithChangesFor:keyPath:options:observer:]_block_invoke_2 + 96 at NSObject+RACPropertySubscribing.m:55
frame #52: 0x000c8d46 Upcoming`-[RACKVOTrampoline observeValueForKeyPath:ofObject:change:context:](self=0x200f4d90, _cmd=0x359cf919, keyPath=0x200c9340, object=0x200c2a80, change=0x200134a0, context=0x001b4444) + 542 at RACKVOTrampoline.m:97
frame #53: 0x340095f2 Foundation`NSKVONotify + 34
frame #54: 0x34007b46 Foundation`-[NSKeyValueObservance observeValueForKeyPath:ofObject:change:context:] + 310
frame #55: 0x33fa3b84 Foundation`NSKeyValueNotifyObserver + 272
frame #56: 0x33fa37dc Foundation`NSKeyValueDidChange + 336
frame #57: 0x33f7dcba Foundation`-[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 94
frame #58: 0x0005a732 Upcoming`-[EKEventManager loadEvents](self=0x200c2a80, _cmd=0x00126d19) + 2834 at EKEventManager.m:216
frame #59: 0x000591ac Upcoming`-[EKEventManager refresh](self=0x200c2a80, _cmd=0x33e9fe1c) + 64 at EKEventManager.m:82
frame #60: 0x000590d6 Upcoming`__33-[EKEventManager promptForAccess]_block_invoke_2(.block_descriptor=0x2005d020) + 246 at EKEventManager.m:74
frame #61: 0x3b829792 libdispatch.dylib`_dispatch_call_block_and_release + 10
frame #62: 0x3b8295da libdispatch.dylib`_dispatch_client_callout + 22
frame #63: 0x3b82ce44 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 228
frame #64: 0x336e81b0 CoreFoundation`__CFRunLoopRun + 1288
frame #65: 0x3365b23c CoreFoundation`CFRunLoopRunSpecific + 356
frame #66: 0x3365b0c8 CoreFoundation`CFRunLoopRunInMode + 104
frame #67: 0x3723a33a GraphicsServices`GSEventRunModal + 74
frame #68: 0x355772b8 UIKit`UIApplicationMain + 1120
frame #69: 0x0003e074 Upcoming`main(argc=1, argv=0x2fdc3d20) + 116 at main.m:15
frame #70: 0x3b83cb20 libdyld.dylib`start + 4
The problem turned out to be that I was holding onto references to EventKit objects after resetting the store. The objects become "invalid" when the store is reset, which I guess means they become dangling pointers (awesome). The combineLatest:reduce:
was failing when the first key changed because the latest value on the second signal was invalid. Sending nil
to both signals just before resetting the store seems to have worked.
-(void)promptForAccess {
[_store requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self willChangeValueForKey:EKEventManagerAccessibleKeyPath];
_accessible = granted;
[self didChangeValueForKey:EKEventManagerAccessibleKeyPath];
if (_accessible) {
// need to set these to nil before resetting the store.
[self willChangeValueForKey:EKEventManagerEventsKeyPath];
_events = nil;
[self didChangeValueForKey:EKEventManagerEventsKeyPath];
[self willChangeValueForKey:EKEventManagerNextEventKeyPath];
_nextEvent = nil;
[self didChangeValueForKey:EKEventManagerNextEventKeyPath];
// load events
[_store reset];
[self refresh];
}
});
}];
}