简体   繁体   中英

How to call a method once two variables have been set

I am using iOS Swift, and I am trying to understand how to execute a method once the value of two variables have been set up (non-null value) once the requests have finished.

After reading some documentation, I have found out some concepts which are interesting. The first one would be didSet , which works as an observer .

I could call the method using this method by simply using didSet if I would require just one variable

didSet

var myVar: String  0 {
    didSet {
        print("Hello World.")

    }
}

Nevertheless, I also need to wait for the second one myVar2 , so it would not work.

I have also found DispatchQueue , which I could use to wait a second before calling the method (the requests that I am using are pretty fast)

DispatchQueue

 DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute:  {
            print("Hello world")
        })

but I consider that this solution is not efficient.

Is there anyway to combine these two variables or requests in order to call a method once they have finishing setting the value?

Update I have tried to replicate David s answer, which I believe is correct but I get the following error on each \.

Type of expression is ambiguous without more context

I copy here my current code

var propertiesSet: [KeyPath<SearchViewController, Car>:Bool] = [\SearchViewController.firstCar:false, \SearchViewController.secondCar:false] {
    didSet {
        if propertiesSet.allSatisfy({ $0.value }) {
            // Conditions passed, execute your custom logic
            print("All Set")
        } else {
            print("Not yet")
        }
    }
}

 var firstCar: Car? {
     didSet {
        propertiesSet[\SearchViewController.firstCar] = true
     }
 }

 var secondCar: Car?  {
     didSet {
        propertiesSet[\SearchViewController.secondCar] = true
     }
 }

The variables are set individually, each one on its own request.

You could make your properties optional and check they both have values set before calling your function .

var varA: String? = nil {
    didSet {
        if varA != nil && varB != nil {
            myFunc()
        }
    }
}

var varB: String? = nil {
    didSet {
        if varA != nil && varB != nil {
            myFunc()
        }
    }
}

Or you can call your function on each didSet and use a guard condition at the start of your function to check that both of your properties have values, or bail out:

var varA: String? = nil {
    didSet {
        myFunc()
    }
}

var varB: String? = nil {
    didSet {
        myFunc()
    }
}

func myFunc() {
    guard varA != nil && varB != nil else { return }
    // your code
}

First, you should think very carefully about what your semantics are here. When you say "set," do you mean "assigned a value" or do you mean "assigned a non-nil value?" (I assume you mean the latter in this case.) You should ask yourself, what should happen if your method has already fired, and then another value is set? What if one of the properties has a value is set, then nil is set, then another value set? Should that fire the method 1, 2, or 3 times?

Whenever possible you should work to make these kinds of issues impossible by requiring that the values be set together, in an init rather than mutable properties, for example.

But obviously there are cases where this is necessary (UI is the most common).

If you're targeting iOS 13+, you should explore Combine for these kinds of problems. As one approach:

class Model: ObservableObject {

    @Published var first: String?
    @Published var second: String?
    @Published var ready = false

    private var observers: Set<AnyCancellable> = []

    init() {
        $first.combineLatest($second)
            .map { $0 != nil && $1 != nil }
            .assign(to: \.ready, on: self)
            .store(in: &observers)
    }
}

let model = Model()

var observers: Set<AnyCancellable> = []

model.$ready
    .sink { if $0 { print("GO!") } }
    .store(in: &observers)

model.first = "ready"
model.second = "set"
// prints "GO!"

Another approach is to separate the incidental state that includes optionals, from the actual object you're constructing, which does not.

// Possible parameters for Thing
struct Parameters {
    var first: String?
    var second: String?
}

// The thing you're actually constructing that requires all the parameters
struct Thing {
    let first: String
    let second: String

    init?(parameters: Parameters) {
        guard let first = parameters.first,
            let second = parameters.second
            else { return nil }
        self.first = first
        self.second = second
    }
}

class TheUIElement {
    // Any time the parameters change, try to make a Thing
    var parameters: Parameters = Parameters() {
        didSet {
            thing = Thing(parameters: parameters)
        }
    }

    // If you can make a Thing, then Go!
    var thing: Thing? {
        didSet {
            if thing != nil { print("GO!") }
        }
    }
}

let element = TheUIElement()
element.parameters.first = "x"
element.parameters.second = "y"
// Prints "GO!"

You need to add a didSet to all variables that need to be set for your condition to pass. Also create a Dictionary containing KeyPath s to your variables that need to be set and a Bool representing whether they have been set already.

Then you can create a didSet on your Dictionary containing the "set-state" of your required variables and when all of their values are true meaning that all of them have been set, execute your code.

This solution scales well to any number of properties due to the use of a Dictionary rather than manually writing conditions like if aSet && bSet && cSet , which can get out of hand very easily.

class AllSet {
    var propertiesSet: [KeyPath<AllSet, String>:Bool] = [\.myVar:false, \.myVar2:false] {
        didSet {
            if propertiesSet.allSatisfy({ $0.value }) {
                // Conditions passed, execute your custom logic
                print("All Set")
            } else {
                print("Not yet")
            }
        }
    }

    var myVar: String {
        didSet {
            propertiesSet[\.myVar] = true
        }
    }

    var myVar2: String {
        didSet {
            propertiesSet[\.myVar2] = true
        }
    }

    init(myVar: String, myVar2: String) {
        self.myVar = myVar
        self.myVar2 = myVar2
    }
}

let all = AllSet(myVar: "1", myVar2: "2")
all.myVar = "2" // prints "Not yet"
all.myVar2 = "1" // prints "All set"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM