How to return Observable<String> based on the conditions of Observable<Bool> RxSwift - swift

I'm quite new to RxSwift.
Is there any way I can create a function that will return Observable based on the conditions of two functions below
func isValidPassword(_ str: PublishSubject<String>) -> Observable<Bool> {
return str.asObservable().map {
validator.isValidPasswordRegex($0) && $0.count >= 8
}
}
func isNotEmpty(_ str: PublishSubject<String>) -> Observable<Bool> {
return str.asObservable().map {
$0.count != 0
}
}
This code below is just an example of what I'm trying to achieve, hoped you got the idea.
func someFunc(_ str:PublishSubject<String>) -> Observable<String> {
if !isValidPassword(str){
return "Not a valid password" //should return as an observable string
}
if isNotEmpty(str){
return "String is not empty" //should return as an observable string
}
}

I'm not sure about what you want to achieve here but I think you should use filter operator and not map. In this way you don't change the type of the starting observable but you remove elements that don't respect your conditions.
func isValidPassword(_ str: String) -> Bool {
validator.isValidPasswordRegex(str) && str.count >= 8
}
func someFunc(_ str: PublishSubject<String>) -> Observable<String> {
str
.filter(isValidPassword)
.filter { !$0.isEmpty }
}

Let me introduce you to zip... It allows you to combine multiple Observables.
func someFunc(_ str: Observable<String>) -> Observable<String> {
Observable.zip(isValidPassword(str), isNotEmpty(str))
.map { isValidPassword, isNotEmpty -> String in
if !isValidPassword {
return "Not a valid password"
}
if isNotEmpty {
return "String is not empty"
}
return "" // you didn't specify what it should return here...
}
}
Note that I updated the type signatures of your existing functions:
func isValidPassword(_ str: Observable<String>) -> Observable<Bool>
func isNotEmpty(_ str: Observable<String>) -> Observable<Bool>
Passing Subjects around like that is a recipe for disaster.
Subjects provide a convenient way to poke around Rx, however they are not recommended for day to day use.
-- Introduction to Rx
UPDATE
I think the code would be better if you implemented like this though:
func errorMessage(text: Observable<String>) -> Observable<String> {
text.map(errorMessage)
}
func errorMessage(_ str: String) -> String {
if !isValidPassword(str) {
return "Not a valid password"
}
if isNotEmpty(str) {
return "String is not empty"
}
return ""
}
func isValidPassword(_ str: String) -> Bool
func isNotEmpty(_ str: String) -> Bool
It's much easier to test this way.

Related

How to use buildExpression in Swift 5.2 Function Builders?

I understand that it's a draft proposal. I tried to implement a simple DSL for building a string, like so:
#_functionBuilder
struct StringBuilder {
static func buildExpression(_ string: String) -> [String] {
[string]
}
static func buildBlock(_ children: [String]...) -> [String] {
children.flatMap{ $0 }
}
}
func s(separator: String = "", #StringBuilder _ makeString: () -> [String]) -> String {
makeString().joined(separator: separator)
}
let z = s(separator: " ") {
"this"
"is"
"cool"
}
However, the compiler complains that "'String' is not convertible to '[String]'". This leads me to believe that buildBlock is the only part of the proposal currently implemented. (This is understandable given that in SwiftUI they are building a hierarchy of views, so that's all they need.)
Is this correct or am I doing something wrong? What is the correct way to use buildExpression?
ielyamani's answer shows how to build a working string builder such as I used in my example above. However, that does not solve the actual problem. I'm not trying to build a string builder. I'm trying to figure out function builders. The string builder is just an example. For example, if we wish to have a string builder that accepts integers, we could in theory do the following:
#_functionBuilder
struct StringBuilder {
static func buildExpression(_ int: Int) -> [String] {
["\(int)"]
}
// The rest of it implemented just as above
}
In this case, when the compiler encountered an Int, it would call buildExpression to then spit out our component type, in this case [String]. But as Martin R said in a comment to this question, buildExpression is not currently implemented.
I encountered the same issue today, it seems that buildExpression isn't implemented. I ended up making a workaround by using a protocol "ComponentProtocol" and then creating "Expression: ComponentProtocol" and "Component: ComponentProtocol". That works for me for now. I am hoping it'll be implemented later.
protocol ComponentProtocol: ExpressibleByIntegerLiteral, ExpressibleByStringLiteral {
var value: String { get }
}
struct Expression: ComponentProtocol {
let _value: String
var value: String { _value }
init(_ value: String) { _value = value }
init(integerLiteral value: Int) { self.init(value) }
init(stringLiteral value: String) { self.init(value) }
init<E: CustomStringConvertible>(_ value: E) {_value = String(describing: value) }
}
struct Component: ComponentProtocol {
let _values: [String]
var value: String { _values.joined(separator: ", ") }
init(integerLiteral value: Int) { self.init(value) }
init(stringLiteral value: String) { self.init(value) }
init<E: CustomStringConvertible>(_ value: E) { _values = [String(describing: value)] }
init<T: ComponentProtocol>(_ values: T...) { _values = values.map { $0.value } }
init<T: ComponentProtocol>(_ values: [T]) { _values = values.map { $0.value } }
}
#_functionBuilder struct StringReduceBuilder {
static func buildBlock<T: ComponentProtocol>(_ components: T ...) -> Component { Component(components) }
static func buildEither<T: ComponentProtocol>(first: T) -> Component { Component(first.value) }
static func buildEither<T: ComponentProtocol>(second: T) -> Component { Component(second.value) }
static func buildOptional<T: ComponentProtocol>(_ component: T?) -> Component? {
component == nil ? nil : Component(component!.value)
}
}
func stringsReduce (#StringReduceBuilder block: () -> Component) -> Component {
return block()
}
let result = stringsReduce {
Expression(3)
"one"
Expression(5)
Expression("2")
83
}
let s2 = stringsReduce {
if .random () { // random value Bool
Expression(11)
} else {
Expression("another one")
}
}
Since buildBlock(_:) takes a variadic number of arrays of strings, this would work:
let z = s(separator: " ") {
["this"]
["is"]
["cool"]
}
But that's still clunky. To take strings instead of Arrays of strings, add this function to StringBuilder which takes a variable number of strings:
static func buildBlock(_ strings: String...) -> [String] {
Array(strings)
}
And now you can do this:
let z = s(separator: " ") {
"Hello"
"my"
"friend!"
}
print(z) //Hello my friend!

