SectionFetchRequest/Result value vs reference

At 24:56 in "Bring Core Data concurrency to Swift and SwiftUI" there's the following discussion:

But here's the important part. Changes to the request are committed whenever the results getter is called, so to update both the sorting and the sectioning safely... I need to update the configuration on a reference to the results that I've pulled into a local.

The code in question is a property:

@SectionedFetchRequest(
    sectionIdentifier: \.day,
    sortDescriptors: [SortDescriptor(\Quake.time, order: .reverse)])
private var quakes: SectionedFetchResults<String, Quake>

And it is updated with:

.onChange(of: selectedSort) { _ in
    let sortBy = sorts[selectedSort.index]
    let config = quakes
    config.sectionIdentifier = sortBy.section
    config.sortDescriptors = sortBy.descriptors
}

It's unclear what the value/reference semantics here are. quakes looks like a value type, but this is clearly treating it as a reference type. But if it's a reference type, why is the config local variable important? It feels like some kind of magic is happening here.

Why is this local variable necessary, and how would I know this?

Replies

I think I've finally figured out what's going on, but I'm at a loss for how I would be able to guess this subtlety from the code or the documentation.

I believe what is happening is that, as was noted in the session, quakes has a mutable getter ("changes to the request are committed whenever the results getter is called"). So if you wrote the obvious code:

// This is fine because quakes hasn't changed yet
quakes.sectionIdentifier = sortBy.section
// This would apply the `sectionIdentifier` change and trigger an unnecessary fetch (?)
quakes.sortDescriptors = sortBy.descriptors

So the recommended code avoids this by only touching the property wrapper once. Despite quakes and config both being structs, they have reference semantics. But despite both referencing the same "object," they have different access semantics due to the property wrapper.

This seems incredibly subtle, contrary to standard Swift value/reference semantics, and unmentioned in the documentation. Am I understanding it correctly? Is there any way I should be able guess this behavior?

Am I understanding it correctly?

Yes

Is there any way I should be able guess this behavior?

Not really? As Jordan hinted (https://mobile.twitter.com/UINT_MIN/status/1407035602864910340) the setters are nonmutating, which is a strong hint that there are shenanigans going on, but doesn't tell you anything about the shenanigans themselves.

In general the "perform fetch on getter" behaviour is a compromise based on what was possible in the moment. For completeness, the downsides of not using that local variable are that the fetch will be performed multiple times (which is not performant) and one of them may fail due to discontiguous sections (SwiftUI should swallow the error but you'll see logging). It should "work", but it may generate a fault.

I'm hoping these calisthenics won't be necessary in the long arc of history but the talk (and the docs) have to reflect the perils of the moment.