Issues with Swift's Combine framework CombineLatest - ios

I went through the WWDC video of "Introducing Combine" where it was said that whenever a publisher value gets updated the CombineLatest gets called and updated. But the snippet I created works oddly.
class Mango {
var enableButton = false
#Published var userName = "admin"
#Published var password = "poweruser"
#Published var passwordAgain = "poweruser"
var validatePassword: AnyCancellable {
Publishers.CombineLatest($password, $passwordAgain).map { (password, reenterpass) -> String? in
print("Is Password Same to \(password)? :", password == reenterpass)
guard password == reenterpass else { return nil }
return password
}.eraseToAnyPublisher()
.map { (str) -> Bool in
print("In Map", str != nil)
guard str != nil else { return false }
return true
}.assign(to: \.enableButton, on: self)
}
init() {
validatePassword
}
func checkSub() {
print("1. Is password same? ->",enableButton)
password = "nopoweruser"
print("2. Is password same? ->",enableButton)
}
}
When I initialize and call the function checkSub() where the publisher 'password' is updated the CombineLatest does not get called. Why is it behaving oddly?
Input:
let mango = Mango()<br>
mango.checkSub()
Output:
Is Password Same to poweruser? : true
In Map true
1. Is password same? -> true
2. Is password same? -> true

It seems like the issue is with memory management. The validatePassword cancellable is autoreleased, meaning that the subscription is completed as soon as you create it, since you do not retain it. Make it a property instead of computed property, using lazy var and it should work fine.
lazy var validatePassword: AnyCancellable = {
Publishers.CombineLatest($password, $passwordAgain).map { (password, reenterpass) -> String? in
print("Is Password Same to \(password)? :", password == reenterpass)
guard password == reenterpass else { return nil }
return password
}.eraseToAnyPublisher()
.map { (str) -> Bool in
print("In Map", str != nil)
guard str != nil else { return false }
return true
}.assign(to: \.enableButton, on: self)
}()
With lazy you are retaining the cancellable which gets released only after the object is released. So, this should work properly.

Related

Handling circular style events on observable sequence RxSwift

