Swift infinite loop while iterating array - ios

I have a protocol defined as...
#objc protocol MyDatasource : class {
var currentReportListObjects:[ReportListObject] { get };
}
and some code to iterate the returned array (as an NSArray from ObjC) in Swift as...
if let reportListObjects = datasource?.currentReportListObjects {
for reportListObject:ReportListObject? in reportListObjects {
if let report = reportListObject {
// Do something useful with 'report'
}
}
}
If my reportListObjects array is nil, I get stuck in an infinite loop at the for-loop. Equally, if there is data in the array, it is iterated and 'something useful' is done until the end of my array is reached but the loop is not broken and continues infinitely.
What am I doing wrong? Or is there something blindingly obvious I'm missing here?

You've added a lot of extra Optionals here that are confusing things. Here's what you mean:
for report in datasource?.currentReportListObjects ?? [] {
// Do something useful with 'report'
}
If datasource has a value, this will iterate over its currentReportListObjects. Otherwise it will iterate over an empty list.
If you did want to break it out, rather than using ??, then you just mean this:
if let reportListObjects = datasource?.currentReportListObjects {
for report in reportListObjects {
println(report)
}
}
There's no need for the intermediate reportListObject:ReportListObject? (and that's the source of your problem, since it accepts nil, which is the usual termination of a generator).

Related

RxSwift Observe variable fields of struct type in array

I have a problem with observing array of my custom type, that have a Variable fields. I want to create observable event, that will check, if value of this fields changed.
I have a struct:
struct Type {
let name: Variable<String>
let email: Variable<String>
}
And I have a array:
let array: Variable<[Type]>
All I need is Observable, that will return true, when field name count will be greater than 0. I try to play with Observable.combineLatest, but I cannot convert properly array.
First of all, you should not be using Variable anymore, as it's deprecated. You should use BehaviorRelay instead.
I'm not sure I understand your question clearly. But you probably need something like this:
// namesNotEmpty will be true if all elements in array have name.count > 0
let namesNotEmpty: Observable<Bool> = array.asObservable()
.flatMap { array -> Observable<[String]> in
if array.isEmpty {
// so that some event is emmited when array is empty
return Observable.just([""])
}
return Observable.combineLatest(array.map { $0.name.asObservable() })
}
.map { array in
array.filter { $0.isEmpty }.isEmpty
}
We know that Variable is deprecated and Variable is just a thin wrapper around a private instance of BehaviorSubject (nice comment about that). So, you can use BehaviorSubject instead.
Next thing, it's not good idea to put Rx type everywhere you want. The better way is to keep array and your Rx property separately.
For example:
struct CustomType {
let name: String
let email: String
}
private var array: [CustomType] = [] {
didSet {
if array.first(where: { $0.name.isEmpty }) == nil {
subject.onNext(())
}
}
}
var subject = PublishSubject<Void>()
...
// and handle changes
subject.subscribe(onNext: {
// logic
})
Here we have array of pure CustomType objects that trigger subject to send event when if condition is correct. This is much better and cleaner, because your logic is separated.
P.S. Also, if you not good at *Subject, i suggest you to google about it, because maybe your code need another sort of *Subject.

How to check if 2 arrays have the same element and if it does, delete that element from one of the arrays?

