Swift computed property templates? - swift

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)
}
}

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

GlobalActor directive doesn't guarantee a function will be called on that actor

Assuming I have defined a global actor:
#globalActor actor MyActor {
static let shared = MyActor()
}
And I have a class, in which a couple of methods need to act under this:
class MyClass {
#MyActor func doSomething(undoManager: UndoManager) {
// Do something here
undoManager?.registerUndo(withTarget: self) {
$0.reverseSomething(undoManager: UndoManager)
}
}
#MyActor func reverseSomething(undoManager: UndoManager) {
// Do the reverse of something here
print(\(Thread.isMainThread) /// Prints true when called from undo stack
undoManager?.registerUndo(withTarget: self) {
$0.doSomething(undoManager: UndoManager)
}
}
}
Assume the code gets called from a SwiftUI view:
struct MyView: View {
#Environment(\.undoManager) private var undoManager: UndoManager?
let myObject: MyClass
var body: some View {
Button("Do something") { myObject.doSomething(undoManager: undoManager) }
}
}
Note that when the action is undone the 'reversing' func it is called on the MainThread. Is the correct way to prevent this to wrap the undo action in a task? As in:
#MyActor func reverseSomething(undoManager: UndoManager) {
// Do the reverse of something here
print(\(Thread.isMainThread) /// Prints true
undoManager?.registerUndo(withTarget: self) {
Task { $0.doSomething(undoManager: UndoManager) }
}
}
I am surprised that the compiler does not generate a warning about calling a global actor 'MyActor'-isolated instance method in a synchronous nonisolated context (i.e. the closure). It would appear that the compiler is confused by the closure syntax within an actor isolated method.
Anyway, you can wrap it in a Task and it should run that on the appropriate actor:
#MyActor func doSomething(undoManager: UndoManager) {
// Do something here
undoManager.registerUndo(withTarget: self) { target in
Task { #MyActor in
target.reverseSomething(undoManager: undoManager)
}
}
}
That having been said, I have found erratic UndoManager behavior when using it from a background thread (i.e., not on the main actor).
So, especially because undo/redo is behavior generally initiated from the UI (on the main thread), I would keep it on the main thread, and only run the desired work on another actor. E.g.:
struct ContentView: View {
#StateObject var viewModel = ViewModel()
#State var input: String = ""
var body: some View {
VStack {
TextField(text: $input) {
Text("enter value")
}
Button("Add record") {
viewModel.addAndPrepareUndo(for: input)
input = ""
}.disabled(input.isEmpty)
Button("Undo") {
viewModel.undo()
}.disabled(!viewModel.canUndo)
Button("Redo") {
viewModel.redo()
}.disabled(!viewModel.canRedo)
}
.padding()
}
}
#globalActor actor MyGlobalActor {
static let shared = MyGlobalActor()
}
#MainActor
class ViewModel: ObservableObject {
#MyGlobalActor
var values: [String] = []
#Published var canUndo = false
#Published var canRedo = false
private var undoManager = UndoManager()
func undo() {
undoManager.undo()
updateUndoStatus()
}
func redo() {
undoManager.redo()
updateUndoStatus()
}
func updateUndoStatus() {
canUndo = undoManager.canUndo
canRedo = undoManager.canRedo
}
func addAndPrepareUndo(for newValue: String) {
undoManager.registerUndo(withTarget: self) { [weak self] target in
guard let self else { return }
self.removeAndPrepareRedo(for: newValue)
}
updateUndoStatus()
Task { #MyGlobalActor in
values.append(newValue)
print(#function, values)
}
}
func removeAndPrepareRedo(for revertValue: String) {
undoManager.registerUndo(withTarget: self) { [weak self] target in
guard let self else { return }
self.addAndPrepareUndo(for: revertValue)
}
updateUndoStatus()
Task { #MyGlobalActor in
values.removeLast()
print(#function, values)
}
}
}
Now, this is a somewhat contrived example (for something this simple, we wouldn't have a simply array on a global actor), but hopefully it illustrates the idea.
Or, you can use a non-global actor:
struct ContentView: View {
#StateObject var viewModel = ViewModel()
#State var input: String = ""
var body: some View {
VStack {
TextField(text: $input) {
Text("enter value")
}
Button("Add record") {
viewModel.addAndPrepareUndo(for: input)
input = ""
}.disabled(input.isEmpty)
Button("Undo") {
viewModel.undo()
}.disabled(!viewModel.canUndo)
Button("Redo") {
viewModel.redo()
}.disabled(!viewModel.canRedo)
}
.padding()
}
}
#MainActor
class ViewModel: ObservableObject {
var model = Model()
#Published var canUndo = false
#Published var canRedo = false
private var undoManager = UndoManager()
func undo() {
undoManager.undo()
updateUndoStatus()
}
func redo() {
undoManager.redo()
updateUndoStatus()
}
func updateUndoStatus() {
canUndo = undoManager.canUndo
canRedo = undoManager.canRedo
}
func addAndPrepareUndo(for newValue: String) {
undoManager.registerUndo(withTarget: self) { [weak self] target in
guard let self else { return }
self.removeAndPrepareRedo(for: newValue)
}
updateUndoStatus()
Task {
await model.append(newValue)
await print(#function, model.values())
}
}
func removeAndPrepareRedo(for revertValue: String) {
undoManager.registerUndo(withTarget: self) { [weak self] target in
guard let self else { return }
self.addAndPrepareUndo(for: revertValue)
}
updateUndoStatus()
Task {
await model.removeLast()
await print(#function, model.values())
}
}
}
actor Model {
private var strings: [String] = []
func append(_ string: String) {
strings.append(string)
}
func removeLast() {
strings.removeLast()
}
func values() -> [String] {
strings
}
}

Swift AsyncStream variable, use after finish()

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.

is reference assignment atomic in Swift 5?

Do I need some kind of explicit synchronization in this case?
class A {
let val: Int;
init(_ newVal: Int) {
val = newVal
}
}
public class B {
var a: A? = nil
public func setA() { a = A(0) }
public func hasA() -> Bool { return a != nil }
}
There is also another method in class B:
public func resetA() {
guard hasA() else { return }
a = A(1)
}
setA() and resetA() may be called from any thread, in any order.
I understand that there may be a race condition, that if concurrently one thread calls setA() and another thread calls resetA(), the result is not determined: val will either be 0, or 1, but I don't care: at any rate, hasA() will return true, won't it?
Does the answer change if A is a struct instead of class?
In short, no, property accessors are not atomic. See WWDC 2016 video Concurrent Programming With GCD in Swift 3, which talks about the absence of atomic/synchronization native in the language. (This is a GCD talk, so when they subsequently dive into synchronization methods, they focus on GCD methods, but any synchronization method is fine.) Apple uses a variety of different synchronization methods in their own code. E.g. in ThreadSafeArrayStore they use they use NSLock).
If synchronizing with locks, I might suggest an extension like the following:
extension NSLocking {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
Apple uses this pattern in their own code, though they happen to call it withLock rather than synchronized. But the pattern is the same.
Then you can do:
public class B {
private var lock = NSLock()
private var a: A? // make this private to prevent unsynchronized direct access to this property
public func setA() {
lock.synchronized {
a = A(0)
}
}
public func hasA() -> Bool {
lock.synchronized {
a != nil
}
}
public func resetA() {
lock.synchronized {
guard a != nil else { return }
a = A(1)
}
}
}
Or perhaps
public class B {
private var lock = NSLock()
private var _a: A?
public var a: A? {
get { lock.synchronized { _a } }
set { lock.synchronized { _a = newValue } }
}
public var hasA: Bool {
lock.synchronized { _a != nil }
}
public func resetA() {
lock.synchronized {
guard _a != nil else { return }
_a = A(1)
}
}
}
I confess to some uneasiness in exposing hasA, because it practically invites the application developer to write like:
if !b.hasA {
b.a = ...
}
That is fine in terms of preventing simultaneous access to memory, but it introduced a logical race if two threads are doing it at the same time, where both happen to pass the !hasA test, and they both replace the value, the last one wins.
Instead, I might write a method to do this for us:
public class B {
private var lock = NSLock() // replacing os_unfair_lock_s()
private var _a: A? = nil // fixed, thanks to Rob
var a: A? {
get { lock.synchronized { _a } }
set { lock.synchronized { _a = newValue } }
}
public func withA(block: (inout A?) throws -> T) rethrows -> T {
try lock.synchronized {
try block(&_a)
}
}
}
That way you can do:
b.withA { a in
if a == nil {
a = ...
}
}
That is thread safe, because we are letting the caller wrap all the logical tasks (checking to see if a is nil and, if so, the initialization of a) all in one single synchronized step. It is a nice generalized solution to the problem. And it prevents logic races.
Now the above example is so abstract that it is hard to follow. So let's consider a practical example, a variation on Apple’s ThreadSafeArrayStore:
public class ThreadSafeArrayStore<Value> {
private var underlying: [Value]
private let lock = NSLock()
public init(_ seed: [Value] = []) {
underlying = seed
}
public subscript(index: Int) -> Value {
get { lock.synchronized { underlying[index] } }
set { lock.synchronized { underlying[index] = newValue } }
}
public func get() -> [Value] {
lock.synchronized {
underlying
}
}
public func clear() {
lock.synchronized {
underlying = []
}
}
public func append(_ item: Value) {
lock.synchronized {
underlying.append(item)
}
}
public var count: Int {
lock.synchronized {
underlying.count
}
}
public var isEmpty: Bool {
lock.synchronized {
underlying.isEmpty
}
}
public func map<NewValue>(_ transform: (Value) throws -> NewValue) rethrows -> [NewValue] {
try lock.synchronized {
try underlying.map(transform)
}
}
public func compactMap<NewValue>(_ transform: (Value) throws -> NewValue?) rethrows -> [NewValue] {
try lock.synchronized {
try underlying.compactMap(transform)
}
}
}
Here we have a synchronized array, where we define an interface to interact with the underlying array in a thread-safe manner.
Or, if you want an even more trivial example, consider an thread-safe object to keep track of what the tallest item was. We would not have a hasValue boolean, but rather we would incorporate that right into our synchronized updateIfTaller method:
public class Tallest {
private var _height: Float?
private let lock = NSLock()
var height: Float? {
lock.synchronized { _height }
}
func updateIfTaller(_ candidate: Float) {
lock.synchronized {
guard let tallest = _height else {
_height = candidate
return
}
if candidate > tallest {
_height = candidate
}
}
}
}
Just a few examples. Hopefully it illustrates the idea.

How to access variable in Document.swift from another class?

In CoreData document I have an entity SpaceLocation which is set of different SpaceLocations. One of them is default / current one, necessary for editing document.
It's defined in main Document.swift as:
#objc var defaultSpaceLocation: SpaceLocation {
return SpaceLocation.defaultSpaceLocation(in: managedObjectContext!)
}
#objc var currentSpaceLocation: SpaceLocation {
get {
if _currentSpaceLocation == nil {
willChangeValue(for: \Document.currentSpaceLocation)
_currentSpaceLocation = defaultSpaceLocation
didChangeValue(for: \Document.currentSpaceLocation)
}
return _currentSpaceLocation!
}
set {
willChangeValue(for: \Document.currentSpaceLocation)
_currentSpaceLocation = newValue
didChangeValue(for: \Document.currentSpaceLocation)
}
}
and in SpaceLocation class as:
class func defaultSpaceLocation(in moc: NSManagedObjectContext) -> SpaceLocation {
let spaceLocationsRequest = NSFetchRequest<SpaceLocation>(entityName: "SpaceLocation")
var result:SpaceLocation? = nil
do {
let spaceLocations = try moc.fetch(spaceLocationsRequest)
result = spaceLocations.filter {$0.isBaseLocation}.first!
}
catch {
Swift.print("•••• ERROR ••••", #file, #function, "Couldn't get Default Location")
}
return result!
}
}
I access to currentSpaceLocation by:
(NSDocumentController.shared.currentDocument as?Document)?.currentSpaceLocation
but it works only if document is in foreground. When app goes background NSDocumentController.shared.currentDocument becomes nil so I cannot access document.currentSpaceLocation. Any idea how to solve this?