Swift AsyncStream variable, use after finish() - swift

I'm currently reading about AsyncStreams and as I understand it, they are best for tasks that produce some results over time, but have a lifespan - e.g. start and end. Here's an example I am playing with:
struct AHardWorkingStruct {
lazy var updates = AsyncStream<String> { continuation in
onProgress = { value in
continuation.yield(value)
}
onFinish = { value in
continuation.yield(value)
continuation.finish()
}
}
private var onProgress: (String) -> Void = { _ in () }
private var onFinish: (String) -> Void = { _ in () }
func doSomeWork() async {
let numbers = (0..<20).map { anInt in
anInt^2
}
for number in numbers {
onProgress("The number is \(number)")
if(number == 20) {
onFinish("I'm DONE!")
}
}
}
}
I then have AHardWorkingStruct as a property in my view controller and use it like so:
class MyViewController: UIViewController {
var myHardWorker = AHardWorkingStruct()
#IBAction func tapToRun(_ sender: UIButton) {
Task {
await myHardWorker.doSomeWork()
}
}
override func viewDidLoad() {
super.viewDidLoad()
Task {
for await stream in myHardWorker.updates {
print(stream)
}
}
}
This works perfectly when I tap the button.
However, once I call finish() on the stream, I no longer get the updates from it - which I understand is expected behaviour.
My question is, how can I keep getting updates from a stream that is a variable on a long-lived object? Is there a "reset"? (other than getting rid of the lazy and nilling the variable)
I also have a 2nd part to this - what would be the best way to unit test an asyncstream? I've tried using expectations, but not sure if this is right.

Related

AsyncStream spams view, where AsyncPublisher does not

I'm running into a behavior with AsyncStream I don't quite understand.
When I have an actor with a published variable, I can "subscribe" to it via an AsyncPublisher and it behaves as expected, updating only when there is a change in value. If I create an AsyncStream with a synchronous context (but with a potential task retention problem) it also behaves as expected.
The weirdness happens when I try to wrap that publisher in an AsyncStream with an asyncronous context. It starts spamming the view with an update per loop it seems, NOT only when there is a change.
What am I missing about the AsyncStream.init(unfolding:oncancel:) which is causing this behavior?
https://developer.apple.com/documentation/swift/asyncstream/init(unfolding:oncancel:)?
import Foundation
import SwiftUI
actor TestService {
static let shared = TestService()
#MainActor #Published var counter:Int = 0
#MainActor public func updateCounter(by delta:Int) async {
counter = counter + delta
}
public func asyncStream() -> AsyncStream<Int> {
return AsyncStream.init(unfolding: unfolding, onCancel: onCancel)
//() async -> _?
func unfolding() async -> Int? {
for await n in $counter.values {
//print("\(location)")
return n
}
return nil
}
//optional
#Sendable func onCancel() -> Void {
print("confirm counter got canceled")
}
}
public func syncStream() -> AsyncStream<Int> {
AsyncStream { continuation in
let streamTask = Task {
for await n in $counter.values {
continuation.yield(n)
}
}
continuation.onTermination = { #Sendable _ in
streamTask.cancel()
print("StreamTask Canceled")
}
}
}
}
struct ContentView: View {
var body: some View {
VStack {
TestActorButton()
HStack {
//TestActorViewA() //<-- uncomment at your own risk.
TestActorViewB()
TestActorViewC()
}
}
.padding()
}
}
struct TestActorButton:View {
var counter = TestService.shared
var body: some View {
Button("increment counter") {
Task { await counter.updateCounter(by: 2) }
}
}
}
struct TestActorViewA:View {
var counter = TestService.shared
#State var counterVal:Int = 0
var body: some View {
Text("\(counterVal)")
.task {
//Fires constantly.
for await value in await counter.asyncStream() {
print("View A Value: \(value)")
counterVal = value
}
}
}
}
struct TestActorViewB:View {
var counter = TestService.shared
#State var counterVal:Int = 0
var body: some View {
Text("\(counterVal)")
.task {
//Behaves like one would expect. Fires once per change.
for await value in await counter.$counter.values {
print("View B Value: \(value)")
counterVal = value
}
}
}
}
struct TestActorViewC:View {
var counter = TestService.shared
#State var counterVal:Int = 0
var body: some View {
Text("\(counterVal)")
.task {
//Also only fires on update
for await value in await counter.syncStream() {
print("View C Value: \(value)")
counterVal = value
}
}
}
}
The real solution to wrapping a publisher appears to be to stick to the synchronous context initializer and have it cancel it's own task:
public func stream() -> AsyncStream<Int> {
AsyncStream { continuation in
let streamTask = Task {
for await n in $counter.values {
//do hard work to transform n
continuation.yield(n)
}
}
continuation.onTermination = { #Sendable _ in
streamTask.cancel()
print("StreamTask Canceled")
}
}
}
From what I can tell the "unfolding" style initializer for AsyncStream is simply not a fit for wrapping an AsyncPublisher. The "unfolding" function will "pull" at the published value from within the stream, so the stream will just keep pushing values from that infinite well.
It seems like the "unfolding" style initializer is best used when processing a finite (but potentially very large) list of items, or when generating ones values from scratch... something like:
struct NumberQueuer {
let numbers:[Int]
public func queueStream() -> AsyncStream<Int> {
var iterator = AsyncArray(values: numbers).makeAsyncIterator()
print("Queue called")
return AsyncStream.init(unfolding: unfolding, onCancel: onCancel)
//() async -> _?
func unfolding() async -> Int? {
do {
if let item = try await iterator.next() {
return item
}
} catch let error {
print(error.localizedDescription)
}
return nil
}
//optional
#Sendable func onCancel() -> Void {
print("confirm NumberQueue got canceled")
}
}
}
public struct AsyncArray<Element>: AsyncSequence, AsyncIteratorProtocol {
let values:[Element]
let delay:TimeInterval
var currentIndex = -1
public init(values: [Element], delay:TimeInterval = 1) {
self.values = values
self.delay = delay
}
public mutating func next() async throws -> Element? {
currentIndex += 1
guard currentIndex < values.count else {
return nil
}
try await Task.sleep(nanoseconds: UInt64(delay * 1E09))
return values[currentIndex]
}
public func makeAsyncIterator() -> AsyncArray {
self
}
}
One can force the unfolding type to work with an #Published by creating a buffer array that is checked repeatedly. The variable wouldn't actually need to be #Published anymore. This approach has a lot of problems but it can be made to work. If interested, I put it in a repo with a bunch of other AsyncStream examples. https://github.com/carlynorama/StreamPublisherTests
This article was very helpful to sorting this out: https://www.raywenderlich.com/34044359-asyncsequence-asyncstream-tutorial-for-ios
As was this video: https://www.youtube.com/watch?v=UwwKJLrg_0U

