Replies: 4 comments 11 replies
-
You might need to keep a reference to your delegate as it’s only kept as weak to avoid circular reference. As soon as you function returns nobody has a strong reference to you delegate object so it get dealocated. |
Beta Was this translation helpful? Give feedback.
-
I prepared some example project to show the issue. It's simplified as much as possible. To see CoreLocationClient behavior change, please uncomment/comment To make it works, it uses simpler hack/workaround with |
Beta Was this translation helpful? Give feedback.
-
I "think" this is what's happening but anyone can correct me if I'm wrong. Your typical variable/property is instantiated when the class/struct it resides in is instantiated. However, I think @dependency values actually behave more like lazy variables. You can see this by adding this to your LocationReducer, public init() {
print("HI")
} your CoreLocationClient initalizer... struct CoreLocationClient {
var authorizationStatus: @Sendable () -> CLAuthorizationStatus
var delegate: @Sendable () -> AsyncStream<LocationManagerDelegate.Action>
var requestLocation: @Sendable () -> ()
var requestWhenInUseAuthorization: @Sendable () -> ()
init(authorizationStatus: @escaping () -> CLAuthorizationStatus, delegate: @escaping () -> AsyncStream<LocationManagerDelegate.Action>, requestLocation: @escaping () -> Void, requestWhenInUseAuthorization: @escaping () -> Void) {
self.authorizationStatus = authorizationStatus
self.delegate = delegate
self.requestLocation = requestLocation
self.requestWhenInUseAuthorization = requestWhenInUseAuthorization
print("HELLO")
}
} and then running the app. Notice that "HI" immediately appeared but "HELLO" doesn't appear until you actually send an action to the reducer. That means func reduce(into state: inout State, action: Action) -> Effect<Action, Never> {
switch action {
case let .coreLocation(.didUpdateLocations(locations)):
let coordinate = locations.first!.coordinate
state.coordinate = .init(latitude: coordinate.latitude, longitude: coordinate.longitude)
return .none
case .coreLocation(.didChangeAuthorization):
requestLocation()
return .none
case .coreLocation:
return .none
case .delegateButtonTapped:
// Without [coreLocationClient], coreLocationClient.delegate() returns nothing
// and the for-loop never iterates
return .run { [coreLocationClient] send in
for await action in coreLocationClient.delegate() {
await send(.coreLocation(action))
}
}
case .getLocationButtonTapped:
requestLocation()
return .none
}
}
} I hope someone smarter than me can chime in to say what is actually happening :) |
Beta Was this translation helpful? Give feedback.
-
It seems
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I hit some strange problem when trying to use ComposableCoreLocation via
@Dependency
property wrapper and find even weirder solution, which I would like to discuss here. I have no idea if it's some TCA bug, Apple bug or it's working as expected ;)Naturally I have added
DependencyKey
conformance forLocationManager
and extendedDependencyValues
.However, when I tried to use this client in a reducer via
@Dependency(\.locationManager) var locationManager
it wasn't fully working.LocationManagerDelegate
was set a delegate ofCLLocationManager
but any delegate's methods wasn't called.Original code for setting up delegate in ComposableCoreLocation is like this:
I suspected that the problem is something with @TaskLocal nad propagating delegate instance/action through system of Swift Concurrency. When browsing Isowords code base I noticed that some delegates are setup on
MainActor
inside new Task. I tried something like this:However it didn't change anything. Delegate endpoint still wasn't called by
CLLocationManager
. 😅I was experimenting more and finally found working solution! I tried setup
CLLocationManager
in DependencyValues and then when creating live LocationManager instance I used manager accessed via@Dependency
property wrapper.This one finally works! Both additional steps are mandatory to make it works. However I have no idea why and feels hacky ;) I would really appreciate if someone can confirm if it's right way to do and why it has to be like this?
I used examples above from Combine version of ComposableCoreLocation, however Swift Concurrency version from this PR has exactly the same problem. Naturally, I tried creating minimalistic CoreLocation from the beginning and it shared the same problem. Is all about setting up delegate for
CLLocationManager
.Here is my fork of ComposableCoreLocation which has two branches:
main
which uses combine andswift-concurrency
one. Both branches have workaround applied.EDITED:
Is even weirder than I previously thought. When I tried to prepare fresh example project, I noticed that hack described above sometime works but sometime not. For now I have no idea what is a culprit. However I have noticed that reading value of LocationManager inside of reduce function solved problem, even for client version without hacks.
I checked on simulator and real device and it behaves the same on both.
Beta Was this translation helpful? Give feedback.
All reactions