Protocol implementation on a class with #MainActor - how to avoid warnings? - swift

If we have a simple protocol and class implementation like the following;
protocol Solution: ObservableObject {
var result: String { get set }
func calc() async
}
#MainActor
class Solve: Solution {
#Published
var result: String = ""
func calc() async { // operate on actors to find the result
result = "the answer"
}
}
Xcode will show a yellow warning : "Main actor-isolated property 'result' cannot be used to satisfy nonisolated protocol requirement" against the class definition of result.
Removing #MainActor will remove the warning, but then we need to manually Dispatch updates to result to ensure they are done on the main thread.
Is there a cleaner way to do this? Perhaps by amending the protocol?

Add MainActor to the protocol
#MainActor
protocol Solution: ObservableObject {
var result: String { get set }
func calc() async
}
I would give actor a look too, it all depends on the purpose of this class.
If its purpose is to update UI then wrapping the class is natural.

The least intrusive solution is
#MainActor var result: String { get set }

Related

Actor-isolated property cannot be passed 'inout' to 'async' function call

I'm new (like most everyone, I suppose) to Swift concurrency, and I'm running into a compiler error I don't know what to do with.
struct Thing {
var counter = 0
mutating func increment() async {
counter += 1
}
}
class Controller: UIViewController {
var thing = Thing()
func mutate() async {
await thing.increment()
print(thing.counter)
}
}
let c = Controller()
Task {
await c.mutate()
}
The first line of the mutate() function gives me the following error.
Actor-isolated property 'thing' cannot be passed 'inout' to 'async' function call
If I just inherit from class instead of UIViewController things work fine, but I need the controller here, so I need to figure out how to make this work in that specific context.
I think the issue comes from Thing being a struct. A mutating func on a struct will assign a new value to the thing property on the Controller. In order for that to work, thing is treated as an inout parameter in the call to thing.increment().
If you make thing an actor instead of a struct then increment()won't need to be a mutating func and so thing won't be treated as an inout parameter.
A possible workaround is to make a copy of the struct first, then call the mutating func on the copy, then store it back on the property in the controller.
func mutate() async {
var thing = self.thing
await thing.increment()
self.thing = thing
print(thing.counter)
}
The reason it's an issue is the UIViewControllers are all actors now, so the properties are considered actor isolated. There is a nonisolated keyword but it cant be applied to stored properties so it doesn't seem to help here.
If the controller is changed to be an actor, the error message changes a bit to say that.
error: cannot call mutating async function 'increment()' on actor-isolated property 'thing'
await thing.increment()
^

How to implement custom implementation of method?

I'm drawing a blank for some reason.. If I want to make a bunch of objects from a class, but I want each instance to have its own unique implementation of a certain method, how would I do this?
For example:
class MyClass {
var name: String
func doSomething() {
// Each object would have custom implementation of this method, here.
}
}
Do I provide each object with its own closure during initialization, and then call that closure in the doSomething() method? I'm trying to figure out the correct or "Swiftly" way to do this. I'm also thinking along the lines of something with protocols, but I can't seem to figure out how to go about this.
I think there're many ways to do it.
In case of Base class + some sub-classes (e.g. Animal, subclassed by Dog, Cat, etc), you can do this:
First of all it's a good idea to define a protocol:
protocol MyProtocol {
func doSomething()
}
Also provide a default implementation, which throws a fatal error if a class doesn't override that method:
extension MyProtocol {
func doSomething() {
fatalError("You must override me")
}
}
Now your base class confirms the protocol thanks to default implementation. But it will throw a fatal error at runtime:
class MyClass: MyProtocol {
// conformant
}
Child class, however, will run correctly as long as it overrides this function:
class MyOtherClass: MyClass {
func doSomething() {
print("Doing it!")
}
}
You could also move fatal error into base class, and not do any extension implementation.
In case of many instances of the same Class, that solution makes no sense. You can use a very simple callback design:
typealias MyDelegate = () -> Void
class MyClass {
var delegate: MyDelegate?
func doSomething() {
delegate?()
}
}
let x = MyClass()
x.delegate = {
print("do it!")
}
x.doSomething()
// Or you can use a defined function
func doIt() {
print("also doing it")
}
x.delegate = doIt
x.doSomething()
It can also be that you re looking for Strategy pattern, or Template pattern. Depends on your usage details.
Do I provide each object with its own closure during initialization, and then call that closure in the doSomething() method
Yes. That is extremely common and eminently Swifty. Incredibly miminalistic example:
struct S {
let f:()->()
func doYourThing() { f() }
}
let s = S { print("hello") }
let s2 = S { print("goodbye" )}
s.doYourThing() // hello
s2.doYourThing() // goodbye
Giving an object a settable method instance property is very, very easy and common. It doesn't have to be provided during initialization — you might set this property later on, and a lot of built-in objects work that way too.
That, after all, is all you're doing when you create a data task with dataTask(with:completionHandler:). You are creating a data task and handing it a function which it stores, and which it will call when it has performed the actual networking.

Referencing instance method requires equivalency (SWIFT)

