Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift concurrency #29

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft

Conversation

rcasula
Copy link

@rcasula rcasula commented Aug 14, 2022

Update the Library to the new swift-concurrency standard.
I was thinking if removing the composable architecture dependency makes sense.

This PR is still in draft because I have to:

  • update documentation
  • eventually remove the composable-architecture dependency

Please let me know if this PR is any good!

@stephencelis
Copy link
Member

@rcasula Thanks for looking into this! We definitely have plans to adopt this new style soon, but haven't had time to look into it. This looks good! Just a couple things we need to figure out in the future:

  1. Does the manager need to be protected in an actor? And what concurrency warnings show up when we turn on -Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks?
  2. We'd probably want to coordinate this change with a library rename. Something like core-location-client or core-location-dependency.

Going to keep this open for now while we find the time to figure out those details. Thanks for getting this work started though!

@3a4oT
Copy link

3a4oT commented Jun 20, 2023

Hey, what is the status of this PR? What needs to be done to wrap up the new release?

manager.dismissHeadingCalibrationDisplay()
#endif
delegate: {
AsyncStream { continuation in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given Dependencies define global instances, I think we need to ensure all dependencies' APIs support being called from multiple places of the application, possibly simultaneously/concurrently.

As it is, every time delegate is called, a new LocationManagerDelegate is created which will make any previous returned delegate stream not receive any new events. There's also the issue of CLLocationManager.delegate not being atomic on iOS, which can result in data races.

A possible solution for this could be to return a new stream for each caller, with the difference that all streams would share a single LocationManagerDelegate instance underneath that would be set as the single CLLocationManager.delegate (possibly when CLLocationManager is instantiated, avoiding data races). The LocationManagerDelegate could be made an actor managing a collection of streams (creation/deletion), forwarding delegate events accordingly.

Does this make sense? I can try to give this a go.

PS: Thanks for the awesome work 🙏🏼

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the meantime, opened MR rcasula#2 which hopefully (also) addresses this issue 🤓

@p4checo
Copy link

p4checo commented Sep 14, 2023

For completeness' sake, following a discussion in this PF's slack thread, to ensure CLLocationManager is accessed from the main RunLoop an alternative solution could be to use a MainActorIsolated structure as introduced here.

It appears to ensure CLLocationManager's APIs are invoked on the Main RunLoop, so it has the benefit of avoiding the Task { @MainActor } and not requiring sprinkling @MainActor in all API closures, making it less verbose and possibly less error prone as well if more APIs are added in the future.

Seems like an interesting approach, but ideally someone with more experience in swift concurrency can chime in, or at least we have one more approach to consider 🤓

@p4checo
Copy link

p4checo commented Sep 21, 2023

@rcasula can this MR be marked as ready for review? What documentation updates are missing? Thanks!

Package.swift Outdated
// "-Xfrontend", "-enable-actor-data-race-checks",
// ])
// )
//}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be enabled?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do, SPM complains about unsafe flags being used when integrating it in other packages/projects

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh, that's a good point. Just thinking loud here. What if we introduce ENV varible for local development and CI.
Something like :

if ProcessInfo.processInfo.environment["COMPOSABLE_DEVELOPER"] == "1" {
settings.append(.unsafeFlags([
     "-Xfrontend",
     "-warn-concurrency",
     "-Xfrontend",
     "-enable-actor-data-race-checks"
     ]))
}

Little incovinience to define variable locally but it can be documented on the README for developers.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's definitely an interesting idea! Let's see what the rest have to say 🤓

@3a4oT
Copy link

3a4oT commented Oct 5, 2023

friendly ping, can we please get an feedback?

@3a4oT
Copy link

3a4oT commented Jul 30, 2024

Hello, any chances to proceed with it?

rcasula and others added 17 commits November 24, 2024 13:58
When testing the current (Swift Concurrency) approach, the delegate
method calls were not being invoked, and after some investigation it
seems the problem was related with the `RunLoop` in which the
`CLLocationManager` was created. With the latest `Dependencies`/`TCA`
implementations, dependencies' access isn't bound to the `@MainActor`,
so code is run from arbitrary queues/threads spanning multiple RunLoops.

According to `CLLocationManager`'s
[documentation](https://developer.apple.com/documentation/corelocation/cllocationmanager):

> "Core Location calls the methods of your delegate object using the
> `RunLoop` of the thread on which you initialized the
> `CLLocationManager` object.

By forcing the `LocationManager.live`'s `CLLocationManager` instance to
be instantiated on the main `RunLoop` via `@MainActor`, the delegate
methods are correctly invoked. To minimize problems, all `.live`
instance APIs' closures were annotated with `@MainActor`.

Furthermore, as the `Dependencies` instances are shared and can be
called from multiple places concurrently, the `LocationManagerDelegate`
was updated to allow forwarding delegate calls to multiple streams
provided via `delegate()`.

Ran `swift-format` and performed some cleanups.
- Enable swift concurrency warnings in `Package.swift`
- Add `Sendable` conformance where needed, and wrap `CLLocation` types
in `@UncheckedSendable` until properly annotated.
@spiridonkopicl
Copy link

Thank you @rcasula, you saved my day 🍻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants