Changing one property in a variable triggers subcriptions to other properties. RxSwift - ios

I have the following struct of properties for Chat
struct Chat {
var id = String()
var gender = String()
var date = Date()
init() {}
}
In a view controller, i declare an instance of Chat called observablechat, and then i used the flatmap operator to attempt and observe only changes in the date property of observablechat. However, if i change the gender property (as shown), the subscription gets triggered. I wonder why that is and how can i fix this code such that the subscription only looks at what happens to the date property and nothing else?
class ViewController: UIViewController {
var observablechat = Variable(Chat())
observablechat.asObservable()
.flatMap { (Chat) -> Observable<Date> in
return Observable.of(Chat.matchDate)
}.subscribe(onNext: { (r) in
print(r)
}).addDisposableTo(disposeBag)
self.observablechat.value.gender = "male"
//triggers subscription above.
}
}

First of all, why flatMap?
You need only distinctUntilChanged and map. DebugPrint will be triggered only twice: initial setup + date changed once. Check out the code below and feel free to ask questions.
import PlaygroundSupport
import RxSwift
struct Chat {
var id: String
var gender: String
var date: Date
init(id: String = "", gender: String = "", date: Date = Date(timeIntervalSince1970: 0)) {
self.id = id
self.gender = gender
self.date = date
}
}
final class YourClass {
lazy var variableChat: Variable<Chat> = Variable<Chat>(Chat())
var observableDate: Observable<Date> {
return variableChat
.asObservable()
.distinctUntilChanged({ (chatOld, chatNew) -> Bool in
return chatOld.date == chatNew.date
})
.map({ (chat) -> Date in
return chat.date
})
.shareReplay(1)
}
}
let value = YourClass()
value.observableDate
.subscribe(onNext: { (date) in
debugPrint(date.timeIntervalSince1970)
})
value.variableChat.value.gender = "male"
value.variableChat.value.date = Date()
value.variableChat.value.gender = "female"
value.variableChat.value.gender = "male"
value.variableChat.value.gender = "female"
P.S. the way to run RxSwift in playground: readme

Related

Issue with adding Data to an AnyObject Var so that I could make native ads work

for postdata in postdata {
if index < tableViewItems.count {
tableViewItems.insert(postdata, at: index)
index += adInterval
} else {
break
}
}
I'll need to add both PostData ads and Native Ads on the same AnyObject Var for me to get it to work and I can't find a way to add the Post Data because it says an error appears saying "Argument type 'PostData' expected to be an instance of a class or class-constrained type." Assistance would be very much appreciated, thank you.
edit 1
class Ad {
var postimage: String!
var publisher: String!
var timestring: String!
var timestamp = Date().timeIntervalSince1970
}
class PostDataAd: Ad {
// Declare here your custom properties
struct PostData1
{
var postimage: String
var publisher: String
var timestring : String
var timestamp = Date().timeIntervalSince1970
}
}
class NativeAd: Ad {
// Declare here your custom properties
struct NativeAd
{
var nativeAds: [GADUnifiedNativeAd] = [GADUnifiedNativeAd]()
}
}
My model class to merge both Data into one AnyObject Var
and then trying to append the Data from Firebase by doing this
var ads: [Ad] = [PostDataAd(), NativeAd()]
let postList = PostDataAd.PostData1(postimage: postimage, publisher:
postpublisher, timestring: postid, timestamp: timestamp)
self.ads.insert(postList, at:0)
an error occurs saying Cannot convert value of type 'PostDataAd.PostData1' to expected argument type 'Ad'
I hope I got what you want correctly. So basically you have two objects which you want to store in one array, under AnyObject. If that is correct, I recommend you to go in a bit of different direction. It is a nice example where you can use subclassing. You can declare a class called Ad, where you define the common properties which will be true for both PostDataAds and NativeAds.
class Ad {
// Add here the common elements you want to retrieve from both classes
var name: String = ""
}
After you define your PostDataAds and NativeAds inheriting from Ad:
class PostDataAd: Ad {
// Declare here your custom properties
}
class NativeAd: Ad {
// Declare here your custom properties
}
And if you want to define an array with two types of objects you can go:
let ads: [Ad] = [PostDataAd(), NativeAd()]
When retrieving you can check their type:
if let item = ads.first as? PostDataAd {
// The Ad is a PostDataAd
} else if let item = ad as? NativeAd {
// The Ad is a NativeAd
}
Or at some cases you don't even how to know the exact type, as you can access the properties defined in Ad without checking.
Update:
First of all your PostData1 and Ad objects are the same, you don't need to duplicate them. If you really want to have two classes you can inherit PostData1 from Ad.
class Ad {
var postimage: String
var publisher: String
var timestring: String
var timestamp = Date().timeIntervalSince1970
// You can add timestamp here also if you wish
init(postimage: String, publisher: String, timestring: String) {
self.postimage = postimage
self.publisher = publisher
self.timestring = timestring
}
}
class PostDataAd: Ad {
// Define some custom properties
}
And if you want to append PostData to the [Ad] array, you would do the following:
var ads: [Ad] = []
// Replace with your values
let postList = PostDataAd(postimage: "", publisher: "", timestring: "")
ads.insert(postList, at: 0)
// Appending NativeAd works also
let nativeAdd = NativeAd(postimage: "", publisher: "", timestring: "")
ads.append(nativeAdd)

