Search code examples
ioscore-datanspredicate

How to format a core data many to many predicate with an intermediate model


I have the following core data model:

enter image description here

I have a view controller that contains a particular SkillGroup. I would like to make a fetch request that contains the records for all skills in that skillGroup. Normally I would think it would be something like

request.predicate = NSPredicate(format: "skill IN %@.skills", skillGroup!)

However I needed to create the intermediate "SkillGroupItem" models so that I could keep track of the displayIndex for each skill in a skillGroup. I am thinking of something like:

request.predicate = NSPredicate(format: "ANY %@.skillGroupItems.skill = skillProgress.skill", skillGroup!)

so any of the skillGroup's skillGroupItems' skill is equal to the records skillProgress object.skill...

but that throws this error:

CoreData: annotation: to-many relationship fault "skillGroupItems" for objectID 0xb9291ca50355a7c1 <x-coredata://33325602-3973-46E7-8400-45922DD97A05/SkillGroup/p1> fulfilled from database.  Got 1 rows
CoreData: sql: SELECT DISTINCT t0.Z_ENT, t0.Z_PK, t0.Z_OPT, t0.ZRECORDTYPE, t0.ZTIMESTAMP, t0.ZSKILLPROGRESS, t0.ZIMAGEFILENAME, t0.ZTEXT, t0.ZTHUMBNAILFILENAME, t0.ZVIDEOFILENAME FROM ZRECORD t0 JOIN ZSKILLPROGRESS t1 ON t0.ZSKILLPROGRESS = t1.Z_PK WHERE ? =  t1.ZSKILL ORDER BY t0.ZTIMESTAMP
2021-04-20 13:37:43.482875-0700 SweatNetOffline[38167:3014868] -[__NSSingleObjectSetI longLongValue]: unrecognized selector sent to instance 0x600002a69360
CoreData: annotation: total fetch execution time: 0.0013s for 0 rows.
2021-04-20 13:37:43.484519-0700 SweatNetOffline[38167:3014868] [error] error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x60000117d5e0> , -[__NSSingleObjectSetI longLongValue]: unrecognized selector sent to instance 0x600002a69360 with userInfo of (null)
CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x60000117d5e0> , -[__NSSingleObjectSetI longLongValue]: unrecognized selector sent to instance 0x600002a69360 with userInfo of (null)
2021-04-20 13:37:43.503515-0700 SweatNetOffline[38167:3014868] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSSingleObjectSetI longLongValue]: unrecognized selector sent to instance 0x600002a69360'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007fff20421af6 __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x00007fff20177e78 objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff204306f7 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0
    3   CoreFoundation                      0x00007fff20426036 ___forwarding___ + 1489
    4   CoreFoundation                      0x00007fff20428068 _CF_forwarding_prep_0 + 120
    5   CoreData                            0x00007fff25114346 -[NSSQLiteConnection execute] + 1416
    6   CoreData                            0x00007fff2538acfa _newFetchedRowsForFetchPlan_ST + 1234
    7   CoreData                            0x00007fff2537b1c5 _executeFetchRequest + 55
    8   CoreData                            0x00007fff252cd352 -[NSSQLFetchRequestContext executeRequestCore:] + 41
    9   CoreData                            0x00007fff253430a3 -[NSSQLStoreRequestContext executeRequestUsingConnection:] + 405
    10  CoreData                            0x00007fff2531468f __52-[NSSQLDefaultConnectionManager handleStoreRequest:]_block_invoke + 56
    11  CoreData                            0x00007fff2527bdbd __37-[NSSQLiteConnection performAndWait:]_block_invoke + 28
    12  libdispatch.dylib                   0x00000001094479c8 _dispatch_client_callout + 8
    13  libdispatch.dylib                   0x0000000109456bfe _dispatch_lane_barrier_sync_invoke_and_complete + 132
    14  CoreData                            0x00007fff2527bca3 -[NSSQLiteConnection performAndWait:] + 134
    15  CoreData                            0x00007fff253145a4 -[NSSQLDefaultConnectionManager handleStoreRequest:] + 273
    16  CoreData                            0x00007fff2531afd8 -[NSSQLCoreDispatchManager routeStoreRequest:] + 283
    17  CoreData                            0x00007fff2524b284 -[NSSQLCore dispatchRequest:withRetries:] + 161
    18  CoreData                            0x00007fff2524681e -[NSSQLCore processFetchRequest:inContext:] + 88
    19  CoreData                            0x00007fff2511a461 -[NSSQLCore executeRequest:withContext:error:] + 1072
    20  CoreData                            0x00007fff2522595a __65-[NSPersistentStoreCoordinator executeRequest:withContext:error:]_block_invoke.797 + 3219
    21  CoreData                            0x00007fff2521e02a -[NSPersistentStoreCoordinator _routeHeavyweightBlock:] + 222
    22  CoreData                            0x00007fff2511993e -[NSPersistentStoreCoordinator executeRequest:withContext:error:] + 1684
    23  CoreData                            0x00007fff25117ef2 -[NSManagedObjectContext executeFetchRequest:error:] + 885
    24  CoreData                            0x00007fff252da7d0 __43-[NSFetchedResultsController performFetch:]_block_invoke + 417
    25  CoreData                            0x00007fff2514ce63 developerSubmittedBlockToNSManagedObjectContextPerform + 154
    26  CoreData                            0x00007fff2514cd4a -[NSManagedObjectContext performBlockAndWait:] + 197
    27  CoreData                            0x00007fff252dcc13 -[NSFetchedResultsController _recursivePerformBlockAndWait:withContext:] + 145
    28  CoreData                            0x00007fff252da523 -[NSFetchedResultsController performFetch:] + 231
    29  SweatNetOffline                     0x0000000108ece722 $s15SweatNetOffline22ProgressViewControllerC11viewDidLoadyyF + 1122
    30  SweatNetOffline                     0x0000000108ecf8db $s15SweatNetOffline22ProgressViewControllerC11viewDidLoadyyFTo + 43
    31  UIKitCore                           0x00007fff23f5e36e -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 88
    32  UIKitCore                           0x00007fff23f62cd7 -[UIViewController loadViewIfRequired] + 1084
    33  UIKitCore                           0x00007fff23f630c1 -[UIViewController view] + 27
    34  UIKitCore                           0x00007fff23e82c37 -[UINavigationController _startCustomTransition:] + 1254
    35  UIKitCore                           0x00007fff23e991d6 -[UINavigationController _startDeferredTransitionIfNeeded:] + 684
    36  UIKitCore                           0x00007fff23e9a5e8 -[UINavigationController __viewWillLayoutSubviews] + 150
    37  UIKitCore                           0x00007fff23e7ad9e -[UILayoutContainerView layoutSubviews] + 217
    38  UIKitCore                           0x00007fff24bf8504 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2924
    39  QuartzCore                          0x00007fff27b1bc2b -[CALayer layoutSublayers] + 258
    40  QuartzCore                          0x00007fff27b2219d _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 575
    41  QuartzCore                          0x00007fff27b2df3f _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 65
    42  QuartzCore                          0x00007fff27a6d44c _ZN2CA7Context18commit_transactionEPNS_11TransactionEdPd + 496
    43  QuartzCore                          0x00007fff27aa4233 _ZN2CA11Transaction6commitEv + 783
    44  QuartzCore                          0x00007fff27aa53ef _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 79
    45  CoreFoundation                      0x00007fff2038f1f8 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
    46  CoreFoundation                      0x00007fff20389a77 __CFRunLoopDoObservers + 547
    47  CoreFoundation                      0x00007fff2038a01a __CFRunLoopRun + 1113
    48  CoreFoundation                      0x00007fff203896d6 CFRunLoopRunSpecific + 567
    49  GraphicsServices                    0x00007fff2c257db3 GSEventRunModal + 139
    50  UIKitCore                           0x00007fff24696cf7 -[UIApplication _run] + 912
    51  UIKitCore                           0x00007fff2469bba8 UIApplicationMain + 101
    52  SweatNetOffline                     0x0000000108e3d9fb main + 75
    53  libdyld.dylib                       0x00007fff2025a3e9 start + 1
    54  ???                                 0x0000000000000003 0x0 + 3
)
libc++abi.dylib: terminating with uncaught exception of type NSException
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSSingleObjectSetI longLongValue]: unrecognized selector sent to instance 0x600002a69360'
terminating with uncaught exception of type NSException
CoreSimulator 732.18.6 - Device: iPhone 11 (E5E1A675-AA38-45CE-A047-1C5569598381) - Runtime: iOS 14.4 (18D46) - DeviceType: iPhone 11