Swift unit testing view model interface

As I understand, it is best to only test public methods of a class.
Let's have a look at this example. I have a view model for the view controller.
protocol MyViewModelProtocol {
var items: [SomeItem] { get }
var onInsertItemsAtIndexPaths: (([IndexPath]) -> Void)? { get set }
func viewLoaded()
}
class MyViewModel: MyViewModelProtocol {
func viewLoaded() {
let items = createDetailsCellModels()
updateCellModels(with: items)
requestDetails()
}
}
I want to test class viewLoaded(). This class calls two other methods - updateItems() and requestDetails()
One of the methods sets up the items and the other one call API to retrieve data and update those items. Items array us updated two times and onInsertItemsAtIndexPaths are called two times - when setting up those items and when updating with new data.
I can test whether after calling viewLoaded() expected items are set up and that onInsertItemsAtIndexPaths is called.
However, the test method will become rather complex.
What is your view, should I test those two methods separately or just write this one huge test?
By testing only viewLoaded(), my idea is that the implementation can change and I only care that results are what I expect.
I think the same thing, only public functions should be tested, since public ones use private ones, and your view on MVVM is correct. You can improve it by adding a DataSource and a Mapper that allows you to improve testing.
However, yes, the test seems huge to me, the tests should test simple units and ensure that small parts of the code work well, with the example you show is difficult, you need to divide by layers (clean code).
In the example you load the data into the viewModel and make it difficult to mockup the data. But if you have a Domain layer you can pass the UseCase mock to the viewModel and control the result. If you run a test on your example, the result will also depend on what the endpoint returns. (404, 200, empty array, data with error ...). So it is important, for testing purposes, to have a good separation by layers. (Presentation, Domain and Data) to be able to test each one separately.
I give you an example of how I would test a view mode, sure there are better and cooler examples, but it's an approach.
Here you can see a viewModel
protocol BeersListViewModel: BeersListViewModelInput, BeersListViewModelOutput {}
protocol BeersListViewModelInput {
func viewDidLoad()
func updateView()
func image(url: String?, index: Int) -> Cancellable?
}
protocol BeersListViewModelOutput {
var items: Box<BeersListModel?> { get }
var loadingStatus: Box<LoadingStatus?> { get }
var error: Box<Error?> { get }
}
final class DefaultBeersListViewModel {
private let beersListUseCase: BeersListUseCase
private var beersLoadTask: Cancellable? { willSet { beersLoadTask?.cancel() }}
var items: Box<BeersListModel?> = Box(nil)
var loadingStatus: Box<LoadingStatus?> = Box(.stop)
var error: Box<Error?> = Box(nil)
#discardableResult
init(beersListUseCase: BeersListUseCase) {
self.beersListUseCase = beersListUseCase
}
func viewDidLoad() {
updateView()
}
}
// MARK: Update View
extension DefaultBeersListViewModel: BeersListViewModel {
func updateView() {
self.loadingStatus.value = .start
beersLoadTask = beersListUseCase.execute(completion: { (result) in
switch result {
case .success(let beers):
let beers = beers.map { DefaultBeerModel(beer: $0) }
self.items.value = DefaultBeersListModel(beers: beers)
case .failure(let error):
self.error.value = error
}
self.loadingStatus.value = .stop
})
}
}
// MARK: - Images
extension DefaultBeersListViewModel {
func image(url: String?, index: Int) -> Cancellable? {
guard let url = url else { return nil }
return beersListUseCase.image(with: url, completion: { (result) in
switch result {
case .success(let imageData):
self.items.value?.items?[index].image.value = imageData
case .failure(let error ):
print("image error: \(error)")
}
})
}
}
Here you can see the viewModel test using mocks for the data and view.
class BeerListViewModelTest: XCTestCase {
private enum ErrorMock: Error {
case error
}
class BeersListUseCaseMock: BeersListUseCase {
var error: Error?
var expt: XCTestExpectation?
func execute(completion: #escaping (Result<[BeerEntity], Error>) -> Void) -> Cancellable? {
let beersMock = BeersMock.makeBeerListEntityMock()
if let error = error {
completion(.failure(error))
} else {
completion(.success(beersMock))
}
expt?.fulfill()
return nil
}
func image(with imageUrl: String, completion: #escaping (Result<Data, Error>) -> Void) -> Cancellable? {
return nil
}
}
func testWhenAPIReturnAllData() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "All OK")
beersListUseCaseMock.error = nil
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.items.bind { (_) in}
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNotNil(viewModel.items.value)
XCTAssertNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
func testWhenDataReturnsError() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "Error")
beersListUseCaseMock.error = ErrorMock.error
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNil(viewModel.items.value)
XCTAssertNotNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
}
in this way you can test the view, the business logic and the data separately, in addition to being a code that is very reusable.
Hope this helps you, I have it posted on github in case you need it.
https://github.com/cardona/MVVM