using functions in swift

I'm new to programming. Please tell me what is wrong in this code! Why am I getting the output "(Function)"?
//first function
func admit(person: String) -> String {
return("\(person) can go")
}
//second function
func deny(person: String) -> String {
return("\(person) can not go")
}
//third function
func screen(onGuestList: String, person: String) -> (String) -> String {
if onGuestList == "yes"{
return admit(person:)
} else {
return deny(person:)
}
}
var outcome = screen(onGuestList: "yes", person: "Sapinder")
print(outcome)
I expect the output of "(person) can go", but the actual output is "(Function)".
Why am I getting the output "(Function)"?
because screen function is not returning a String, it returns (String) -> String instead.
Simply, the fix for it is to implement screen as:
func screen(onGuestList: String, person: String) -> String {
if onGuestList == "yes"{
return admit(person: person)
} else {
return deny(person: person)
}
}
so what is the difference here? Well, first of all now it returns a string instead of a function that takes a string and returns a string. Also, for calling admit and deny you have to mention the label (person) to pass a parameter to them.
Unrelated tip:
func screen(onGuestList: String, person: String) -> String {
return onGuestList == "yes" ? admit(person: person) : deny(person: person)
}
preferably, try to name the functions as verbs instead of nouns, we usually do this for properties (fields) but not methods (behaviors). For example: displayScreen instead of screen.
So what's the meaning of returning (String) -> String?
Briefly, Swift does allow such a thing. Consider the following example:
func sayHello() -> (String) -> String {
let functionToReturn: (String) -> String = { name in
return "Hello \(name)"
}
return functionToReturn
}
func takeMy(function: (String) -> String, name: String) {
print("I am about to print the function:")
print(function(name))
}
takeMy(function: sayHello(), name: "Sappie")
// I am about to print the function:
// Hello Sappie
as you can see, takeMy function is that takes another function as a parameter of type (String) -> String, therefore we passed sayHello() for it since it's signature matches the parameter type.
As a real world example, you could find many methods that parameters as functions when working with collections (for instance). As an example, the filter method:
func returnMoreThanfive(element: Int) -> Bool {
return element > 5
}
let array = [1,2,3,4,5,6,7,8,9]
let filteredArray = array.filter(returnMoreThanfive)
// [6, 7, 8, 9]
we passed to filter a function that takes an element and returns a boolean. Keep in mind It's just an example to make it more clear to you, however we usually do like this:
let filteredArray = array.filter { $0 > 5 }
Try
func admit(person: String) -> String {
return("\(person) can go")
}
//second function
func deny(person: String) -> String {
return("\(person) can not go")
}
//third function
func screen(onGuestList: String, person: String) -> String {
if onGuestList == "yes"{
return admit(person: person)
} else {return deny(person: person)
}
}
var outcome = screen(onGuestList: "yes", person: "Sapinder")
print(outcome)
What you were doing were returning a (String) -> String instead of String
In swift you can return a Function as a return type