How to send information from one class to another using rxswift?

How to send information from one class A to another class B using rxswift ? In iOS . for example I want to send my class Account.
Maybe rxswift is not for delegates or sending information at all.
https://www.reddit.com/r/swift/comments/7pxt4h/rxswift_vs_delegation_which_is_better/
class Account
{
var myId = 0
var Name = "name"
init (myId inputMyId:Int , Name inputName: String)
{
myId = inputMyId
Name = inputName
}
}
class B
{
var myAccount : Account
func receive()
{
// here self.myAccount should be replaced with myA.myAccount
// where myA is object of class A
}
}
class A
{
var myAccount : Account
func send()
{
// here myB.myAccount should be replaced with self.myAccount
// where myB is object of class B
}
}
solution 1
create class C that will make observables equal from A and B
class SimpleC
{
func makeAB()
{
var theB : SimpleB = SimpleB()
var theA : SimpleA = SimpleA()
theA.valueAccount = theB.valueAccount
theB.receive()
theA.send()
}
}
class SimpleA
{
var myAccount : Account = Account(myId : 0, Name : "")
var valueAccount = PublishSubject<Account>()
var myB : SimpleB?
func send()
{
// here myB.myAccount should be replaced with self.myAccount
// where myB is object of class B
self.myAccount = Account(myId : 1, Name : "MicrosoftTest5")
self.valueAccount.onNext(self.myAccount)
self.myAccount = Account(myId : 2, Name : "bestWorkTest5")
self.valueAccount.onNext(self.myAccount)
}
}
class SimpleB
{
var myAccount : Account = Account(myId : 0, Name : "")
var valueAccount = PublishSubject<Account>()
let disposer = DisposeBag()
func receive()
{
// here self.myAccount should be replaced with myA.myAccount
// where myA is object of class A
valueAccount.subscribe(onNext: { (newValue) in
print("SimpleB subscribe")
self.myAccount = newValue
var theString = "Account{" + "\(self.myAccount.myId)"
theString = theString + "} {"
theString = theString + "\(self.myAccount.Name)" + "}"
print(theString)
}).addDisposableTo(disposer)
}
}
Answering this question in hope to cover two scinarios -
Case 1: passing data while object creation (I can create and wrap it in variables, and pass along), this is same as vanilla swift
This holds true in cases of pushing/ presenting as well
class Account
{
var myId = Variable(0)
var name = Variable("")
init (myId inputMyId:Int , Name inputName: String)
{
myId.value = inputMyId
name.value = inputName
}
func passDataAnotherAccount() {
let anotherAccount = AnotherAccount(id: myId, name: name)
anotherAccount.makeChangesToVars()
}
}
class AnotherAccount {
var classListenerOfMyId = Variable(0)
var classListenerOfMyName = Variable("name")
init(id: Variable<Int>, name: Variable<String>) {
self.classListenerOfMyId = id
self.classListenerOfMyName = name
}
func makeChangesToVars() {
self.classListenerOfMyId.value = self.classListenerOfMyId.value + 1
self.classListenerOfMyName.value = Date().description
}
}
case 2 (Alternative approach to DELEGATION and callbacks)
lets say you want to know about changes from class AnotherAccount in previous class, you can make use of OBSERVABLES in that case
change the method from class Account to this
func passDataAnotherAccount() {
let anotherAccount = AnotherAccount(id: myId, name: name)
anotherAccount.makeChangesToVars()
// this will observe changes from AnotherAccount class
// we just created above in my current class,
// i.e. Account class
anotherAccount.classListenerOfMyId.asObservable().subscribe(onNext:{[weak self] valueChange in
print(valueChange)
}).disposed(by: DisposeBag())
anotherAccount.classListenerOfMyName.asObservable().subscribe(onNext:{[weak self] valueChange in
print(valueChange)
}).disposed(by: DisposeBag())
}
for handling delegations/callbacks you need to observe the changes by subscribing to the underlying Variables/Subjects/Observable.
Points to Note
Please use single dispose bag for one class, unlike me, i have disposed using DisposeBag() itself.
Read More about replacing delegates in RxSwift with Observables (this will be helpful)