I would like to know the best possible way to handle the following situation, I have tried an approach as it will be described but I have encountered an issue of events calling each other repeatedly in a circular way hence it causes stackoverflow 😂
I have 4 observables as follows: -
let agreeToPrivacyPolicyObservable = BehaviorRelay<Bool>(value: false)
let agreeToTermsObservable = BehaviorRelay<Bool>(value: false)
let agreeToMarketingEmailObservable = BehaviorRelay<Bool>(value: false)
let agreeToAllOptionsObservable = BehaviorRelay<Bool>(value: false)
Goal:
Sync agree to all button with individual options. ie if agree to all is true/checked then force other options to be checked as well and vice-versa. Additionally if the previous state of all items were checked and either of them emit unchecked then remove a checkmark on Agree to all button.
The image below visualizes my goal above.
What I have tried:
Observable.combineLatest(
agreeToPrivacyPolicyObservable,
agreeToTermsObservable,
agreeToMarketingEmailObservable,
agreeToAllOptionsObservable
, resultSelector:{(termsChecked,privacyChecked,marketingChecked,agreeToAllChecked) in
switch (termsChecked,privacyChecked,marketingChecked,agreeToAllChecked) {
case (true, true, true,true):
//All boxes are checked nothing to change.
break
case (false,false,false,false):
//All boxes are unchecked nothing to change.
break
case (true,true,true,false):
// I omitted the `triggeredByAgreeToAll` flag implementation details for clarity
if triggeredByAgreeToAll {
updateIndividualObservables(checked: false)
}else {
agreeToAllOptionsObservable.accept(true)
}
case (false,false,false,true):
if triggeredByAgreeToAll {
updateIndividualObservables(checked: true)
}else {
agreeToAllOptionsObservable.accept(false)
}
default:
if triggeredByAgreeToAll && agreeToAllChecked {
updateIndividualObservables(checked: true)
}else if triggeredByAgreeToAll && agreeToAllChecked == false {
updateIndividualObservables(checked: false)
} else if (termsChecked == false || privacyChecked == false || marketingChecked == false ) {
agreeToAllOptionsObservable.accept(false)
}
}
}
})
.observeOn(MainScheduler.instance)
.subscribe()
.disposed(by: rx.disposeBag)
// Helper function
func updateIndividualObservables(checked: Bool) {
agreeToPrivacyPolicyObservable.accept(checked)
agreeToTermsObservable.accept(checked)
agreeToMarketingEmailObservable.accept(checked)
}
Explanation:
My attempt gives me Reentracy anomaly was detected error , which according to my observations is caused by events being triggered repeatedly. This seems to occurs in the default switch case (on my solution above). I think this solution is not good as I have to check which event triggered the function execution.
Is there any better approach or is it possible to refactor this solution into something easily manageable? Btw Feel free to ignore my implementation and suggest a different better approach if any. Thanks!
UPDATES (WORKING SOLUTION)
I successfully implemented a working solution by using #Rugmangathan idea (Found on the accepted answer). So I leave my solution here to help anyone in the future facing the same issue.
Here is the working solution: -
import Foundation
import RxSwift
import RxRelay
/// This does all the magic of selecting checkboxes.
/// It is shared across any view which uses the license Agreement component.
class LicenseAgreemmentState {
static let shared = LicenseAgreemmentState()
let terms = BehaviorRelay<Bool>(value: false)
let privacy = BehaviorRelay<Bool>(value: false)
let marketing = BehaviorRelay<Bool>(value: false)
let acceptAll = BehaviorRelay<Bool>(value: false)
private let disposeBag = DisposeBag()
func update(termsChecked: Bool? = nil, privacyChecked: Bool? = nil, marketingChecked: Bool? = nil, acceptAllChecked: Bool? = nil) {
if let acceptAllChecked = acceptAllChecked {
// User toggled acceptAll button so change everything to it's value.
acceptAll.accept(acceptAllChecked)
updateIndividualObservables(termsChecked: acceptAllChecked, privacyChecked: acceptAllChecked, marketingChecked: acceptAllChecked)
} else {
// If either of the individual item is missing change acceptAll to false
if termsChecked == nil || privacyChecked == nil || marketingChecked == nil {
acceptAll.accept(false)
}
updateIndividualObservables(termsChecked: termsChecked, privacyChecked: privacyChecked, marketingChecked: marketingChecked)
}
// Deal with the case user triggered select All from individual items and vice-versa.
Observable.combineLatest(terms, privacy, marketing,resultSelector: {(termsChecked,privacyChecked, marketingChecked) in
switch (termsChecked,privacyChecked, marketingChecked) {
case (true, true, true):
self.acceptAll.accept(true)
case (false,false,false):
self.acceptAll.accept(false)
default:
break
}
})
.observeOn(MainScheduler.instance)
.subscribe()
.disposed(by: disposeBag)
}
// MARK: - Helpers
private func updateIndividualObservables(termsChecked: Bool?,privacyChecked: Bool?, marketingChecked:Bool?) {
if let termsChecked = termsChecked {
terms.accept(termsChecked)
}
if let privacyChecked = privacyChecked {
privacy.accept(privacyChecked)
}
if let marketingChecked = marketingChecked {
marketing.accept(marketingChecked)
}
}
}
Your helper function updateIndividualObservables(:) triggers an event every time you update which in turn triggers the combineLatest you implemented above.
I would suggest you to keep a State object instead
struct TermsAndConditionState {
var terms: Bool
var privacy: Bool
var marketing: Bool
var acceptAll: Bool
}
In updateIndividualObservables method change this state and implement this state change with your respective checkboxes
func render(state: TermsAndConditionState) {
if state.acceptAll {
// TODO: update all checkboxes
} else {
// TODO: update individual checkboxes
}
}
This is a simple state machine. State machines are implemented in Rx using the scan(_:accumulator:) or scan(into:accumulator:) operator like so:
struct Input {
let agreeToPrivacyPolicy: Observable<Void>
let agreeToTerms: Observable<Void>
let agreeToMarketingEmail: Observable<Void>
let agreeToAllOptions: Observable<Void>
}
struct Output {
let agreeToPrivacyPolicy: Observable<Bool>
let agreeToTerms: Observable<Bool>
let agreeToMarketingEmail: Observable<Bool>
let agreeToAllOptions: Observable<Bool>
}
func viewModel(input: Input) -> Output {
enum Action {
case togglePrivacyPolicy
case toggleTerms
case toggleMarketingEmail
case toggleAllOptions
}
let action = Observable.merge(
input.agreeToPrivacyPolicy.map { Action.togglePrivacyPolicy },
input.agreeToTerms.map { Action.toggleTerms },
input.agreeToMarketingEmail.map { Action.toggleMarketingEmail },
input.agreeToAllOptions.map { Action.toggleAllOptions }
)
let state = action.scan(into: State()) { (current, action) in
switch action {
case .togglePrivacyPolicy:
current.privacyPolicy = !current.privacyPolicy
case .toggleTerms:
current.terms = !current.terms
case .toggleMarketingEmail:
current.marketingEmail = !current.marketingEmail
case .toggleAllOptions:
if !current.allOptions {
current.privacyPolicy = true
current.terms = true
current.marketingEmail = true
}
}
current.allOptions = current.privacyPolicy && current.terms && current.marketingEmail
}
return Output(
agreeToPrivacyPolicy: state.map { $0.privacyPolicy },
agreeToTerms: state.map { $0.terms },
agreeToMarketingEmail: state.map { $0.marketingEmail },
agreeToAllOptions: state.map { $0.allOptions }
)
}
struct State {
var privacyPolicy: Bool = false
var terms: Bool = false
var marketingEmail: Bool = false
var allOptions: Bool = false
}

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 KeyPaths 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"