Q: AudiKit : AKEqualizerFilter does not work properly with iBAction

Well, I am really confused since when I change the parameters and try to start()/ bypass() a node with iBAction, but nothing happens. The oscillator(WhiteNoise) seems to work properly while the Eq doesn't. Those parameters do have changed, however, the sound remained unchanged.
and here are my codes:
import AudioKit
class Conductor {
private var oscillator = AKWhiteNoise()
public var filter = AKEqualizerFilter()
private var gain = -12.0
init() {
oscillator.amplitude = 1
oscillator.stop()
filter = AKEqualizerFilter(oscillator)
filter.bypass()
AudioKit.output = filter
do {
try AudioKit.start()
} catch {
AKLog("AudioKit did not start!")
}
}
func play() {
if oscillator.isPlaying {
oscillator.stop()
} else {
oscillator.play()
}
}
func bypass(centerFrequency: Double, Q: Double) {
if filter.isBypassed {
filter.rampDuration = 0.3
filter.centerFrequency = centerFrequency
filter.bandwidth = centerFrequency/Q
filter.gain = pow(10, gain/20)
filter.start()
} else {
filter.bypass()
}
print(filter.isBypassed)
}
}
and my calls in ViewController:
#IBAction func bypass(_ sender: Any) {
conductor.bypass(centerFrequency: 125, Q: 7)
}

Implement a condition which is based on calls from different external IBActions?