Deep copy of a structure - how to?

I have the following structure, which I want to make a deep copy of, so I can treat those differently.
class NearbyStopsViewModel {
var stopViewModels: [StopViewModel]
}
class StopViewModel {
var stop: Stop
var name: String
var departures: [DepartureViewModel]?
}
class DepartureViewModel: NSObject {
var departure: Departure
var name: String
}
I having a hard time wrapping my head around making a deep copy of this structure, any ideas?
To create a 'deep' copy of a class instance you need to conform to NSCopying for those relevant classes, Structs are passed by value. I added my own implementation for the missing structures.
import Foundation
struct Departure {
var to: Stop
var from: Stop
init(from: Stop, to: Stop) {
self.from = from
self.to = to
}
}
struct Stop {
var name: String = ""
init(named: String) {
self.name = named
}
}
class NearbyStopsViewModel: NSCopying {
var stopViewModels: [StopViewModel]
init(stops: [StopViewModel] = []) {
self.stopViewModels = stops
}
func copy(with zone: NSZone? = nil) -> Any {
var stops: [StopViewModel] = []
self.stopViewModels.forEach { (stop) in
stops.append(stop.copy() as! StopViewModel)
}
let nearbysvm = NearbyStopsViewModel.init(stops: stops)
return nearbysvm
}
}
class StopViewModel: NSCopying {
var stop: Stop
var name: String = ""
var departures: [DepartureViewModel]
init(stop: Stop, named: String, with departures: [DepartureViewModel] = [] ) {
self.stop = stop
self.name = named
self.departures = departures
}
func copy(with zone: NSZone? = nil) -> Any {
var departuresCopy: [DepartureViewModel] = []
self.departures.forEach { (departure) in
departuresCopy.append(departure.copy() as! DepartureViewModel)
}
let stopvm = StopViewModel.init(stop: self.stop, named: self.name, with: departuresCopy)
return stopvm
}
}
class DepartureViewModel: NSObject, NSCopying {
var departure: Departure
var name: String = ""
init(name: String, departure: Departure) {
self.name = name
self.departure = departure
}
func copy(with zone: NSZone? = nil) -> Any {
let departure = DepartureViewModel.init(name: self.name, departure: self.departure)
return departure
}
}
// Structs are passed by Value, making a 'minimal' copy of themselves as they move.
var pennStation = Stop(named: "Pennsylvania Station")
print(pennStation)
let copyByValue = pennStation
print(copyByValue) // "Pennsylvania Station"
pennStation.name = "Penn. Station"
print(copyByValue) // "Pennsylvania Station"
// Classes are passed by Reference
let clarkLake = Stop(named: "Clark and Lake")
let stateLake = Stop(named: "State and Lake")
let clarkToState = Departure(from: clarkLake, to: stateLake)
// DepartureViewModel is your lowest level class that conforms to NSCopying
let departure = DepartureViewModel(name: "clark to state", departure: clarkToState)
print(departure) // This Memory Address should never change.
let referenceDeparture = departure
departure.name = "Unexpected delay"
print(referenceDeparture.name)
print(referenceDeparture) // Same Address as departure.
let deepCopyOfDeparture = departure.copy() as! DepartureViewModel // Copy() and mutableCopy() will return a passed-by-value copy.
print(deepCopyOfDeparture) // Different Memory Address as departure
let stopvm = StopViewModel(stop: pennStation, named: "Penn. Station", with: [departure])
print("Stop View Model's Child Departure Instance(s): \(stopvm.departures)")
let copyOfSVM = stopvm.copy() as! StopViewModel
print("Copy of SVM Child Departure Instance(s): \(copyOfSVM.departures)")
Output:
Stop(name: "Pennsylvania Station")
Stop(name: "Pennsylvania Station")
Stop(name: "Pennsylvania Station")
<StackOverflow.DepartureViewModel: 0x149dc4de0>
Unexpected delay
<StackOverflow.DepartureViewModel: 0x149dc4de0>
<StackOverflow.DepartureViewModel: 0x149da89a0>
<Stop View Model's Child Departure Instance(s): <StackOverflow.DepartureViewModel: 0x149dc4de0>
<Copy of SVM Child Departure Instance(s): <StackOverflow.DepartureViewModel: 0x149dc7630>
You can take advantage of value types, which are deeply copied by default, the caveat here is that all members are also value types (structs, enums, tuples), and those members contain only value types, and so on:
struct NearbyStopsViewModel {
var stopViewModels: [StopViewModel]
}
struct StopViewModel {
var stop: Stop
var name: String
var departures: [DepartureViewModel]?
}
struct DepartureViewModel: NSObject {
var departure: Departure
var name: String
}
struct Departure {
// fields needed for the struct
}
With a hierarchy like this, every assign to any of the above types will result in a deep copy of all underlying members. You let the compiler do all the work for you.
Beware, though, there might be some performance issues if you work with a large amount of these values (for a fair enough amount the performance thing is unnoticeable).