Reassigning a value in an if statement in Swift [duplicate]

This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
the following is printing "no user with username" but is printing retVal as "false" ( I changed function to a string just for troubleshooting, ideally this should be bool ) I am new to swift and this is absolutely driving me crazy. it is making it to the chunk of code where retVal would get reassigned, but it isn't reassigning it
static func isUserNameUnique(_ username : String) -> String {
var retVal = "false"
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments { (document, error) in
if document!.isEmpty {
retVal = "true"
print("No user with username")
}
}
print("\(retVal)")
return retVal
}
func validateFields() -> String? {
//Check that all fields are filled in
if premierCodeTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" || userNameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" {
return "Please fill in all fields."
}
//Check unique username
let cleanedUserName = userNameTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
Utilities.isUserNameUnique(cleanedUserName) { res in
if !res {
// return "please choose a unique username"
}
}
return nil
}
You are trying to return a value synchronously while using and asynchronous method.
When you do newQuery.getDocuments execution continues without waiting for completion handler to be called. So after that line is executed, the return is executed, and THEN the completion handler gets called.
If you want to get a value from an asynchronous method, you need to create a method that takes a completion handler like the answer Khan gave you.
static func isUserNameUnique(_ username: String, completionHandler: #escaping (Bool) -> Void) {
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments { (document, error) in
completionHandler(document!.isEmpty)
}
}
You need a completion as the request is asynchnous , plus use Bool instead of a String
static func isUserNameUnique(_ username : String,completion:#escaping((Bool) ->())) {
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments { (document, error) in
completion(document!.isEmpty)
}
}
Call
Utilities.isUserNameUnique { res in
if !res {
// name exists
}
}
It's impossible to achieve what you want since newQuery.getDocuments isn't returning value instantly. It will answer you at some point by calling function that you passed to it.
Your code can be described as
func foo() -> String {
// set retVal to "false"
var retVal = "false"
// create query
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
// ask query to evaluate
newQuery.getDocuments { (document, error) in
// at some point probably after foo ends
if document!.isEmpty {
// if document is not empty set retVal to "true" (at this point there is no-one that could look at value of retVal)
retVal = "true"
print("No user with username")
}
}
// while query is evaluating in background
// print retVal (probably still "false")
print("\(retVal)")
// return retVal (probably still "false")
return retVal
}
Now let's fix your problem.
Solution could be:
class X {
private var document: <insert correct type here>? {
didSet {
// do what you want with document
}
}
func foo() {
let db = Firestore.firestore()
let newQuery = db.collection("users").whereField("userName", isEqualTo: username)
newQuery.getDocuments {
[weak self] (document, error) in // [weak self] is important!
// I have no idea on which thread firebase runs it's callback
// It's important that only one thread could modify self.document
// otherwise you will have race condition and a lot of strange behaviours
DispatchQueue.main.async {
self?.document = document;
}
}
}
}
If you really need to create func foo() -> String and you don't care that your thread will have to wait (UI will not respond, you will have 0 fps etc) you can do it using NSLock (I won't post code since it's really bad idea in most of the cases).

Properly configuring closure to capture results

