I am trying to make an independent class/library that runs on its own and handles errors internally, but also gives feedback (status) to the frontend for possible user interaction. In Android I'd solve this with a Listener:
class Status(errCode:Int=0, msg:String=""){
var errCode:Int=0
var text:String=""
private var listener: ChangeListener? = null
init {
this.errCode=errCode
this.text=msg
}
fun set(errCode:Int, msg:String) {
this.errCode=errCode
this.text=msg
if (listener != null) listener!!.onChange()
}
fun getListener(): ChangeListener? {
return listener
}
fun setListener(listener: ChangeListener?) {
this.listener = listener
}
interface ChangeListener {
fun onChange()
}
}
Whenever I want to update it, e.g. on Exception, I call:
catch (e: IOException) {
this.status.set(109,"someException: $e")
}
And in the MainActivity I just have to handle these changes:
myObj.status.setListener(object : Status.ChangeListener {
override fun onChange() {
when(myObj!!.status.errCode) {
//errors
109 -> log(myObj!!.status.text) //some Exception
111 -> myObj!!.restart() //another exc
112 -> myObj!!.checkAll() //some other error
...
In Swift I found didSet that seems similar. I read you can attach it to a struct so whenever something in the struct changes, it is called. So I added my "struct Status" to my "struct myObj" like this:
struct myObj {
var status:Status=Status(errCode: 0, msg: "Init")
struct Status {
var errCode:Int
var msg:String
mutating func set(err:Int, txt:String){
errCode=err
msg=txt
}
}
and in my code I initialize a new instance of myObj
var mObj:myObj=myObj.init() {
didSet{
switch myObj.status.errCode{
case 1:
promptOkay()
case 113:
obj.errorHandling()
default:
log(txt: mObj.status.msg)
}
}
}
However, it never trigger didSet, even though internal functions should change the Status. I read that didSet is not triggered on init, but I do not run anything right now after initializing the class object that should run quite independently. I want to verify that the approach is okay before I go further and have to unravel everything again.
didSet must be declared on the property, not during initialization:
class MyObj {
var status: Status = Status(errCode: 0, msg: "Init") {
didSet {
// status did change
print("new error code: \(status.errCode)")
}
}
struct Status {
var errCode:Int
var msg:String
mutating func set(err:Int, txt:String){
errCode = err
msg = txt
}
}
}
let obj = MyObj()
obj.status.set(err: 10, txt: "error 10") // prints "new error code: 10"
At this point you can react to every changes made to obj.status in the didSetClosure.
Edit — React to didSet from outside the class
If you want to respond to changes from outside the MyObj, I would recommend using a closure:
class MyObj {
var status: Status = Status(errCode: 0, msg: "Init") {
didSet {
statusDidChange?(status)
}
}
// closure to call when status changed
var statusDidChange: ((Status) -> Void)? = nil
struct Status {
var errCode:Int
var msg:String
mutating func set(err:Int, txt:String){
errCode = err
msg = txt
}
}
}
This way, you can assign a closure from outside to perform custom actions:
let obj = MyObj()
obj.statusDidChange = { status in
// status did change
print("new error code: \(status.errCode)")
}
obj.status.set(err: 10, txt: "error 10") // prints "new error code: 10"
Edit 2 — Call didSet closure directly from initialization
You also can manually call the statusDidChange closure during the init.
class MyObj {
var status: Status = Status(errCode: 0, msg: "Init") {
didSet {
statusDidChange(status)
}
}
// closure to call when status changed
var statusDidChange: (Status) -> Void
init(status: Status, statusDidChange: #escaping (Status) -> Void) {
self.status = status
self.statusDidChange = statusDidChange
self.statusDidChange(status)
}
}
let obj = MyObj(status: MyObj.Status(errCode: 9, msg: "error 09")) { status in
// status did change
print("new error code: \(status.errCode)")
}
obj.status.set(err: 10, txt: "error 10")
This will print
new error code: 9
new error code: 10
Related
So I am currently working on my app and have come across a problem where I have to access a published variable in another observable class.
Here is some code on what I am trying to do
class PeriodViewModel: ObservableObject {
#Published var value = 1
}
class DataViewModel: ObservableObject {
#ObservedObject var periodViewModel = PeriodViewModel()
periodViewModel.value = 1
}
How would I be able to access the updated variable from periodViewModel in dataViewModel? Thanks.
[Note]: Ignore all the variables I am just showing the the flow of using different mangers.
Here is an example of function that I am using for my FirebaseDatabaseManage. You can see a clouser is passing into the function parameter. When your firebase insert function response after async you need to call your clouser which I named as completion.
class FirebaseDatabaseManager {
public func insertRecipe(with recipe: RecipeModel, completion: #escaping (Bool, String) -> Void) {
SwiftSpinner.show("Loading...")
let userID = UserDefaultManager.shared.userId
database.child(FireBaseTable.recipes.rawValue).child(userID).childByAutoId().setValue(recipe.convertToDictionary!, withCompletionBlock: { error, ref in
SwiftSpinner.hide()
guard error == nil else {
completion(false, "failed to write to database")
return
}
completion(true, ref.key ?? "no key found")
})
}
}
Now look at my ViewModel class in which I am calling my FirebaseManager method. On calling of completion I am updating my #Publisher Which you can use to update your UI.
class RecipeViewModel: ObservableObject {
#Publisher var id = 0
func createRecipe() {
FirebaseDatabaseManager.shared.insertRecipe(with: self.recipeModel) { status, id in
self.id = id
}
}
}
Hope this help your in understand your concepts.
In SwiftUI I have a List() with the name and status of 3 tasks:
import SwiftUI
struct ContentView: View {
let tasks = [TaskOne(), TaskTwo(), TaskThree()]
var body: some View {
List(tasks) { task in
Text(task.name)
Text(task.httpCode) // TODO
}
Button(action: { runTasks() }) {
Text("Run tasks")
}
}
private func runTasks() {
for task in tasks {
task.run()
// 1. Pass the output to the next task once finished
// 2. Update the status of the task in the List
}
}
}
Each task does http request but can have custom logic and they need to be separate:
protocol ExecutableTask {
var name: String { get set }
var httpCode: Int? { get set }
func execute(input: String) -> String
}
struct TaskOne: ExecutableTask {
var name = "one"
var httpCode: Int?
func execute(input: String) -> String {
// task specific logic here
// TODO: Perform HTTP call to "http://example.com/\(input)" and update httpCode in the List
return "first"
}
}
struct TaskTwo: ExecutableTask {
var name = "two"
var httpCode: Int?
func execute(input: String) -> String {
// task specific logic here
// TODO: Perform HTTP call to "http://example.com/\(input)" and update httpCode in the List
return "second"
}
}
struct TaskThree: ExecutableTask {
var name = "three"
var httpCode: Int?
func execute(input: String) -> String {
// task specific logic here
// TODO: Perform HTTP call to "http://example.com/\(input)" and update httpCode in the List
return "third"
}
}
How can I execute all of the serially in a background thread, passing the output from one to another and updating status in the List?
I suppose I need to update each row in the table by using #State and perform task execution using DispatchQueue. However I'm not entirely sure how to do that.
This made-up example displays a behavior I don't quite understand with structs/mutating functions/dynamic members. In a Playground, the changeStatus func will get called an infinite number of times and end up freezing Xcode. I expected a unique call to the closure to be made, executing the private method once.
// closure-returning subscript
#dynamicMemberLookup
struct LightManager {
private var status = false
private mutating func changeStatus() -> String {
status = !status
if status {
return "on"
} else {
return "off"
}
}
subscript(dynamicMember member: String) -> () -> Void {
return {
// following line causes an infinite loop
let newStatus: String = self.changeStatus()
print("Turn lights \(newStatus)")
}
}
}
let bedroomLights = LightManager()
bedroomLights.doStuff()
Is there a way to create a throwing function with a specified Error sub type that will be thrown? Below code is an example of how I'd like it to be.
protocol ValidatorError: Error {
var somePrintableThingy: String { get }
}
protocol Validator {
associatedType T
var model: T { get set }
init(model: T)
func validate() throws where Error: ValidatorError
}
Example Use Case
class SomeModelErrorNotValidatorError: Error {
case invalidUsername
}
class SomeModelError: ValidatorError {
case invalidUsername
var somePrintableThingy: String {
switch self { case .invalidUsername: return "haha" }
}
}
class SomeModel {
var username: String = ""
init(username: String) { self.username = username }
}
class SomeModelValidator: Validator {
var model: SomeModel
init(model: SomeModel) { self.model = model }
func validate() throws {
guard self.model.username.isEmpty else {
// this line should be fine
throw SomeModelError.invalidUsername
// if i replace it with this line it should not be fine
throw SomeModelErrorNotValidatorError.invalidUsername
}
}
}
class SomeModelViewController: UIViewController {
// .. some other codes here
func buttonClicked(_ sender: Any? = nil) {
let model = SomeModel(username: self.txtUsername.text ?? "")
let validator = SomeModelValidator(model: model)
do {
try validator.validate()
} catch let error {
// since I already know that this is a `SomeModelError`
// I could just do this
print(error.somePrintableThingy)
}
}
}
PS: The possible workaround for this is to create a non throwing function with a callback but I don't want to do that
PS PS: Another possible workaround for this would be to use a linting tool, but that's too much.
Example:
#noreturn func setOnlyPropertyGetterError(__function__: String) {
fatalError("\(__function__) is set-only")
}
var property: Property {
get {setOnlyPropertyGetterError(__FUNCTION__)}
set {//useful work}
}
Can we avoid having to pass __FUNCTION__?
I think this is what you want to achieve:
#noreturn func writeOnlyProperty(propertyName: String = __FUNCTION__) {
fatalError("Property \(propertyName) is write-only")
}
class Foo {
var blackHole: Int {
get { writeOnlyProperty() }
set { print("Consuming value \(newValue)") }
}
}
let foo = Foo()
foo.blackHole = 1 // Prints "Consuming value 1"
let bar = foo.blackHole // Produces fatal error "Property blackHole is write-only"