I have these two IBActions in WorkoutsController.swift.
#IBAction func startWalkingButton() {
print("Walking start button pressed")
presentControllerWithName("Dashboard", context: sessionContext)
wSM!.startWorkout()
}
#IBAction func startCyclingButton() {
print("Cycling start button pressed")
presentControllerWithName("Dashboard", context: sessionContext)
wSM!.startWorkout()
}
They are calling the startWorkout() function in WorkoutSessionManager.swift
func startWorkout() {
self.healthStore.startWorkoutSession(self.workoutSession)
if ... {
print("startWorkout() called from startWalkingButton")
} else if ... {
print("startWorkout() called from startCyclingButton")
}
}
How do I create a condition to print out different print statements depending on which button function called the method? Should I use an if statement or switch statement?
I know there is already a print statement for the separate IBActions but I want to know if it's possible to do it in the reverse for redundancy.
Simply add one Bool parameter with your method startWorkout
func startWorkout(isFromWalking: Bool) {
if (isFromWalking) {
print("startWorkout() called from startWalkingButton")
}
else {
print("startWorkout() called from startCyclingButton")
}
}
Now call this function from startWalkingButton method with passing true
startWorkout(true)
and from startCyclingButton method with passing false
startWorkout(false)
Edit:
You haven't told that you have multiple option, then best option is to used enum in this case, create one enum like this and use that with the method
enum Workout {
case Walking
case Cycling
//Add case that you have
}
Now change the function like this
func startWorkout(workout: Workout) {
switch(workout) {
case .Walking :
print("Walking")
case .Cycling:
print("Cycling")
}
}
And call the function like this
self.startWorkout(.Walking)
self.startWorkout(.Cycling)
Simply add some sort of 'sender' parameter to your startWorkout() method.
Example:
// Hold a reference to your buttons, connected from IB
#IBOutlet var startWalkingButton: UIButton!
#IBOutlet var startCyclingButton: UIButton!
// here are your .TouchUpInside actions
// UIControl action methods receive the sender of the event as the first parameter (sender)
#IBAction func startWalkingButtonTouched(sender: AnyObject) {
...
startWorkout(sender)
}
#IBAction func startCyclingButtonTouched(sender: AnyObject) {
...
startWorkout(sender)
}
func startWorkout(sender: AnyObject) {
self.healthStore.startWorkoutSession(self.workoutSession)
switch sender {
case startWalkingButton:
print("startWorkout() called from startWalkingButton")
break
case startCyclingButton:
print("startWorkout() called from startCyclingButton")
break
default: ()
}
}
Hope this helps.
I feel you should probably use a block here. startWorkout method should accept an optional block. This approach avoids passing arguments and also avoids having if and case statements.
class Walking {
let workout = Workout()
func startWalkingButton() {
print("startWalkingButton")
workout.startWorkout() {
print("Walking Over")
}
}
}
class Cycling {
let workout = Workout()
func startCyclingButton() {
print("startCyclingButton")
workout.startWorkout() {
print("Cycling Over")
}
}
}
class Workout {
func startWorkout( afterWorkout: () -> Void ){
print("startWorkout")
afterWorkout()
}
}
let w = Walking()
w.startWalkingButton()

Swift computed property templates?

Is there a way to template computed properties to avoid repeating the same code over and over? For example, right now I have a class with a block of code that looks like this:
private var _state:State?
private var _maxs:State?
private var _state1s:State?
private var _state10s:State?
var state:State? {
get {
dispatch_semaphore_wait(statephore, DISPATCH_TIME_FOREVER)
let s=_state
dispatch_semaphore_signal(statephore)
return s
}
set {
dispatch_semaphore_wait(statephore, DISPATCH_TIME_FOREVER)
_state=newValue
dispatch_semaphore_signal(statephore)
if newValue != nil {statsTest(newValue!)}
}
}
var maxs:State? {
get {
dispatch_semaphore_wait(maxphore, DISPATCH_TIME_FOREVER)
let m=_maxs
dispatch_semaphore_signal(maxphore)
return m
}
set {
dispatch_semaphore_wait(maxphore, DISPATCH_TIME_FOREVER)
_maxs=newValue
dispatch_semaphore_signal(maxphore)
}
}
var state1s:State? {
get {
dispatch_semaphore_wait(state1sphore, DISPATCH_TIME_FOREVER)
let s=_state1s
dispatch_semaphore_signal(state1sphore)
return s
}
set {
dispatch_semaphore_wait(state1sphore, DISPATCH_TIME_FOREVER)
_state1s=newValue
dispatch_semaphore_signal(state1sphore)
}
}
var state10s:State? {
get {
dispatch_semaphore_wait(state10sphore, DISPATCH_TIME_FOREVER)
let s=_state10s
dispatch_semaphore_signal(state10sphore)
return s
}
set {
dispatch_semaphore_wait(state10sphore, DISPATCH_TIME_FOREVER)
_state10s=newValue
dispatch_semaphore_signal(state10sphore)
}
}
There's an obvious pattern here, and all the repeated code just obfuscates what's happening and has led to errors as I cut/paste/edit/fail. Is there a way I can capture this pattern, and then define my properties with something like:
var state=ProtectedValue(_state,statephore)
?
This looks like a job for generics and inout variables.
func setProtectedValue<T>(inout destination: T, newValue: T, semaphore: SemaphoreType) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
destination = newValue
dispatch_semaphore_signal(semaphore)
}
at the call site:
var state10s:State? {
get {
//...
}
set {
setProtectedValue(&_state10s, newValue, state10sphore)
}
}