Is it possible to have a same collection instance in a dictionary associated with multiple keys in swift?

I have a Set instance and want to put it into a Dictionary, and associate it with multiple keys so I can lookup/modify it in the future.
Following Python code is what I want to achieve in Swift.
s = set()
D = {}
D["a"] = s
D["b"] = s
D["a"].add("Hello")
D["a"].add("World")
print(D["b"]) # getting {"Hello", "World"} back
I tried something like following in Swift.
var s = Set<String>()
var D = Dictionary<String, Set<String>>()
D["a"] = s // copy of s is assigned
D["b"] = s // another copy of s is assigned
D["a"]!.insert("Hello")
D["a"]!.insert("World")
print(D["b"]!) // empty :(
Since collections in Swift hold value semantics, by the time I put a set into a dictionary, new instance is created. Is there any workaround? I know I could use NSMutableSet instead of Swift's Set, but I want to know how I can approach this by using collections with value semantics if possible.
Ah! Now we get to the heart of it. You just want a reference type based on stdlib rather than using the one that Foundation gives you. That's straightforward to implement, if slightly tedious. Just wrap a Set in a class. If you don't want full SetAlgebra or Collection conformance, you don't have to implement all of these methods. (And you might want some more init methods to make this more convenient, but hopefully those implementations are fairly obvious from your code needs.)
final class RefSet<Element> where Element: Hashable {
private var storage: Set<Element> = Set()
init() {}
}
extension RefSet: Equatable where Element: Equatable {
static func == (lhs: RefSet<Element>, rhs: RefSet<Element>) -> Bool {
return lhs.storage == rhs.storage
}
}
extension RefSet: SetAlgebra {
var isEmpty: Bool { return storage.isEmpty }
func contains(_ member: Element) -> Bool {
return storage.contains(member)
}
func union(_ other: RefSet<Element>) -> RefSet<Element> {
return RefSet(storage.union(other.storage))
}
func intersection(_ other: RefSet<Element>) -> RefSet<Element> {
return RefSet(storage.intersection(other.storage))
}
func symmetricDifference(_ other: RefSet<Element>) -> RefSet<Element> {
return RefSet(storage.symmetricDifference(other.storage))
}
#discardableResult
func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element) {
return storage.insert(newMember)
}
#discardableResult
func remove(_ member: Element) -> Element? {
return storage.remove(member)
}
#discardableResult
func update(with newMember: Element) -> Element? {
return storage.update(with: newMember)
}
func formUnion(_ other: RefSet<Element>) {
storage.formUnion(other.storage)
}
func formIntersection(_ other: RefSet<Element>) {
storage.formIntersection(other.storage)
}
func formSymmetricDifference(_ other: RefSet<Element>) {
storage.formSymmetricDifference(other.storage)
}
}
extension RefSet: Collection {
typealias Index = Set<Element>.Index
var startIndex: Index { return storage.startIndex }
var endIndex: Index { return storage.endIndex }
subscript(position: Index) -> Element {
return storage[position]
}
func index(after i: Index) -> Index {
return storage.index(after: i)
}
}

Observable with an a decision tree

I need a function that encapsulates a complicated IAP purchase tree into a simple attemptPurchase function that returns a boolean observable (true -> success, false -> cancelled, error -> any error)
But I am stumped at how to create that function, mainly because the start of the decision is async.
Decision tree and code below.
// fails -> missing return function
// but I cannot return the credit check, since the execution is different depending on the result
func attemptPurchase(amount: Int) -> Observable<Bool>{
let creditCheck = creditCheck(amount)
creditCheck.filter{$0}.subscribeNext{ _ in
return Observable.just(true)
}
creditCheck.filter{$0}.subscribeNext{ _ in
return confirmIAP().processIAP()
}
}
func creditCheck(amount: Int) -> Observable<Bool>{
return API.creditCheck.map{$0 > amount}
}
func confirmIAP() -> Observable<Bool> {
// UI for confirming IAP
}
func processIAP() -> Observable<Bool> {
// UI for uploading IAP on my server
}
This is how you could do it:
func attemptPurchase(amount: Int) -> Observable<Bool> {
return creditCheck(amount)
.flatMapLatest { (enoughCredit: Bool) -> Observable<Bool> in
if enoughCredit {
return Observable.just(true)
} else {
return confirmIAP()
.flatMapLatest { (isConfirmed: Bool) -> Observable<Bool> in
if isConfirmed {
return processIAP()
} else {
return Observable.just(false)
}
}
}
}
}
From iankeen's answer in the RxSwift slack group:
func attemptPurchase(amount: Int) -> Observable<Bool>{
return creditCheck(amount)
.flatMap { enough in
return (enough ? .just(true) : confirmIAP())
}
.flatMap { ready in
guard ready else { /* failure */ }
return processIAP()
}
}