I currently have 2 arrays, one called rewardsArray and one called expiredRewardsArray. The rewardsArray I'm getting from an api thats fetched every time I connect. The expiredRewardsArray I save locally. What I'm trying to do is in the viewDidLoad, after I retrieve the rewardsArray data I want to compare it to the expiredRewardsArray's data. If there is a match I want to remove the item from the rewardsArray. This is what I have so far but it never goes inside the "if let" brackets so it's not removing the item from the rewardsArray:
func rewardsMinusExpired () {
expiredRewardsArray = rewardManager.getExpiredRewards()
for expiredReward in expiredRewardsArray {
if let ex = rewardsArray.indexOf(expiredReward){
print("Expired Reward to be removed: \(ex)")
rewardsArray.removeAtIndex(ex)
rewardsTableView.reloadData()
}
}
}
Each item in the array has an id, I use that to see if the item is in the expiredRewardsArray:
for expiredReward in expiredRewardsArray {
print("This is an expired reward: \(expiredReward.id)")
}
Here's a solution that uses Set arithmetic. As a consequence, it's really fast, but it will not preserve the ordering of elements, and it will clobber any duplicates. It runs in linear time (O(n)), whereas a naive approach would be O(n^2).
let unexpiredRewards = Set(rewardsArray).subtract(Set(ExpiredRewards))
for item in expiredRewardsArray {
if let index = rewardsArray.index(of: item) {
//found the item
rewardsArray.remove(at: index)
}
}
This will find the item and delete it from the rewardsArray
UPDATE:
You say that each item has an id. If with the code above the if let block is never called than you can be sure that the items aren't actually the same or you don't have any equal items.
for itemOne in expiredRewardsArray {
for itemTwo in rewardsArray {
if itemOne.id == itemTwo.id {
rewardsArray.remove(at: index)
}
}
}
Not very performant but it does its job and keeps the order
You should really use swift method filter in cases like this. This can be easily solved as this,
func rewardMinusExpired() {
let notExpiredRewards = rewardsArray.filter { a in
return !expiredRewardArray.contains(a)
}
}
If expiredRewardsArray and rewardsArray are arrays of objects, then you'll need to compare the elements using their id property to compare them, rather than indexOf(_:). The reason for this is that objects are reference types, so you can have two different objects with identical properties, but if they're not the same object, indexOf(_:) will treat them as separate entities.
Try this code instead:
func rewardsMinusExpired() {
expiredRewardsArray = rewardManager.getExpiredRewards()
rewardsArray = rewardsArray.filter { reward in
let isExpired = expiredRewardsArray.contains { expiredReward in
return expiredReward.id == reward.id
}
return !isExpired
}
}

How to avoid If let in Swift before doing a for loop

I have this code on Swift:
let items = doSomethingFuncToGetDataWithOptionalResults()
if let items = items {
for item in items {
// so something...
}
}
Could anyone can help me to avoid if let ... in this case. It would be better if we could ignore if let in this case. I feel annoyed when writing this statements every time.
Regards,
Generally, if a function returns an optional then you can use
optional chaining to operate on the result only if it is not nil.
In your case of an optional array you can use
optional chaining and forEach():
doSomethingFuncToGetDataWithOptionalResults()?.forEach { item in
// do something with `item` ...
}
The forEach() statement will not be executed if the function
returns nil.
You can do something like this:
if let items = doSomethingFuncToGetDataWithOptionalResults() {
for item in items {
// so something...
}
}

Create a typed array w/ N items in it using Swift

I have a method that I'm using to create an array w/ n of a specific type of object in it:
func many(count: Int) -> [Cube] {
var cubes: [Cube] = []
for i in 0...count {
let cube = CubeFactory.single()
cubes.append(cube)
}
return cubes
}
This works but I'm sure there's a more swift like way to do this. Any one know how this can be refactored?
Does CubeFactory.single() actually do anything other than return the same instance every time?
If it does active stuff, return map(0..<count) { _ in CubeFactory.single() } (or numerous variants) will give you an array of them.
(the _ in really oughtn’t to be necessary, but Swift’s type inference gets a bit flummoxed without it and you’ll get an error about half-open intervals – the other possibility for the overloaded ..< operator – not conforming to SequenceType)
If it’s just inert and every cube is identical, return Array(count: count, repeatedValue: Cube.single()) is all you need.
In Swift 2, this is
let array = (0..<30).map { _ in CubeFactory.single() }
Not sure if it's exactly what you're looking for, but you can use:
let array = map(0..<30) { _ in CubeFactory.single() }
Swift 4.2
struct Cube {
let id:Int
}
...
let array = (0..<24).compactMap {
return Cube(id: $0)
}

Type cast array elements in for loop

In a delegate method, I get back a ‘results’ array of a custom object type, and I want to loop through the array elements.
I do the following now, and this works
for result in results {
if result is XYZClass {
//This Works!
}
}
Is there a way to type cast the objects in the for-loop to avoid writing two lines? Does swift permit this? Used to get this done fairly easily in Objective - C
for (XYZClass *result in results) {
}
However, I have not been successful in Swift. I’ve tried explicit-cast with no luck.
for result as XYZClass in results {
//ERROR: Expected ‘;’ in ‘for’ statements
}
for result:AGSGPParameterValue in results {
/* ERROR: This prompts down cast as
for result:AGSGPParameterValue in results as AGSGPParameterValue { }
which in turn errors again “Type XYZClass does not conform to Sequence Type”
*/
}
Any help is appreciated
Try this:
for result in results as [XYZClass] {
// Do stuff to result
}
Depending on how you are using the for loop it may be instead preferable to use compactMap (or flatMap if you are before Swift 4.1) to map your objects into a new array:
let onlyXyzResults: [XYZClass] = results.compactMap { $0 as? XYZClass }
Now you have an array XYZClass objects only, with all other object types removed.

Resources