Map PassthroughSubject<[Int], Never> to AnyPublisher<Int, Never> - swift

I want to achieve the opposite of the collect operator. Instead of collecting 10 items and then getting a publisher with array type, I want to get a subject with array type and have the publisher get each array element one by one.
So I could get my intended output by doing this:
let subject = PassthroughSubject<Int, Never>()
let sub = subject.sink(receiveValue: {
print($0)
})
for val in 1...100 {
subject.send(val)
}
I want it to work like this, that the subject gets an array, and then add another operator to emit the values from the array one by one, like the opposite of the collect operator:
let subject = PassthroughSubject<[Int], Never>()
let sub = subject.sink(receiveValue: {
print($0)
})
subject.send(Array(1...100))
The issue is that the second implementation prints the whole array as it just gets it as a value. I want to add a stage in the stream, a little like collect works but the opposite, to manipulate the stream that it will emit the values from the array one by one.
Is there any operator to achieve that?

I solved it by using Sequence:
let sub = Publishers
.Sequence(sequence: Array(1...100))
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)

Related

How to apply .filter operator on individual elements of a CurrentValueSubject that's an array in Swift Combine?

I have a CurrentValueSubject that's an array of ChatModelFirebase. What I want to do is remove all ChatModelFirebase models that have a status field of "canceled". The issue is that when I apply .filter(..) operator it's acting on the entire array and not the individual items.
var chatFirebaseModels: CurrentValueSubject<[ChatModelFirebase], Never> = CurrentValueSubject([])
chatFirebaseModels
.filter({ (chatModelFireBaseArray) -> Bool in
// I want individual array items, not chatModelFireBaseArray
return false
})
Here is what code completion looks like:
You can see that .filter(..) is being applied to the [ChatModelFirebase] array and not the individual ChatModelFirebase models.
Is the answer here to just use .flatMap(..)? I am curious if there is a right way of using .filter(...), hence my question.
You can either use .map to return a filtered array:
chatFirebaseModels
.map { array in
array.filter { element in
// ... filter by element
}
}
Or use a pure-Combine chain (though, seems unnecessarily complicated):
chatFirebaseModels
.flatMap { Publishers.Sequence(sequence: $0) }
.filter { element in
// ... filter by element
}
.collect()

Chained RxSwift request with Realm add() / setValue()

Say I have 2 functions with 2 different Observable return types :
func getWatchedMovies() -> Observable<[TraktMovie]>
func getDetails(id: Int, language: String) - > Observable<TMDbMovie>
I'd like to flatMap each value in my getWatchedMovies() request to be able to request the details of each movie like this (I'm not sure it's the best way to do it though..)
traktDataManager?
.getWatchedMovies()
.flatMap({ (traktMovies) -> Observable<[TraktMovie]> in
let moviesObs = Observable.from(traktMovies)
let movieDetails = moviesObs.flatMap {
self.tmdbDataManager!.getMovieDetails(id: $0.ids.tmdb, language: Device.lang)
}
})
The thing is, I need to add each TraktMovie to Realm AND update a TraktMovie property, named tmdbMovie, with the nested request value of type TMDbMovie in Realm too.
What I mean is :
first, I need to loop in my [TraktMovie] array to save each value of it in Realm (say an object named traktMovie)
for traktMovie in traktMovies {
let realm = try! Realm()
realm.write {
realm.add(traktMovie)
}
}
second, I need to retrieve the details of each TraktMovie object with the second request (e.g. getDetails(_ , _)) : with something like flatMap ?
third, I need to update each traktMovie object property as follow with the value retrieved with the getDetails request (say tmdbMovie for the retrieved value):
traktMovie.setValue(tmdbMovie, forKeyPath: "tmdbMovie")
Here I have an object retrieved from the first request(getWatchedMovies()) named traktMovie and I update one of its property named tmdbMovie with the object retrieved from the second request (getDetails(_, _)) also named tmdbMovie
The thing is my first request returns an array and the second only a single object.
If I return the TMDbMovie object, I got only one object with onNext event and I loose my [TraktMovie] array.
Hope I'm clear enough.
Help is really appreciated ! 😅
You can try to use Observable.zip for this as in example below:
getWatchedMovies()
.flatMap({ [unowned self] (traktMovies) -> Observable<[TraktMovie]> in
let movieDetails = traktMovies.flatMap { movie in
// you can save in realm here
return Observable.just(movie)
.withLatestFrom(self.getMovieDetails(id: 0, language: "")) { movie, details in
// here you have both movie & movieDetails
return movie
}
}
return Observable.zip(movieDetails, { return $0 })
})
It may be a bit risky, if one of getMovieDetails will fail it will fail whole stream, as well it will require all getMovieDetails to emit onNext event in order that zipped Observable to emit a value.

Refactor Swift code with a closure

