Warm tip: This article is reproduced from serverfault.com, please click

Variable listener in iOS Swift

发布于 2020-12-07 10:22:00

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.

Questioner
André
Viewed
0
rraphael 2020-12-07 23:29:02

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