Avoiding sending actions using the 'send' argument from 'Effect.run' after the effect has completed. #3551
-
I'm trying to send an action whenever a specific motion is detected (for now, just whenever x acceleration is positive). It works, but I'm getting the runtime warning whenever the Here's my class MotionManager {
private static let manager = CMMotionManager()
/// Starts motion updates and provides acceleration and rotation callbacks.
static func startMotionUpdates(
accelerationHandler: @escaping (_ x: Double, _ y: Double, _ z: Double) -> Void,
rotationHandler: @escaping (_ roll: Double, _ pitch: Double, _ yaw: Double) -> Void
) {
guard manager.isAccelerometerAvailable, manager.isGyroAvailable else {
print("Motion sensors are not available on this device.")
return
}
manager.accelerometerUpdateInterval = 0.1
manager.gyroUpdateInterval = 0.1
manager.startAccelerometerUpdates(to: .main) { data, _ in
if let data = data {
let x = data.acceleration.x
let y = data.acceleration.y
let z = data.acceleration.z
print("Acceleration - x: \(x), y: \(y), z: \(z)")
accelerationHandler(x, y, z)
}
}
manager.startGyroUpdates(to: .main) { data, _ in
if let data = data {
let roll = data.rotationRate.x
let pitch = data.rotationRate.y
let yaw = data.rotationRate.z
print("Rotation - roll: \(roll), pitch: \(pitch), yaw: \(yaw)")
rotationHandler(roll, pitch, yaw)
}
}
}
/// Stops motion updates.
static func stopMotionUpdates() {
manager.stopAccelerometerUpdates()
manager.stopGyroUpdates()
print("Motion updates stopped")
}
} And here's the reducer where the runtime warning occurs: var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .boardAppeared:
print("Starting motion updates")
return .run { send in
MotionManager.startMotionUpdates(
accelerationHandler: { x, y, z in
print("Acceleration detected: \(x), \(y), \(z)")
if x > 0 {
DispatchQueue.main.async {
send(.guessed)
}
}
},
rotationHandler: { roll, pitch, yaw in
print("Rotation detected: \(roll), \(pitch), \(yaw)")
}
)
}
.cancellable(id: CancelID.motionUpdates, cancelInFlight: true)
case .guessed:
print("Guessed!")
return .none
case .passed:
print("Passed!")
return .none
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
You'll want your An @preconcurrency import Combine
AsyncThrowingStream<CMAccelerometerData, any Error> { continuation in
let manager = CMMotionManager()
manager.startAccelerometerUpdates(to: OperationQueue()) { data, error in
if let error {
continuation.finish(throwing: error)
} else if let data {
continuation.yield(data)
}
}
continuation.onTermination = { _ in
manager.stopAccelerometerUpdates()
}
} And then your dependency can return this stream, instead, which you can return .run { send in
for try await data in MotionManager.accelerationUpdates() {
// Handle data...
}
}
.cancellable(id: CancelID.motionUpdates, cancelInFlight: true) You should be able to modify the above to also deliver other motion updates. |
Beta Was this translation helpful? Give feedback.
You'll want your
MotionManager
to introduce a lifecycle for delivering events, either with a Combine publisher or an async sequence.An
AsyncStream
is probably the most convenient way to wrap things. For example, just for acceleration updates:And then your dependency can re…