I want to refactor this Swift code with a closure syntax
var station: Station!
var allStations = [Station]()
var favoriteStationIds = [Int]()
for favoriteStationId in favoriteStationIds {
for station in allStations {
if station.stationId == favoriteStationId {
station.isFavorite = true
continue
}
}
}
You can use forEach, which have a trailing closure syntax instead of normal for ... in loops.
Moreover, you don't need to manually iterate through both arrays, you can use index(where:), that accepts a closure to find the station with the specified id as favoriteStationId.
favoriteStationIds.forEach{ id in
allStations[allStations.index(where: {$0.stationId == id})!].isFavorite = true
}
Bear in mind that above piece of code assumes all elements in favoriteStationIds are valid ids that are present in allStations (if this is not the case, use optional binding for the index instead of force unwrapping).

Swift array reduction: cannot use mutating member on immutable value

I have the following code that attempts to consolidate redundant elements of an array:
var items : [String] = ["hello", "world", "!", "hello"]
var mutableSet = Set<String>()
items.reduce(mutableSet, combine: { (set: Set<String>, element: String) in
return set.insert(element)
})
set.insert(element) gives me the error Cannot use mutating member on immutable value: 'set' is a 'let' constant. What's wrong and how can I fix it?
The problem with the OP's code is that the accumulator in the reduce is immutable so it won't allow you to use the mutating function insert().
A tidy solution is to define an non-mutating equivalent to insert() called inserting() in an extension to Set as follows.
extension Set {
//returns a new set with the element inserted
func inserting(_ element: Element) -> Set<Element> {
var set = self
set.insert(element)
return set
}
}
Now we can write the reduce as follows
var items : [String] = ["hello", "world", "!", "hello"]
let set = items.reduce(Set<String>()){ accumulator, element in
accumulator.inserting(element)
}
In Swift, collections are value types. Value-typed variables declared with let (as implicitly are function parameters) cannot be modified. Additionally, your closure returns nothing, so reduce will probably not succeed.
I believe that reduce is not the best-suited tool for this task. Consider this for loop instead:
var set = Set<String>()
for element in items { set.insert(element) }
Another even simpler option would be to use the unionInPlace method:
var set = Set<String>()
set.unionInPlace(items)
Even better perhaps, create the set straight from the collection:
var set = Set<String>(items)
The 'set' value returned is a constant. This is important as it is the accumulator, which represents the values that have accumulated, thus far. It should not change in your closure.
Here is an example from a project I'm working on at the moment, where I want to find all of the unique performers, across many theatrical performances. Notice how I am using union, which does not modify the constant value 'performers', but instead consumes it to produce a new value.
let uniquePerformers = performances.reduce(Set<Performer>(), { (performers: Set<Performer>, performance) -> Set<Performer> in
return performers.union(Set(performance.performers))
})

Mixing for-in and if-let in Swift

Can I merge a for-in and if-let in one statement?
for item in array {
if let f = item as? NSDictionary {
result.addObject(newFile(f))
}
}
array is made by a JSON, so I don't know if each item is a NSDictionary or not. I have to check.
I was looking for something like this:
for item as? NSDictionary in array {
// code
}
Like Python or Ruby.
#nickfalk is on the right track, but we can do better. His result unfortunately returns [AnyObject], which you can't then call newFile with (I assume). But that's ok, we can get the rest of the way pretty easily.
What you want is partial map. That is to say, you want to map some (but possibly not all) of the elements of one list to another list (from AnyObject to File, if we can). So there must be some rule for choosing, and some rule for mapping. Optional let's us combine those. Let's call the function that does that f. Then its type is:
f: T->U?
So there's some magic function that will possibly convert T to U. We want to map with that. Sounds easy:
extension Array {
func partialMap<U>(f: T->U?) -> [U] {
var result = [U]()
for x in self {
if let u = f(x) {
result.append(u)
}
}
return result
}
}
So now we've hidden all the nasty mutation and var and whatnot down deep where we don't have to look at it. We have a function that takes a mapping function from "something" to "maybe something else" and returns a list of "something elses that we could map."
Now everything is nice and immutable and reusable:
let result = array.partialMap { ($0 as? NSDictionary).map(newFile) }
Whoa there. What's that map in the middle? Well, as? returns NSDictionary?. When you map an optional, then if the optional is None, it returns None, otherwise it applies the function to the value and wraps it in Some. So this whole thing takes AnyObject and returns File? just like we wanted. One partialMap later we have our answer.
I would probably just go for something like:
let result = array.filter() { $0 is NSDictionary }
If you need result to be an NSDictionary array, you can just cast it:
let result = array.filter() { $0 is NSDictionary } as [NSDictionary]
If your goal is to reduce an NSArray to an array only containing NSDictionary filter is a very powerful tool. Create the appropriate filtering function:
func filterForNSDictionary(object: AnyObject) -> Bool{
return object.isKindOfClass(NSDictionary)
}
Then simply pass in you array and function to the filter function
let result = filter(array, filterForNSDictionary)
As #RobNapier points out my solution above will end up with a result array being of the type [AnyObject] this can of course easily be remedied:
let result = filter(array, filterForNSDictionary) as [NSDictionary]
This could be considered risky, if you force the array to be of the wrong type. as [NSString] (for instance9 would most likely blow up in your face down the line...
Rob's solution being pure awesome cleverness of course and #MattGibson delivering the perfect shorthand, while exposing me as an absolute beginner in this field.