I need to make a long async calculation based on a String input and return a big Data instance.
I use Single trait to achieve this:
func calculateData(from: String) -> Single<Data>
This example is simple and works. But I also need to track progress — a number between 0 and 1. I'm doing something like this:
func calculateData(from: String) -> Observable<(Float, Data?)>
where I get the following sequence:
next: (0, nil)
next: (0.25, nil)
next: (0.5, nil)
next: (0.75, nil)
next: (1, result data)
complete
I check for progress and data to understand if I have a result, it works, but I feel some strong smell here. I want to separate streams: Observable with progress and Single with a result. I know I can return a tuple or structure with two observables, but I don't like this as well.
How can I achieve this? Is it possible?
What you have is fine although I would name the elements in the tuple
func calculateData(from: String) -> Observable<(percent: Float, data: Data?)>
let result = calculateData(from: myString)
.share()
result
.map { $0.percent }
.subscribe(onNext: { print("percent complete:", $0) }
.disposed(by: disposeBag)
result
.compactMap { $0.data }
.subscribe(onNext: { print("completed data:", $0) }
.disposed(by: disposeBag)
Another option is to use an enum that either returns percent complete OR the data:
enum Progress {
case incomplete(Float)
case complete(Data)
}
func calculateData(from: String) -> Observable<Progress>
However, doing that would make it harder to break the Observable up into two streams. To do so, you would have to extend Progress like so:
extension Progress {
var percent: Float {
switch self {
case .incomplete(let percent):
return percent
case .complete:
return 1
}
}
var data: Data? {
switch self {
case .incomplete:
return nil
case .complete(let data):
return data
}
}
}
And as you see, doing the above essentially turns the enum into the tuple you are already using. The nice thing about this though is that you get a compile time guarantee that if Data emits, the progress will be 1.
If you want the best of both worlds, then use a struct:
struct Progress {
let percent: Float
let data: Data?
init(percent: Float) {
guard 0 <= percent && percent < 1 else { fatalError() }
self.percent = percent
self.data = nil
}
init(data: Data) {
self.percent = 1
self.data = data
}
}
func calculateData(from: String) -> Observable<Progress>
The above provides the compile time guarantee of the enum and the ease of splitting that you get with the tuple. It also provides a run-time guarantee that progress will be 0...1 and if it's 1, then data will exist.
Related
I have created enum with associated value and I want to be able to dynamically update associated value. As far as I know Swift doesn't support that at the moment.
Because of that I used following approach:
enum PersonInfo {
class EnumValue<T> {
var value: T
init(_ value: T) {
self.value = value
}
}
// Instead of using String or Bool or any other type directly, use EnumValue wrapper
case firstName(EnumValue<String>)
case lastName(EnumValue<String>)
case isAdult(EnumValue<Bool>)
}
I want to add function that would update EnumValue.value property in following way:
func updateAssociatedValue<V>(_ updateValue: V) {
let mirror = Mirror(reflecting: self)
for associatedValue in mirror.children {
guard let value = associatedValue.value as? EnumValue<V> else {
continue
}
value.value = updateValue
}
}
Problem is that this guard statement always fails (guard let value = associatedValue.value as? EnumValue<V>) and I can't figure it out why.
On the other hand, when I write updateAssociatedValue with typed type then things work properly:
// This works
func updateAssociatedValue(_ updateValue: String) {
let mirror = Mirror(reflecting: self)
for associatedValue in mirror.children {
guard let value = associatedValue.value as? EnumValue<String> else {
continue
}
value.value = updateValue
}
}
Things compile normally but during the runtime guard statement always fails. Am I using generic value in some incorrect way? Should I use somehow updateValue.Type or updateValue.self (I tried but it didn't work).
Example of usage:
var array: [PersonInfo] = [
.firstName(PersonInfo.EnumValue("John")),
.lastName(PersonInfo.EnumValue("Doe")),
.isAdult(PersonInfo.EnumValue(false))
]
print(array)
// John, Doe, false
array.first?.updateAssociatedValue("Mike")
print(array)
// Mike, Doe, false
I can always reassign enum value in array but if possible I want to avoid that. That's the reason for asking this question.
I am trying to implement something like this,
let api1 = Observable.of(["documents"]) //Replace with observable to download docs
let api2 = Observable.of(["applications"]) //Replace with observable to download apps
let api3 = Observable.of(["videos"]) //Replace with observable to download videos
Observable.combineLatest(api1, api2, api3){(docs, apps, videos) in
return (docs, apps, videos)
}.skipWhile{ (docs, apps, videos) in
return docs.count == 0 && apps.count == 0 && videos.count == 0
}.subscribe(onNext:{(docs, apps, videos) in
})
.disposed(by:disposeBag)
In my case, I am trying to create observables dynamically and add it to an array like this,
private var discoverObservables = [Observable<Any>]()
func loadDiscoverFeeds(){
self.feeds.forEach({
feed in
switch feed.feedType{
case "a":
let observable = self.aObservable(url: feed.feedURL ?? "")
self.discoverObservables.append(observable)
break
case "b":
let observable = self.bObservable(url: feed.feedURL ?? "")
self.discoverObservables.append(observable)
break
case "c":
let observable = self.cObservable(url: feed.feedURL ?? "")
self.discoverObservables.append(observable)
break
case "d" :
let observable = self.dObservable(url: feed.feedURL ?? "")
self.discoverObservables.append(observable)
break
default:
break
}
})
}
private func aObservable(url : String) -> Observable<A?>{
return APIManager.shared.getA(url: url)
}
private func bObservable(url : String) -> Observable<B?>{
return APIManager.shared.getB(url: url)
}
private func cObservable(url : String) -> Observable<C?>{
return APIManager.shared.getC(url: url)
}
But this is not working because discoverObservables array is expecting the value of Type Observable<Any> and I am trying to add Observable<A?>
How can I do this correctly, I want to make sure all the observables return data before I start processing the data.
Edit
I am trying to load data from different sources before that is added to the view, basically, I have a collectionview, each section loads data from different API, I am trying to get all the required data from all sources before that is added to collection view.
Add the same protocol to A, B and C.
protocol YourProtocol {...}
class A: YourProtocol {...}
class B: YourProtocol {...}
class C: YourProtocol {...}
Then you can make :
private var discoverObservables = [Observable<YourProtocol>]()
The first code block seems to be doing the job with one exception, the condition checks if all of the (docs, apps, videos) are empty, perhaps you wanted to use || instead of &&.
As for the second code block with an array, I did something that could help.
struct A {}
let observable1 = Observable.just(A())
let observable2 = Observable.just(A())
let observable3 = Observable.just(A())
let observables: [Observable<A>] = [observable1, observable2, observable3]
Observable.combineLatest(observables).skipWhile { (streams) -> Bool in
streams.forEach {
if $0.count == 0 { return true }
}
return false
}.subscribe(...
This subscription will result with Observable<[A]>.
I'm going to specifically address this from your question: "I want to make sure all the observables return data before I start processing the data."
Strictly speaking, you probably don't want an Any structure. Better would be a protocol or enum. I see that other answers have addressed the protocol idea so I will use the enum idea:
enum EndpointResponse {
case a(A?)
case b(B?)
// etc...
}
let responses = Observable.zip(
feeds.map { (feed) -> Observable<EndpointResponse> in
switch feed.feedType {
case "a":
return aObservable(url: feed.feedURL ?? "").map { EndpointResponse.a($0) }
case "b":
return bObservable(url: feed.feedURL ?? "").map { EndpointResponse.b($0) }
default:
fatalError()
}
}
)
The above responses observable will contain an array of all the responses once they have all emitted values. In other words, the zip operator will gather up all the responses from all the network calls and emit a single array containing all of them.
My Previous answer:
There really isn't a lot of information to go on in the question, but something like this answers the direct question you ask about converting an Observable<X> to an Observable<Any>...
let discoverObservables = Observable.zip(
feeds.map { (feed) -> Observable<Any> in
switch feed.feedType {
case "a":
return aObservable(url: feed.feedURL ?? "").map { $0 as Any }
case "b":
return bObservable(url: feed.feedURL ?? "").map { $0 as Any }
case "c":
return cObservable(url: feed.feedURL ?? "").map { $0 as Any }
case "d":
return dObservable(url: feed.feedURL ?? "").map { $0 as Any }
default:
break
}
}
)
This is what I've tried and can't figure out where the error is coming from. Is there something missing? Syntax error? I tried doing similar with if-else in the function and also getting errors.
var steps = 0
func incrementSteps() -> Int {
steps += 1
print(steps)
return steps
}
incrementSteps()
let goal = 10000
func progressUpdate() -> Int {
let updated = progressUpdate()/goal
switch updated {
case (0.0..<0.1):
print("Great start")
case (0.11..<0.5):
print("Almost halfway")
case (0.51..<0.9):
print("Almost complete")
default:
print("Beat goal")
}
}
progressUpdate()
You need to specify updated as Double. And cast it back to Int when returning(if you require Int for your requirement).
Note: Also, you need to modify calling the progressUpdate function within progressUpdate definition which causes a recursion. If you want to do so you might want to give condition to break the loop.
func progressUpdate() -> Int {
let updated = Double(steps/goal)
switch updated {
case (0.0..<0.1):
print("Great start")
case (0.11..<0.5):
print("Almost halfway")
case (0.51..<0.9):
print("Almost complete")
default:
print("Beat goal")
}
return Int(updated)
}
Let's say that you have the code
if stringValue == "ab" || stringValue == "bc" || stringValue == "cd" {
// do something
}
Is there a way to shorten this condition or beautify it (preferably without using the switch statement)? I know that this code does NOT work:
if stringValue == ("ab" || "bc" || "cd") {
// do something
}
I've seen some complex solutions on other languages, but they seem language specific and not applicable to Swift. Any solutions would be appreciated.
let valuesArray = ["ab","bc","cd"]
valuesArray.contains(str) // -> Bool
You can create an extension like this:
extension Equatable {
func oneOf(other: Self...) -> Bool {
return other.contains(self)
}
}
and use it like this:
if stringValue.oneOf("ab", "bc", "cd") { ... }
Credit for the impl which saved me typing it: https://gist.github.com/daehn/73b6a08b062c81d8c74467c131f78b55/
Not that i am aware, you can do something like this though,
let validStrings = Set<String>(arrayLiteral:"ab", "bc", "cd")
if validStrings.contains(str) {
//do something
}
Use a Switch Statement.
switch stringValue {
case "ab", "bc", "cd":
print("Yay!")
default:
break
}
The construction ["some", "array"].contains("value") works, but is somewhat annoying:
It inverts the left-to-right order you may want to write.
Items in the array are not declared using Swift's type inference, often forcing you to include unnecessary information to please the compiler.
You can instead use Set(["value"]).isSubset(of: ["some", "array"]).
The benefit is especially apparent when working with enums:
enum SomeReallyReallyLongTypeName {
case one, two
}
struct Thing {
let value: SomeReallyReallyLongTypeName
}
let thing = Thing(value: .one)
if Set([thing.value]).isSubset(of: [.one, .two]){
// :)
// Left-to-right order
// You get nice type inference
}
if [SomeReallyReallyLongTypeName.one, .two].contains(thing.value) {
// :(
// Annoying to have "SomeReallyReallyLongTypeName" in the code
}
if someArray.contains(object) {
// contains
} else {
// does not contains
}
The above function returns bool value, then you write logic accordingly.
Just for fun, how about overloading functions over String:
if a.isOneOf("ab", "bc", "cd") {
print("yes")
}
extension String {
#inlinable
func isOneOf(_ first: String, _ second: String) -> Bool {
self == first || self == second
}
#inlinable
func isOneOf(_ first: String, _ second: String, _ third: String) -> Bool {
self == first || isOneOf(second, third)
}
#inlinable
func isOneOf(_ first: String, _ second: String, _ third: String, _ fourth: String) -> Bool {
self == first || isOneOf(second, third, fourth)
}
}
This gives you full performance benefits, as the compiler will be able to inline and tail call as much as it wants, at the cost of having to write as many overloads as you need in your code, and also not being able to pass arrays - but other answers deal with this too.
let a = 1
let b = 1
let c = 1
let d = 1
if a == b,a==c,a==d {
print("all of them are equal")
}
else {
print("not equal")
}
I am using Swift 1.2 in Xcode 6.3.1
Following is my Person struct
struct Person {
let age: Int
init?(age: Int) { //Failable init
if age > 100 { return nil }
self.age = age
}
}
I am having a list of ages against which I have to make Person Objects.
I have made playground file.
let arr = Array(1...150) //Sample set of ages against which Person is created
var personList: [Person]!
and
personList = arr.map({ (val: Int) -> Person? in
return Person(age: val) //Makes object of type Person?
}).filter {
$0 != nil
}.map {
return $0!
}
Here I have uses map - filter - map because the first map invokes failable intializer, (hence it returns Person?) and personList is of type [Person].
Hence second function filters all the non nil objects and third map forcefully opens to optional therby making Person? to Person.
Is there a more easy/readable way out ? Chaining map-filter-map definitely seems to be an overkill for this
You can use flatMap to get rid of any nils in the array, this tutorial discusses the method in length, but the following will work best:
let personList = arr.flatMap { Person(age: $0) }
Note: This answer was given for Swift 1.2, the current
version at the time the question was posted. Since Swift 2 there is a better solution, see #Jeremie's answer.
I don't know of a built-in function that combines filter()
and map(). You can write the code slightly more compact using
the shorthand argument $0 in all closures:
let personList = arr.map { Person(age: $0) }
.filter { $0 != nil }
.map { $0! }
Of course you can define your own extension method which maps the
array elements and keeps only the non-nil results:
extension Array {
func optmap<U>(transform: T -> U?) -> [U] {
var result : [U] = []
for elem in self {
if let mapped = transform(elem) {
result.append(mapped)
}
}
return result
}
}
and then use it as
let personList = arr.optmap { Person(age: $0) }
You can use compactMap which is better that flatMap in this case to remove any nils in the array:
let personList = arr.compactMap { Person(age: $0) }
The Swift document declared:
Returns an array containing the non-nil results of calling the given
transformation with each element of this sequence.