How to test a combineLatest observable with RxTest?

So I have this viewModel which has a validation observable that is simply the combination of 5 other signals into a boolean.
import RxSwift
class SchedulingFormViewModel: BaseViewModel {
let places = Variable<[String]>([])
var formIsValid: Observable<Bool>!
override init() {
super.init()
places.value = ["LUGAR 1", "LUGAR 2", "LUGAR 3"]
formIsValid = Observable.combineLatest(UserSession.currenctScheduling.dateSignal.asObservable(),
UserSession.currenctScheduling.carSignal.asObservable(),
UserSession.currenctScheduling.locationSignal.asObservable(),
UserSession.currenctScheduling.servicesSignal.asObservable())
{ (date: Date?, car: Car?, location: String?, services: [Service]?) in
print("DATE: \(date), CAR: \(car), LOCATION: \(location), SERVICES:\(services)")
guard let servicesArray = services else { return false }
let valid = date != nil && car != nil && location != nil && servicesArray.count > 0
return valid
}
}
}
And then I have my test class with a method that should just test whether the signal changes or not. I've tried a variety of approaches but none of them really gets the true value at the end. The observable only emits a true signal after the test has run.
My test class is setup like this
import XCTest
import RxSwift
import RxTest
#testable import Automobi
class SchedulingUnitTests: XCTestCase {
var viewModel: SchedulingFormViewModel!
var disposeBag: DisposeBag!
var scheduler: TestScheduler!
override func setUp() {
super.setUp()
viewModel = SchedulingFormViewModel()
UserSession.clearScheduling()
disposeBag = DisposeBag()
scheduler = TestScheduler(initialClock: 0)
}
}
I've tried the TestScheduler
func testFormValidation() {
UserSession.currenctScheduling.dateSignal.value = Date()
UserSession.currenctScheduling.carSignal.value = Car()
UserSession.currenctScheduling.servicesSignal.value = [Service(), Service()]
let location = scheduler.createHotObservable([
next(100, "TEST")
])
location.subscribe(onNext: { (text) in
print("LOCATION: \(text)")
UserSession.currenctScheduling.locationSignal.value = text
}).addDisposableTo(disposeBag)
let results = scheduler.createObserver(Bool.self)
scheduler.scheduleAt(0) {
self.viewModel.formIsValid.subscribe(results).addDisposableTo(self.disposeBag)
}
let expected = [
next(100, true)
]
scheduler.start()
XCTAssertEqual(results.events, expected)
}
Also
func testFormValidation() {
UserSession.currenctScheduling.dateSignal.value = Date()
UserSession.currenctScheduling.carSignal.value = Car()
UserSession.currenctScheduling.servicesSignal.value = [Service(), Service()]
let location = scheduler.createHotObservable([
next(100, "TEST"),
next(150, "TEST 2"),
next(200, "TEST 3")
])
location.subscribe(onNext: { (text) in
print("LOCATION: \(text)")
UserSession.currenctScheduling.locationSignal.value = text
}).addDisposableTo(disposeBag)
var results = [Bool]()
viewModel.formIsValid.subscribe(onNext: { (value) in
results.append(value)
}).addDisposableTo(disposeBag)
let expected = [true, true, true]
scheduler.start()
XCTAssertEqual(results, expected)
}
I've tried things like binding my formIsValid to a Variable and verifying its value at the end.
func testFormValidation() {
UserSession.currenctScheduling.dateSignal.value = Date()
UserSession.currenctScheduling.carSignal.value = Car()
UserSession.currenctScheduling.locationSignal.value = "TESTE"
UserSession.currenctScheduling.servicesSignal.value = [Service(), Service()]
sleep(5)
XCTAssertTrue(viewModel.formIsValid.value)
}
But I never get the expected result. I do get a true signal after all the tests fail and the code goes back to executing. Also when running the app the code executes as expecte I just need to catch it in the test. Any ideas?!
On your first try, you are probably getting a value on clock 0, so your expected value should be:
let expected = [
next(0, false)
next(100, true)
]

