How to merge two observable to another observable with RxSwift? - ios

How to merge two observable to another observable with RxSwift?
In my case:
struct ViewModel {
let items: Observable<[MediaAsset]>
init(type: Observable<AssetMediaType>, provider: DataProvider) {
items = Observable.combineLatest(type, provider.rx.didAuthorize, resultSelector: { (type, status) in
return provider.rx.fetchMedia(type)
})
}
public var didAuthorize: Observable<AuthorizeResult> {
return Observable.create { o in
//....
}
}
public func fetchMedia(_ withType: AssetMediaType) -> Observable<[MediaAsset]> {
return Observable.create { observer in
//....
}
}
but xcode build failed for reason:
Cannot convert value of type '(AssetMediaType, ) -> Observable<[MediaAsset]>' to expected argument type '(, _) -> _'

If you want them to fire at the same time you need to use a Zip
let zipped = Observalbe.zip(a, b){$0}
or in your code
struct ViewModel {
let items: Observable<[MediaAsset]>
init(type: Observable<AssetMediaType>, provider: DataProvider) {
items = Observable.zip(type, provider.rx.didAuthorize){$0}
}

Root Issue:
Your types are mismatched.
Assuming that those functions belong to DataProvider.rx, instead of ViewModel, This code:
Observable.combineLatest(type, provider.rx.didAuthorize, resultSelector: { (type, status) in
return provider.rx.fetchMedia(type)
})
returns an Observable< Observable<[ViewModel.MediaAsset]>>
but you declared items as an Observable<[MediaAsset]>
Solution: Flatten the observable
items = Observable.combineLatest(type, provider.didAuthorize) { type,status in
return (type,status)
}
.flatMapLatest { type,status in
return provider.fetchMedia(type)
}
Note: It's unclear how you are using status. I passed it to the flatMapLatest, but it won't be passed along any further.

Related

how to pass expression as argument in a function in Swift?

I am a junior iOS developer. I want to refactor my code. currently I want to refactor my realm methods. to save or delete data in Realm, I need to use a lot of do try catch block all over the place, so I want to make a service to handle this. I have tried but I don't know if this is the correct way or not
import RealmSwift
class RealmService {
private init() {}
static let shared = RealmService()
var realm = try! Realm()
func save(expression: ()) {
do {
try realm.write {
// I want to pass the expression in this block here
expression
}
} catch {
post(error)
}
}
}
usage:
func saveToRealmDatabase(wishList: WishList, product: Product) {
RealmService.shared.save(expression: {
// I want to pass expression in the block in here as argument
wishList.products.append(product)
}())
}
I want to pass the expression wishList.products.append(product) as argument of the function in RealmService class. is there a better way to achieve that ?
Your "expression" can be represented by the type () -> Void - a closure that accepts no parameters and returns nothing.
This is how your method should be declared:
func save(expression: #escaping () -> Void) {
do {
try realm.write {
expression()
}
} catch {
post(error)
}
}
This is how you should use it:
func saveToRealmDatabase(wishList: WishList, product: Product) {
RealmService.shared.save(expression: {
wishList.products.append(product)
})
}
From the approach you are trying i can guess you want to do something like,
RealmService.shared.save(expression: {
wishList.products.append(product)
realm.add(wishList.products)
})
So based on the understanding that you want to perform the addition operation in that callback, i would rather suggest the below implementation where you don't need to implement the callback for every operation. Rather when you are interested to know the status of the operation then only you would implement the callback. Check the below snippet,
class RealmService {
private init() {}
static let shared = RealmService()
var realm = try! Realm()
/// Saving a list of object i.e, List<Object>
func save<O, L: List<O>>(_ list: L, callback: ((Error?) -> Void)? = nil) where O: Object {
do {
try realm.write {
realm.add(list)
callback?(nil)
}
} catch {
callback?(error)
}
}
/// Saving a single object i.e, `Object`
func save(_ object: Object, callback: ((Error?) -> Void)? = nil) {
do {
try realm.write {
realm.add(object)
callback?(nil)
}
} catch {
callback?(error)
}
}
}
Usage
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var age = 0
}
let tomy = Dog()
tomy.name = "Tomy"
let johny = Dog()
johny.name = "Johny"
let dogs = List<Dog>()
dogs.append(tomy)
dogs.append(johny)
//When you are not interested in save operation status
RealmService.shared.save(dogs)
//When you want to handle error while saving
RealmService.shared.save(dogs) { [weak self] in self?.showAlert($0) }
func showAlert(_ error: Error?) {
guard let e = error else { return }
showAlert(alertTitle: "Error", alertMessage: e.localizedDescription, actionTitle: "Back")
}

Cannot get data in handler for when(resolved:)

PromiseKit 6
I have a function that fetches a list of alerts from a db, I then need to use the contentId property on each to fetch the content and attach to the appropriate alert item.
Maybe there's a better way, but for now, what I came up with is to collect the promises into a list and call when(resolved:). I chose that because if any promise fails, I want to be able to return all those that can pass.
Inside my then handler in the function attachContents, the handler gets passed this type [Result] but the only field I can access when I map the list is isFulfilled
I checked what type Result was and found this:
public enum Result<T> {
case fulfilled(T)
case rejected(Error)
}
public extension PromiseKit.Result {
var isFulfilled: Bool {
switch self {
case .fulfilled:
return true
case .rejected:
return false
}
}
}
It led me to the first one, in Resolver.swift so I'm not sure why I can't get the data by calling fulfilled
private func getContent(for alert: Model) -> Promise<ViewModel> {
return firstly { () -> Promise<Any> in
if let contentType = AlertContentType(rawValue: alert.type) {
switch contentType {
case .news:
return newsService.get(contentId: contentId, superareaId: superareaId).compactMap { $0 as Any }
case .insight:
return insightService.get(contentId: contentId, superareaId: superareaId).compactMap { $0 as Any }
}
} else { throw DataError.Missing(error: "Could not retrieve type!") }
}.compactMap { content in
var viewModel = alert.toViewModel()
viewModel.content = content
return viewModel
}
}
private func attachContents(to alerts: [Model]) -> Promise<[ViewModel]> {
return firstly { () -> Guarantee<[Result<ViewModel>]> in
let contentPromises: [Promise<AlertViewModel>] = alerts.map {
return self.getContent(for: $0)
}
return when(resolved: contentPromises)
}.then { (vModels: [Result<ViewModel>]) -> Promise<[OIAlertViewModel]> in
vModels.map { (vm: Result<OIAlertViewModel>) in
// vm.isFulfilled is the only accessible property here
// can't call
}
}
}
Result is an enum of .fulfilled or .rejected
vModels.map { (vm: Result<OIAlertViewModel>) in
switch vm {
case .fulfilled(let alert):
print(alert)
case .rejected(let error):
print(error)
}
}

How do declare methods that return a Sequence of custom Structs in Swift

What is the proper way to declare a function that return a Sequence in Swift 4. I tried the following but receive a error stating:
error: Models.playground:29:13: error: cannot convert return expression of type 'Cars' to return type 'S'
return Cars(cars)
^~~~~~~~~~
as! S
Here is the code I used:
import Foundation
struct Car {
let make:String
let model:String
}
class Cars: Sequence, IteratorProtocol {
typealias Element = Car
var current = 0
let cars:[Element]
init(_ cars:[Element]) {
self.cars = cars;
}
func makeIterator() -> Iterator {
current = 0
return self
}
func next() -> Element? {
if current < cars.count {
defer { current += 1 }
return cars[current]
} else {
return nil
}
}
}
let cars = Cars([Car(make:"Buick", model:"Century"), Car(make:"Buick", model:"LaSabre")])
func getCars<S:Sequence>(cars:[Car]) -> S where S.Iterator.Element == Car {
return Cars(cars)
}
The return value cannot be a specialization of the Sequence protocol.
You can either return Cars itself, as Daniel suggested, or –
if you want to hide the implementation of the sequence – a
“type-erased” sequence:
func getCars(cars:[Car]) -> AnySequence<Car> {
return AnySequence(Cars(cars))
}
or even
func getCars(cars:[Car]) -> AnySequence<Car> {
return AnySequence(cars)
}
AnySequence is a
generic struct conforming to the Sequence protocol which forwards
to the underlying sequence or iterator from which it was created.
See also A Little Respect for AnySequence
for more examples.
Remark: Similarly, it is possible to make Cars a Sequence
by returning a type-erased iterator which forwards to the array
iterator:
class Cars: Sequence {
typealias Element = Car
let cars: [Element]
init(_ cars: [Element]) {
self.cars = cars;
}
func makeIterator() -> AnyIterator<Element> {
return AnyIterator(cars.makeIterator())
}
}
The problem is that you are using a generic for a specific type. You can either return a Cars element (note that Cars conforms to Sequence, so you are returning a Sequence here):
func getCars(cars: [Car]) -> Cars {
return Cars(cars)
}
or use a generic (also a Sequence, since it is defined in the generic):
func getCars<S: Sequence>(cars: [Car]) -> S {
return cars as! S
}

Swift Collection Of Objects Conforming To Protocol With Associatedtype

I have a question (maybe regarding type erasure). Imagine the following scenario:
public protocol DataItem {
associatedtype T
var action: ((_ item: T) -> Void)? {get}
}
struct UserItem: DataItem {
typealias T = UserItem
// Custom Properties
let name: String
// Protocol: DataItem
let action: ((T) -> Void)?
}
struct DriverItem: DataItem {
typealias T = DriverItem
// Custom Properties
let licenseNumber: String
// Protocol: DataItem
let action: ((T) -> Void)?
}
let items = [
UserItem(name: "Dexter", action: { (item) in print(item.name)}),
DriverItem(licenseNumber: "1234567890", action: { (item) in print(item.licenseNumber)})
]
items.forEach {
$0.action?($0)
}
I have a DataItem which is an abstract data item for a UITableViewCell and which has an action property that should be called when the cell is selected. My question is how can I create an array of DataItem objects, select an item from this list (or iterate over it) and call the respective action, that will print the name of the UserItem and the license number of the DriverItem. But with that implementation above the compiler complains with the following message that the items list can only be of type [Any]...
Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
That way I cannot call the action declared in the protocol DataItem. I tried to wrap my head around type erasures but couldn't understand it yet...
Would be happy if anyone comes up with a solution...
The error just asks you to add a type to items:
let items: Array<Any> = [
UserItem(name: "Dexter", action: { (item) in print(item.name)}),
DriverItem(licenseNumber: "1234567890", action: { (item) in print(item.licenseNumber)})
]
Not sure if this won't lead to more errors because I am not sure if you can call a completion block while creating the array.
Edit
I think what you are trying to do is get a call when the variable is called. The way to do this would be to use the get you already typed but didnt complete, like so:
var licenseNumber: String {
get {
print("doSomething!")
return self.licenseNumber
}
}
The array would become something like:
let items: Array<Any> = [
UserItem(name: "Dexter"),
DriverItem(licenseNumber: "1234567890")
]
It's not that hard with no additional requirements:
struct UserItem {
let name: String
func action() {
print(name)
}
}
struct DriverItem {
let licenseNumber: String
func action() {
print(licenseNumber)
}
}
let data = UserItem(name: "Test")
let closure = { print("Some object data \(data.name)") }
let items = [
UserItem(name: "Dexter").action,
DriverItem(licenseNumber: "1234567890").action,
closure
]
items.forEach {
$0()
}

Heterogeneous mixture of protocol types, including a generic protocol

protocol ParentProtocol { }
protocol ChildProtocol: ParentProtocol { }
protocol Child_With_Value_Protocol: ParentProtocol {
associatedType Value
func retrieveValue() -> Value
}
Attempting to create a single array of type ParentProtocol that contains both ChildProtocol and Child_With_Value_Protocol. Is there any possible way to create a function that loops through the heterogeneous array and returns the values of just type Child_With_Value_Protocol?
This may require an architecture change. Open to all solutions.
Attempted Failed Solution #1
var parents: [ParentProtocol] = [...both ChildProtocol & Child_With_Value_Protocol...]
func retrieveValues() -> [Any] {
var values = [Any]()
for parent in parents {
if let childWithValue = parent as? Child_With_Value_Protocol { // Fails to compile
values.append(childWithValue.retrieveValue())
}
}
return values
}
This fails with an error of protocol 'Child_With_Value_Protocol' can only be used as a generic constraint because it has Self or associated type requirements which makes sense since the compiler would not know the type when converted to just Child_With_Value_Protocol, this leads to the next failed solution.
Attempted Failed Solution #2
If the array was a homogeneous array of just Child_With_Value_Protocol, type erasing could be used to retrieve the values.
var parents: [ParentProtocol] = [...both ChildProtocol & Child_With_Value_Protocol...]
struct AnyValue {
init<T: Child_With_Value_Protocol>(_ protocol: T) {
_retrieveValue = protocol.retrieveValue as () -> Any
}
func retrieveValue() -> Any { return _retrieveValue() }
let _retrieveValue: () -> Any
}
func retrieveValues() -> [Any] {
var values = [Any]()
for parent in parents {
values.append(AnyValue(parent).retrieveValue()) // Fails to compile
}
return values
}
This fails to compile due to the fact that the struct AnyValue has no initializer for the ParentProtocol.
Attempted Failed Solution #3
struct AnyValue {
init<T: Child_With_Value_Protocol>(_ protocol: T) {
_retrieveValue = protocol.retrieveValue as () -> Any
}
func retrieveValue() -> Any { return _retrieveValue() }
let _retrieveValue: () -> Any
}
var erased: [AnyValue] = [AnyValue(...), AnyValue(...), AnyValue(...)]
func retrieveValues() -> [Any] {
var values = [Any]()
for value in erased {
values.append(value.retrieveValue())
}
return values
}
Unlike the other solutions, this solution actually compiles. Problem with this solution resides in the fact that the array erased can only hold values of the type-erased versions of Child_With_Value_Protocol. The goal is for the array to hold types of both Child_With_Value_Protocol and ChildProtocol.
Attempted Failed Solution #4
Modifying the type-erase struct to include an initializer for ParentProtocol still creates a solution that compiles, but then the struct will only use the less specific init, instead of the more specific init.
struct AnyValue {
init?<T: ParentProtocol>(_ protocol: T) {
return nil
}
init?<T: Child_With_Value_Protocol>(_ protocol: T) {
_retrieveValue = protocol.retrieveValue as () -> Any
}
func retrieveValue() -> Any { return _retrieveValue() }
let _retrieveValue: (() -> Any)?
}
The prior comments are likely right. Nevertheless, you could box the variants in an enum and create an array of those. The reference would then switch on the enum value, each having associated data of the right type
EDIT: I didn't bother with the associatedValue, because it seems irrelevant to the question being asked. The following works in a playground:
protocol ParentProtocol: CustomStringConvertible {
static func retrieveValues(parents: [FamilyBox]) -> [ParentProtocol]
}
protocol ChildProtocol: ParentProtocol { }
protocol Other_Child_Protocol: ParentProtocol { }
enum FamilyBox {
case Parent(parent: ParentProtocol)
case Child(child: ChildProtocol)
case OtherChildProtocol(withValue: Other_Child_Protocol)
}
var parents: [FamilyBox] = []
struct P: ParentProtocol {
var description: String { return "Parent" }
static func retrieveValues(parents: [FamilyBox]) -> [ParentProtocol] {
var values = [ParentProtocol]()
for parent in parents {
switch parent {
case .Parent(let elementValue):
values.append(elementValue)
default:
break;
}
}
return values
}
}
struct C: ChildProtocol {
var description: String { return "Child" }
static func retrieveValues(parents: [FamilyBox]) -> [ParentProtocol] {
var values = [ParentProtocol]()
for parent in parents {
switch parent {
case .Child(let elementValue):
values.append(elementValue)
default:
break;
}
}
return values
}
}
struct CV: Other_Child_Protocol {
var description: String { return "Other Child" }
static func retrieveValues(parents: [FamilyBox]) -> [ParentProtocol] {
var values = [ParentProtocol]()
for parent in parents {
switch parent {
case .OtherChildProtocol(let elementValue):
values.append(elementValue)
default:
break;
}
}
return values
}
}
let p = FamilyBox.Parent(parent: P())
let c = FamilyBox.Child(child: C())
let cv = FamilyBox.OtherChildProtocol(withValue: CV())
let array:[FamilyBox] = [p, c, cv]
print(P.retrieveValues(array))
print(C.retrieveValues(array))
print(CV.retrieveValues(array))
The prints from the last three lines are:
[Parent]
[Child]
[Other Child]
While I'm sure it can be improved, I think that meets the original intent. No?

Resources