Im tripped up by how to use the ANY in this case. so any of the skillGroupItems makes sense.. but ANY skillGroup.skillGroupItems.skill seems bad to me. You can't look for a .skill on an array of skillGroupItems.

Any advice on how to accomplish this? Thanks


Solution

  • The predicate is parsed using the entity you are fetching (in your case, Record) as the reference point. So you would use this:

    request.predicate = NSPredicate(format: "recordType == %@", givenRecordType)
    

    to create a predicate based on the attribute recordType, and

    request.predicate = NSPredicate(format: "skillProgress == %@", givenSkillProgressObject)
    

    to create a predicate based on the relationship skillProgress (if givenSkillProgress was a SkillProgress object you already had fetched). If you do not have a SkillProgress object in memory, but you know an attribute value you want to match for it, you can use the corresponding keypath expression to refer to the attribute of the related object, eg:

    request.predicate = NSPredicate(format: "skillProgress.isActive == YES")
    

    will fetch those Record objects whose related SkillProgress object is active. You can continue to "traverse" the relationship tree by using additional elements in the key path. So:

    request.predicate = NSPredicate(format: "skillProgress.skill.id == %@", givenSkillID)
    

    will fetch those Record objects whose related SkillProgress is in turn related to a Skill whose id attribute matches the givenSkillID. For the skillGroupItems relationship (on the Skill entity), because it is to-many, you will need to use "ANY" to indicate that any of the related objects should match your criteria:

    request.predicate = NSPredicate(format: "ANY skillProgress.skill.skillGroupItems == %@", givenSkillGroupItem)
    

    The final step is then to use the skillGroup relationship from SkillGroupItem to get the full key path from Record to SkillGroup:

    request.predicate = NSPredicate(format: "ANY skillProgress.skill.skillGroupItems.skillGroup == %@", givenSkillGroup)