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
I tried this actually, and it triggers a change, but my goal was to get the change from outside the MyObj struct/class, so a frontend developer can use it for actions/prompts. didSet has to be called on an initialization btw, that is exactly what you did, just you called it on the Status init. But I am just thinking I can make status a standalone struct that MyObj can refer to, i.e. I declare it in the main code "var status= Status(...){didSet...}" and hand it to MyObj: "var mObj=MyObj(status: status...)"; then I can just call status.set inside the class. I'll try this right now!
I edited my answer to be able to forward the didSt outside of
MyObj
Great! My idea didn't work really, but I tried yours and compared the code, and the main issue seemed that it really doesn't trigger at init(). I need to set the init first, then call a func that triggers a change after (i.e. not from the init()). But your closure makes it also nice, because now only changes in status trigger on frontend, not all changes to the struct! In short: it is important to do "class.init()" that sets the didSet and then call some "class.start()" that triggers changes. Thanks for your help!
You can manually call the
statusDidChange
closure during the init if you need to. I edited with this case.