I have an Observable and would like it to filter elements that exists in an external array. The problem is that an element obviously can't be compared directly to an array, so I loop the array and then make the comparison. This doesn't work because the return statement needs to be outside of the for loop.
func scanAndFilterCoreData() -> Observable<BleHandler.BlePeripheral> {
let request = NSFetchRequest<LocalDoorCoreDataObject>(entityName: "LocalDoorCoreDataObject")
let result = self.coreDataHandler.fetchAll(fetchRequest: request)
return bleHandler.scan(serviceId: AppSettings.discoverServiceId)
.flatMap{ Observable.from($0) }
.filter { value in
for coreData in result {
return value.peripheral.identifier.uuidString == coreData.dPeripheralId
}
}
}
Being new to Rx I'm thinking there has to be some way to include the external array into the Observable as a second parameter, or how is this done?
You should use map instead of filter:
.map { value in
let uuidString = value.peripheral.identifier.uuidString
return result.filter { $0.dPeripheralId == uuidString }
}
Related
In my ViewModel file I have an observable array created after applying map on it. Now before returning it I want to check if it has any content or not. If there is nothing in there I want to return it without applying map. Following is my code:
func retrieveDeals(location: CLLocation?) -> Observable<[SaleItem]> {
let specials = nearestFlightSpecials.retrieveNearestFlightSpecials(userLocation: location)
let happyHourDeals = specials.map {
$0.filter { $0.isHappyHour }
}
return happyHourDeals
}
Before I return happyHourDeals I want to check if it contains any element or not. The above array is subscribed in view but I don't want to apply the above logic there. I want to keep it here in ViewModel.
I suspect what you want to do is filter out empty output:
func retrieveDeals(location: CLLocation?) -> Observable<[SaleItem]> {
let specials = nearestFlightSpecials.retrieveNearestFlightSpecials(userLocation: location)
let happyHourDeals = specials.map {
$0.filter { $0.isHappyHour }
}
.filter { !$0.isEmpty } // this is the line you need.
return happyHourDeals
}
Terminology is important here. Observables don't "contain" values. Observables don't return values, they emit events.
Your happyHourDeals will still be returned but with the filter line, it will no longer emit empty arrays. What this means is that whatever is subscribed to the value returned will not be updated if specials.map { $0.filter { $0.isHappyHour } } emits an empty array.
I am trying to achieve something similar in rxswift example project from RxSwift repo. But in my case there are dependent observables. I couldn't find any solution without using binding in viewmodel
Here is the structure of my viewmodel:
First the definitions of input, output and viewmodel
typealias UserListViewModelInput = (
viewAppearAction: Observable<Void>,
deleteAction: Observable<Int>
)
typealias UserListViewModelOutput = Driver<[User]>
typealias UserListViewModel = (UserListViewModelInput, #escaping UserApi) -> UserListViewModelOutput
Then there is actual implementation which doesn't compile.
let userListViewModel: UserListViewModel = { input, loadUsers in
let loadedUserList = input.viewAppearAction
.flatMapLatest { loadUsers().materialize() }
.elements()
.asDriver(onErrorDriveWith: .never())
let userListAfterDelete = input.deleteAction
.withLatestFrom(userList) { index, users in
users.enumerated().compactMap { $0.offset != index ? $0.element : nil }
}
.asDriver(onErrorJustReturn: [])
let userList = Driver.merge([loadedUserList, userListAfterDelete])
return userList
}
Viewmodel has two job. First load the user list. Second is delete a user at index. The final output is the user list which is downloaded with UserApi minus deleted users.
The problem in here in order the define userList I need to define userListAfterDelete. And in order to define userListAfterDelete I need to define userList.
So is there a way to break this cycle without using binding inside view model? Like a placeholder observable or operator that keeps state?
This is a job for a state machine. What you will see in the code below is that there are two actions that can affect the User array. When the view appears, a new array is downloaded, when delete comes in, a particular user is removed.
This is likely the most common pattern seen in reactive code dealing with state. So common that there are whole libraries that implement some variation of it.
let userListViewModel: UserListViewModel = { input, loadUsers in
enum Action {
case reset([User])
case delete(at: Int)
}
let resetUsers = input.viewAppearAction
.flatMapLatest { loadUsers().materialize() }
.compactMap { $0.element }
.map { Action.reset($0) }
let delete = input.deleteAction.map { Action.delete(at: $0) }
return Observable.merge(resetUsers, delete)
.scan(into: [User](), accumulator: { users, action in
switch action {
case let .reset(newUsers):
users = newUsers
case let .delete(index):
users.remove(at: index)
}
})
.asDriver(onErrorJustReturn: [])
}
I have a setup where I fetch some json data from a server to populate a table. There is a chance that the data has changed, so I fetch all the data each time. I map that data into a Realm object and persist it to the database. A primary ID is used to prevent duplication.
I use Realm notifications to keep the tableview/collection view in sync with the datasource. When an server request completes, objects are updated or added to the database, and the views automatically reload.
The problem is that all of the cells reload because all of the objects in the database get updated, even if they aren't actually any different because I'm just blindly using realm.add(object, update:true). Is there is a good way to prevent updating objects that haven't actually changed so that cell's aren't needlessly reloaded?
The solution I tried was to write an extension to Realm's Object class including a function that checks if any objects with the same primary ID exist, compare them, and add/update the Object iff they don't match. However, I have many classes of objects, and I couldn't find a way to get the objects type from the object itself without knowing its class to start with.
// All subclasses of ServerObject have id as their primaryKey
let object = database.objectForPrimaryKey(type:???, key: self.id)
I do not want to copy the same hunk of check-before-add code to every one of my classes because that's asking for trouble, so I need something that can go in a protocol or extension, or just a completely different way to go about handling the server's response.
It sounds like you want something along the lines of:
extension Object {
public func hasChanges(realm: Realm) -> Bool {
guard let obj = realm.objectForPrimaryKey(self.dynamicType, key: self["id"])
else { return true }
for property in obj.objectSchema.properties {
let oldValue = obj[property.name]
let newValue = self[property.name]
if let newValue = newValue {
if oldValue == nil || !newValue.isEqual(oldValue) {
return true
}
} else if oldValue != nil {
return true
}
}
return false
}
}
This would be used as:
let obj = MyObjectType()
obj.id = ...;
obj.field = ...;
if obj.hasChanges(realm) {
realm.add(obj, update: true)
}
For object with nested objects (lists), this modified solution seems to work well.
// This function is general and can be used in any app
private func hasChanges(realm: Realm) -> Bool {
guard let obj = realm.objectForPrimaryKey(self.dynamicType, key: self["id"])
else { return true }
for property in obj.objectSchema.properties {
// For lists and arrays, we need to ensure all the entries don't have changes
if property.type == .Array {
let list = self.dynamicList(property.name)
for newEntry in list {
if newEntry.hasChanges(realm) {
return true
}
}
// For all properties that are values and not lists or arrays we can just compare values
} else {
let oldValue = obj[property.name]
let newValue = self[property.name]
if let newValue = newValue {
if oldValue == nil || !newValue.isEqual(oldValue) {
return true
}
} else if oldValue != nil {
return true
}
}
}
return false
}
I'm trying to understand some code in a project I'm working on. I have an array property of strings:
var names: [String]!
func findName(name: String?) -> [Name]? {
if name != nil {
return nameManager.namesForSearchString(name)?.filter({self.names.contains($0.name)})
} else {
return nameManager.allNames.filter({self.names.contains($0.name)}) //<-what get's returned here?
}
}
What I don't understand is if the name is nil, what happens when .contains is called, and with that, what happens when .filter gets called? This is implemented in a Favorites class, and I need to call this function to return all favorites if a button is tapped, so what would I pass to this function to ensure that all the contents of Names: [Name] are returned?
On a lower level, I want to understand how .contains and .filter work and what gets returned if nil is passed to them.
Another version of the same method from a different commit (that I also did not write) is this:
func findFavorites(name: String?) -> [Station]? {
if name != nil {
return nameManager.namesForSearchString(name)!.filter({contains(self.names, $0.objectId)})
} else {
return nameManager.allNames.filter({contains(self.names, $0.objectId)})
}
}
I don't want to post a non-answer, but I do want this to be properly formatted so I guess a comment won't do. This might help you understand what's going on, and what happens with filter/contains. If you have any more questions, let me know, and I'll answer the question. If I'm completely off-base, let me know as well!
// I don't know why this is implicitely unwrapped, as a nil in this Array crashes Playground execution
var localNames: [String!] = ["Troy", "Bob", "Donald"]
// I'm just modelling what I know about NameManager
struct NameManager {
var allNames = [Name(name: "Bob"), Name(name: "Liz"), Name(name: "Anastasia")]
}
// I also assume the `name` in Name is a non-optional.
struct Name {
var name: String = "some name"
}
var nameManager = NameManager()
func findName(name: String?) -> [Name]? {
// Case where `name` is non-nil is excluded for demonstration purposes
// I have expanded all the closure short-hands so we always see what we're doing.
let allNames = nameManager.allNames
// namesMatchingName is of type [Name], that we get by applying a filter.
// `filter` works on a predicate basis: it goes through each element, one at a time,
// and checks if it meets the "predicate", that is, a boolean
// condition that returns true or false. If it DOES meet the criteria, it will be included in
let namesMatchingName = allNames.filter { (currentName) -> Bool in
// Now we're inside the filter-predicate. What we do here is check if the `currentName`
// is in `localNames`.
let namesHasCurrentName = localNames.contains(currentName.name)
// If the name IS in `localNames` we return true to the filter,
// which means it will be included in the final array, `namesMatchingName`.
return namesHasCurrentName
}
// So now we have all the names that appear in both `nameManager.allNames` and `localNames`
return namesMatchingName
}
findName(nil) // returns [{name: "Bob"}]
I'm trying to chain a number of promises that need to be resolved before returning.
In my case, for each element of databaseResult I need to fetch some data with a method that returns a promise.
Once I've fetched the data for every single element of the array I need to return to the calling method.
var toReturn = [MatchModel]()
//get my array of data
let databaseResults = MatchDatabaseManager.getMatchList();
//not sure what I'm doing
var promise = dispatch_promise{ 0 }
if(databaseResults.count > 0) {
return Promise { fulfill, reject in
for index in 0..<databaseResults.count {
print(index)
promise = promise.then { y -> Promise<Int> in
//Fetch the data I need ...
DataProvider.getUserProfileWithUserId(
(databaseResults[y].partnerUserProfile?.userId)!)
.then {(model) in {
//and use it to create the data I need to return
toReturn.append(MatchModel(realmModel:
databaseResults[y], partnerProfile: model))
}
}
return dispatch_promise { index }
}
}
//Once all the promises are fulfilled, toReturn contains the data I need and I can return it
promise.then{ x in {
fulfill(toReturn)
}
}
}
}
If I run this I get
PromiseKit: Pending Promise deallocated! This is usually a bug
I have very little experience with PromiseKit and documentation / exaples are scarce, so I have no idea what I'm missing here.
After asking the library developer for some help, I found out one must use "when" to wait for a series of promises to be completed.
The solution to the problem then becomes
return when(databaseResults.map{ (dbresult : MatchRealmModel) in
return DataProvider.getUserProfileWithUserId((dbresult.partnerUserProfile?.userId)!).then { model in
return MatchModel(realmModel: dbresult, partnerProfile: model)
}
})
I also found that a when() call with an empty array as the parameter can cause this issue.