I am writing some custom store methods for in app purchases; sort of a wrapper for SwiftyStore. The problem I'm running into is the inability to get the results from the closures before they exit.
Any suggestions on how to properly set them up? IE: Closures...
I have a function that checks for an existing subscription and returns true if it finds one in firebase, if it doesn't then it goes out to the apple store to verify a previously purchased subscription:
func checkSubscription() -> Bool {
var RetVal: Bool = false
var retStat: String = ""
var myVal: Bool = false
self.rootRef.child("users").child(self.userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let mySubType = value?["subtyp"] as? String ?? ""
// set value
if mySubType == "" {
// get receipt
if self.myStore.getReceipt() == true {
(myVal, retStat) = self.myStore.verifyPurchase(product: "com.xxxxx.xxxxx.monthly")
if myVal == true && retStat == "Valid" {
// we have a valid product update firebase
print("Valid")
} else if myVal == true && retStat == "Expired" {
// we have a valid product that is expired
print("Expired")
}
}
} else {
// we have a purchase, verify its not expired.
print("Purchased")
RetVal = true
}
}) { (error) in
print(error.localizedDescription)
}
return RetVal
}
The problem here is its dropping down to the return RetVal before the closure is complete so the function could be returning an invalid value. Not sure how I can fix this in the current setup, but any suggestions or pointers would be appreciated.
To expand on Tom's comment, if you want to return a result when the nested asynchronous function is complete, you could pass in a completion handler closure that uses the Result type that Swift offers like the following:
func checkSubscription(completion: #escaping (Result<Bool, Error>) -> Void) {
var RetVal: Bool = false
var retStat: String = ""
var myVal: Bool = false
self.rootRef.child("users").child(self.userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let mySubType = value?["subtyp"] as? String ?? ""
// set value
if mySubType == "" {
// get receipt
if self.myStore.getReceipt() == true {
(myVal, retStat) = self.myStore.verifyPurchase(product: "com.xxxxx.xxxxx.monthly")
if myVal == true && retStat == "Valid" {
// we have a valid product update firebase
print("Valid")
} else if myVal == true && retStat == "Expired" {
// we have a valid product that is expired
print("Expired")
}
}
} else {
// we have a purchase, verify its not expired.
print("Purchased")
RetVal = true
}
completion(.success(RetVal))
}) { (error) in
print(error.localizedDescription)
completion(.failure(error))
}
}
Calling the function using this type of completion handler would look something like this:
checkSubscription { (result) in
switch result {
case .success(let boolValue):
// do something with resulting boolean
break
case .failure(let error):
// do something with resulting error
break
}
}
func checkSubscription(completion: (_ scuess:Bool) ->()){
var RetVal: Bool = false
var retStat: String = ""
var myVal: Bool = false
self.rootRef.child("users").child(self.userID!).observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let mySubType = value?["subtyp"] as? String ?? ""
// set value
if mySubType == "" {
// get receipt
if self.myStore.getReceipt() == true {
(myVal, retStat) = self.myStore.verifyPurchase(product: "com.xxxxx.xxxxx.monthly")
if myVal == true && retStat == "Valid" {
// we have a valid product update firebase
print("Valid")
} else if myVal == true && retStat == "Expired" {
// we have a valid product that is expired
print("Expired")
}
}
completion(false)
} else {
// we have a purchase, verify its not expired.
print("Purchased")
completion(true)
}
}) { (error) in
print(error.localizedDescription)
completion(false)
}
return RetVal
}
call completion(true) whenever your retValue supposed to be true and completion(false) whenever your retValue supposed to be true
Then call this function this way:
checkSubscription { (sucuess) in
if(sucuess){
print("OK")
}else{
print("BAD")
}
}

iOS: create a struct in swift with credentials

I have to create a struct to check if two UITextField are valid.
And my idea is this, is it a good way to create a struct?
struct Credentials{
func isCorrect() -> Bool {
guard let username = emailTF.text else {
return false
}
guard let password = passwordTF.text else {
return false
}
return true
}
but I have some question:
how can I pass the values of emailTextField and passwordTF inside the struct? with an init method?
and is it better have some var or let inside the struct or is also a good idea have only a method inside a struct?
thanks
You can creare a struct like this
struct Credentials {
let email: String
let password: String
init?(email:String?, password: String?) {
guard let email = email, password = password else { return nil }
self.email = email
self.password = password
}
var correct: Bool {
// do your check
guard email.isEmpty == false && password.isEmpty == false else { return false }
return true
}
}
As you can see correct is a computed property, non a function because it does't need any params.
Usage
let correct = Credentials(email: emailTF.text, password: passwordTF.text)?.correct == true
Structs work roughly the same as classes. They can have variables and unlike classes if there are variables that are not explicitly given values swift will automatically create an initializer. You would do something like this.
let myStruct = Credentials(emailTF: "something", passwordTF: "something")
print(myStruct.isCorrect())
The function would use the let and var things just like a class does.
However, saying all this I would recommend just putting this function straight into your class, rather than in a struct. Just pass in values as parameters.

Resources