Extending SignalProducerType in case Value is an Array<SomeProtocol>

I have a protocol for fetching database objects by PrimaryKey
typealias PrimaryKey = String
protocol PrimaryKeyConvertible {
var pkValue : PrimaryKey { get }
static func pkObject(key: PrimaryKey) -> Self?
}
and I want to extend the SignalProducerType to be able to operate on a SignalProducer.Value of that type.
So the Single object extension (single as in not Array) works fine and implemented as following:
extension SignalProducerType
where Value: PrimaryKeyConvertible
{
func fetchOnMainThread() -> SignalProducer<Value?, Error> {
return
self.map{ (obj: Value) -> PrimaryKey in
return obj.pkValue
}
.observeOn(UIScheduler())
.map{ (key: PrimaryKey) -> Value? in
return Value.pkObject(key)
}
}
}
But when I try to implement it on an Array of these elements i hit some compilation challenges:
extension SignalProducerType
{
func fetchOnMainThread<P: PrimaryKeyConvertible where Self.Value == Array<P>>() -> SignalProducer<[P], Error> { //(1)
return self.map({ (value: Self.Value) -> [PrimaryKey] in
return value.map{ $0.pkValue } //(2)
})
}
}
(1) i suspect that the signature is not communicating the idea to the compiler correctly
(2) produces the following error:
Type of expression is ambiguous without more context
the issue i'm trying to solve is how to let the compiler recognize the the SignalProducer is operating on an Array<P> where P is PrimaryKeyConvertible and have the .map operate on it accordingly ...
my current solution for the array issue is to implement using a generic function as listed below:
func fetchOnMainThread<Value: PrimaryKeyConvertible, Error: ErrorType>
(signal: SignalProducer<[Value], Error>) -> SignalProducer<[Value], Error> {
return signal
.map{ (convertibles: [Value]) -> [PrimaryKey] in
return convertibles.map { $0.pkValue }
}
.observeOn(UIScheduler())
.map{ (keys: [PrimaryKey]) -> [Value] in
return keys.flatMap{ Value.pkObject($0) }
}
}
and then used for example:
extension GoogleContact: PrimaryKeyConvertible {...}
extension GoogleContact {
static func fetchGoogleContactsSignal() -> SignalProducer<[GoogleContact], GoogleContactError> { ...}
}
and the call site would be like:
let signal = fetchOnMainThread(GoogleContacts.fetchGoogleContactsSignal()).onNext...
where I would prefer to have it as an extension where it would flow as usual
GoogleContacts
.fetchGoogleContactsSignal()
.fetchOnMainThread()
Update
another version of the function I've tried : (#J.Wang)
extension SignalProducerType
where Value == [PrimaryKeyConvertible]
{
func fetchArrayOnMainThread2<T: PrimaryKeyConvertible>() -> SignalProducer<[T], Error> {
return self
.map{ (values: Self.Value) -> [PrimaryKey] in
return values.map{ $0.pkValue }
}
.deliverOnMainThread()
.map{ (keys: [PrimaryKey]) -> [T] in
return keys.flatMap{ T.pkObject($0) }
}
}
}
let signal =
GoogleContacts
.fetchGoogleContactsSignal()
.fetchArrayOnMainThread2() //(3)
(3) Generates error:
'[PrimaryKeyConvertible]' is not convertible to '[GoogleContact]'
Hmm, although I'm not quite sure what the problem is, but I think the following implementation might be what you want.
extension SignalProducerType where Value == [PrimaryKeyConvertible]
{
func fetchOnMainThread() -> SignalProducer<[PrimaryKey], Error> {
return self.map { value in
value.map { $0.pkValue }
}
}
}
Try this:
extension SignalProducerType where Value == [PrimaryKeyConvertible]
{
func fetchOnMainThread<T: PrimaryKeyConvertible>() -> SignalProducer<[T], Error> {
return self.map { value in
value.map { $0.pkValue }
}.map { keys in
keys.flatMap { T.pkObject($0) }
}
}
}