Construct typed dictionary using swift

I would like to create a typed map (Dictionary) class to meet the following requirements:
func testMap() {
var map = ActivitiesMap()
var activity = Activity()
activity.title = "Activity 1"
activity.uuid = "asdf1234"
map[activity.uuid] = activity
for (key, mapActivity) in map {
logger.debug("ACTIVITY MAP: \(key)=\(mapActivity)")
}
}
In short, I want this class to both be a dictionary such that it can be used in the for loop, however I want to ensure the keys are strings and the values are Activity objects.
I tried many variations of inheriting from Dictionary or typing the class, but so far it's resulted in multiple errors.
EDIT:
I don't think a simple generic dictionary will work, such as String:Activity. I want to have extra methods in the ActivityMap class, such as getAllActivitiesBetweenDates().
I need an actual class definition, not a generic dictionary expression.
You can make it looks like dictionary by implement subscript operator
And conform to Sequence protocol to support for-in loop
struct ActivitiesMap : Sequence {
var map = [String:Activity]()
subscript(key: String) -> Activity? {
get {
return map[key]
}
set(newValue) {
map[key] = newValue
}
}
func generate() -> GeneratorOf<(String, Activity)> {
var gen = map.generate()
return GeneratorOf() {
return gen.next()
}
}
// I can't find out type of map.generator() now, if you know it, you can do
//func generate() -> /*type of map.generator()*/ {
// return map.generate();
//}
}
This works for me. Not sure what is in your ActivitiesMap class, but just typed a Dictionary
class Activity{
var title:String = "";
var uuid: String = "";
}
func testMap() {
//var map = ActivitiesMap()
var map: Dictionary< String, Activity> = Dictionary< String, Activity>();
var activity = Activity()
activity.title = "Activity 1"
activity.uuid = "asdf1234"
map[activity.uuid] = activity
for (key, mapActivity) in map {
println("ACTIVITY MAP: \(key)=\(mapActivity)")
}
}
testMap();
This is my output:
ACTIVITY MAP: asdf1234=C11lldb_expr_08Activity (has 2 children)
class Activity {
var title=""
var id=""
init(id:String, title:String) { self.id=id; self.title = title }
}
var activities = [String:Activity]()
let a1 = Activity(id:"a1", title:"title1")
let a2 = Activity(id:"a2", title:"title2")
let a3 = Activity(id:"a3", title:"title3")
activities[a1.id] = a1
activities[a2.id] = a2
activities[a3.id] = a3
for (id,activity) in activities {
println("id: \(id) - \(activity.title)")
}
should print
id: a2 - title2
id: a3 - title3
id: a1 - title1
(key order not guaranteed to be the same)
You can use typealias keyword to define nice name of any type.
Here is how it can be used for your code:
class Activity { /* your code */ }
typealias ActivityMap = Dictionary<String, Activity>
var activityDict = ActivityMap()
And to support custom functions you can write an extension, example bellow:
extension Dictionary {
func getAllActivitiesBetweenDates(fromDate:NSDate, toDate:NSDate) -> Array<Activity>
// your code
return []
}
}
Usage:
let matchedActivities = activityDict.getAllActivitiesBetweenDates(/*date*/, /*date*/)

Resources