Variable listener in iOS Swift - swift

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

How would I access a published variable from another class?

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.

SwiftUI serial DispatchQueue in background thread

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.

Call to my struct dynamic member causes an infinite loop within my closure?

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

How to have Error Type inference for a throwing function in a protocol

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.

Can you get __FUNCTION__ from the caller?

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"