I am trying to leverage SwiftUI and Combine to store user defaults for my application. Looking at suggestions in a few other posts, I have updated my code as you see below. However, I am now getting the error of "Referencing instance method 'send()' on 'Subject' requires the types 'Setup' and 'Void' be equivalent". It has been suggested that I change "Setup" to void in the PassthroughSubject, however this then gives a hard crash in the app at startup - " Fatal error: No observable object of type Setup.Type found."
I am at a bit of loss... any pointers would be welcomed.
============== DataStoreClass ============
import SwiftUI
import Foundation
import Combine
class Setup: ObservableObject {
private var notificationSubscription: AnyCancellable?
let objectWillChange = PassthroughSubject<Setup,Never>()
#UserDefault(key: "keyValueBool", defaultValue: false)
var somevalueBool: Bool {
didSet{
objectWillChange.send() // <====== Referencing instance method 'send()' on 'Subject' requires the types 'Setup' and 'Void' be equivalent
}
}
init() {
notificationSubscription = NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { _ in
self.objectWillChange.send()
}
}
}
============= property wrapper ===========
import Foundation
#propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
UserDefaults(suiteName: "group.com.my.app")!.value(forKey: key) as? T ?? defaultValue
} set {
UserDefaults(suiteName: "group.com.my.app")!.set(newValue, forKey: key)
}
}
}
The error comes from the fact that you have declared your Output type as Setup, but you are calling objectWillChange with Void.
So you have to pass self to objectWillChange:
self.objectWillChange.send(self)
Important thing to notice is that you should call objectWillChange not in didSet but in willSet:
var somevalueBool: Bool {
willSet{
objectWillChange.send(self
}
}
You never set somevalueBool, so this bit of code will not get called anyway.
Your setup should look roughly like this:
class Setup: ObservableObject {
private var notificationSubscription: AnyCancellable?
public let objectWillChange = PassthroughSubject<Setup,Never>()
#UserDefault(key: "keyValueBool", defaultValue: false)
var somevalueBool: Bool
init() {
notificationSubscription = NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { _ in
self.objectWillChange.send(self)
}
}
}
The send method requires you to pass the input type of the subject, or a failure completion. So your send lines should pass the Setup;
objectWillChange.send(self)
That said, in most SwiftUI code, PassthroughSubject is <Void, Never> (such that send() does not require a parameter). It's not clear what the source of the crash you're describing is; we would need to see the code that's involved in the crash to debug that. I haven't reproduced it so far.
SwiftUI doesn't use a PassthroughSubject, it uses an ObservableObjectPublisher. I am pretty sure that that is an alias for PassthroughSubject<Void, Never> but I'm not sure. The ObservableObject protocol defines a correct objectWillChange for you so the best thing you can do is to remove your definition.
The publisher is objectWillChange and as its name suggests it should be sent in willSet and not didSet, I don't suppose that it matters much but Apple changed from didSet to willSet and my guess is that they had a good reason.

Combining testable code with static method dispatch in swift

I've been reading a lot about Swift's runtime lately, and became more and more interested in optimising my code using static method dispatch. This happens with the following methods:
struct methods
final class methods, i.e. declared with the final keyword, as private or in a final class
protocol methods that are defined in a protocol extension, without being declared in the protocol itself.
Problem is, non of these situations enables me to write testable code, at least not the way I do it now: injecting protocol entities that are replaced by mocks in unit testing.
So, is it possible to write testable code without giving up static method dispatch, and if so how does one go about it?
Thanks!
Generics is what you look for. You can abstract over a protocol, but the compiler still knows what exact type you are using, so there's no need for dynamic dispatch.
protocol Dependency {
func doSomething()
}
struct RealDependency: Dependency {
func doSomething() {
print("I'm doing real work")
}
}
struct MockDependency: Dependency {
func doSomething() {
print("I'm the mock, so I do nothing")
}
}
struct MyApp<D: Dependency> {
let dependency: D
func doSomething() {
dependency.doSomething()
}
}
let myAppReal = MyApp(dependency: RealDependency())
let myAppMock = MyApp(dependency: MockDependency())
myAppReal.doSomething() // Prints "I'm doing real work"
myAppMock.doSomething() // Prints "I'm the mock, so I do nothing"
However, note that in Swift, generics monomorphization is not guaranteed. So you might end with some form of dynamic dispatch anyway. See this link

Calling a method from a struct within the same class

I'd like to logically organize class properties to signify that they are one logical unit and to distinguish them from other class properties that are less tightly related.
I thought of doing this using a struct within my class. However, it seems that I can't call class methods from the struct property setters. I get what seems to be an inappropriate compile error: "missing argument for parameter #1 in call"
This seems to be different from calling method from struct in swift
where the function is within the struct. In my case, the methods are generic and don't just apply to my struct but to all class properties. Therefore, I don't want to move them within the struct.
Do you have ideas on how to organize properties into tight(er) logical units within classes?
class MyClass {
struct MyStruct {
static var aVarInMyStruct: String? {
didSet {
anotherVarInMyStruct = foo() // This gives compile error "missing argument for parameter #1 in call"
}
}
static var anotherVarInMyStruct: String?
}
func foo() {
println("I am foo()")
}
}
The inner type MyStruct knows nothing about its outer type MyClass. So there is no foo function to call from MyStruct. To better organize your code I suggest you to use // MARK: - whatever this section is comments. Types are not here to organize codes. Types are here to create right abstractions for the program.
I fix your bug:
class MyClass {
struct MyStruct {
static var aVarInMyStruct: String? {
didSet {
anotherVarInMyStruct = MyClass.foo() // This gives compile error "missing argument for parameter #1 in call"
}
}
static var anotherVarInMyStruct: String?
}
static func foo()->String {
println("I am foo()")
return "it is ok"
}
}