For loop with closure - arrays

Suppose you have an array, and you want to iterate over each element in the array and call a function obj.f which accepts that element as a parameter.
f is asynchronous, and completes nearly instantly, but it invokes a callback handler found in obj.
What would be the best way to match each element only after the previous finishes?
Here is one way:
let arr = ...
var arrayIndex = 0
var obj: SomeObj! // Required
obj = SomeObj(handler: {
...
arrayIndex += 1
if arrayIndex < arr.count {
obj.f(arr[arrayIndex])
}
})
obj.f(arr[0]) // Assumes array has at least 1 element
This works fine, but isn't ideal.
I could use a DispatchSemaphore, but that isn't great because it blocks the current thread.
Also, the reason why each operation must run only when the previous has finished is because the api I'm using requires it (or it breaks)
I was wondering if there was a better/more elegant way to accomplish this?

You say:
Suppose you have an array, and you want to iterate over each element in the array and call a function ... which accepts that element as a parameter.
The basic GCD pattern to know when a series of asynchronous tasks are done is the dispatch group:
let group = DispatchGroup()
for item in array {
group.enter()
someAsynchronousMethod { result in
// do something with `result`
group.leave()
}
}
group.notify(queue: .main) {
// what to do when everything is done
}
// note, don't use the results here, because the above all runs asynchronously;
// return your results in the above `notify` block (e.g. perhaps an escaping closure).
If you wanted to constrain this to, say, a max concurrency of 4, you could use the non-zero semaphore pattern (but make sure you don't do this from the main thread), e.g.
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 4)
DispatchQueue.global().async {
for item in array {
group.enter()
semaphore.wait()
someAsynchronousMethod { result in
// do something with `result`
semaphore.signal()
group.leave()
}
}
group.notify(queue: .main) {
// what to do when everything is done
}
}
An equivalent way to achieve the above is with a custom asynchronous Operation subclass (using the base AsynchronousOperation class defined here or here), e.g.
class BarOperation: AsynchronousOperation {
private var item: Bar
private var completion: ((Baz) -> Void)?
init(item: Bar, completion: #escaping (Baz) -> Void) {
self.item = item
self.completion = completion
}
override func main() {
asynchronousProcess(bar) { baz in
self.completion?(baz)
self.completion = nil
self.finish()
}
}
func asynchronousProcess(_ bar: Bar, completion: #escaping (Baz) -> Void) { ... }
}
Then you can do things like:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4
let completionOperation = BlockOperation {
// do something with all the results you gathered
}
for item in array {
let operation = BarOperation(item: item) { baz in
// do something with result
}
operation.addDependency(completionOperation)
queue.addOperation(operation)
}
OperationQueue.main.addOperation(completion)
And with both the non-zero semaphore approach and this operation queue approach, you can set the degree of concurrency to whatever you want (e.g. 1 = serial).
But there are other patterns, too. E.g. Combine offers ways to achieve this, too https://stackoverflow.com/a/66628970/1271826. Or with the new async/await introduced in iOS 15, macOS 12, you can take advantage of the new cooperative thread pools to constrain the degree of concurrency.
There are tons of different patterns.

you could try using swift async/await, something like in this example:
struct Xobj {
func f(_ str: String) async {
// something that takes time to complete
Thread.sleep(forTimeInterval: Double.random(in: 1..<3))
}
}
struct ContentView: View {
var obj: Xobj = Xobj()
let arr = ["one", "two", "three", "four", "five"]
var body: some View {
Text("testing")
.task {
await doSequence()
print("--> all done")
}
}
func doSequence() async {
for i in arr.indices { await obj.f(arr[i]); print("--> done \(i)") }
}
}

Related

How to call data from firebase synchronously

I have a function that calls data from my Firebase Database, but it is an async function, and messing up the other functions of my app. I have looked all around tutorials but they only show asyncronus functions, and my app depends on the Firebase Data to load first.
Code for the function:
#State var realLat: [String] = [ ]
#State var realLong: [String] = [ ]
func downloadFirebaseData() async throws -> Float {
let group = DispatchGroup()
group.enter() // << start
let db = Firestore.firestore()
try withUnsafeThrowingContinuation { continuation in
db.collection("annotations")
.getDocuments { (querySnapshot, error) in
defer {
group.leave() // << end on any return
}
// result heading code here
if let Lat = i.document.get("lat") as? String {
DispatchQueue.main.async {
realLat.append(Lat)
print("downloadLatServerData() \(realLat)")
}
}
}
}
group.wait() // << block till leave
}
The function DownloadFirebaseMapServerData() is an async function becuase of the line db.collection("annotations").addSnapshotListener {(snap, err) in... and I need realLat and realLong to be downloaded first inorder to assign them to a mapAnnotation, so is there any way that I could make this function syncronus or make another function with the same end goal? Also another thing to note is that realLat and realLong are both String Arrays, or Arrays that are Strings
Ok, let's say that - try to avoid making synchronous API that designed asynchronous - it was done by purpose, long time-consuming bla-bla-bla
but, if it is needed technically it is possible, for example using dispatch groups, like
func downloadFirebaseData() async throws -> Float {
let group = DispatchGroup()
group.enter() // << start
let db = Firestore.firestore()
try withUnsafeThrowingContinuation { continuation in
db.collection("annotations")
.getDocuments { (querySnapshot, error) in
defer {
group.leave() // << end on any return
}
// result heading code here
}
}
group.wait() // << block till leave
}

Swift - How to update object in multi-dimensional directory

I want to be able to find and update a custom object in an array of these objects. The challenge is that the custom objects also can be children of the object.
The custom object looks like this:
class CustomObject: NSObject {
var id: String?
var title: String?
var childObjects: [CustomObject]?
}
I would like to be able to create a function that overwrites the custom object with fx a specific ID, like this:
var allCustomObjects: [CustomObject]?
func updateCustomObject(withId id: String, newCustomObject: CustomObject) {
var updatedAllCustomObjects = allCustomObjects
// ...
// find and update the specific custom object with the id
// ...
allCustomObjects = updatedAllCustomObjects
}
I recognize this must be a pretty normal issue regarding multidimensional arrays / directories in both Swift and other languages. Please let me know what normal practice is used for this issue.
As with most things to do with trees, recursion is going to help. You can add an extra parameter that indicates which array of CustomObjects that you are currently going through, and returns a Bool indicating whether the ID is found, for short-circuiting purposes.
#discardableResult
func updateCustomObject(withId id: String, in objectsOrNil: inout [CustomObject]?, newCustomObject: CustomObject) -> Bool {
guard let objects = objectsOrNil else { return false }
if let index = objects.firstIndex(where: { $0.id == id }) {
// base case: if we can find the ID directly in the array passed in
objectsOrNil?[index] = newCustomObject
return true
} else {
// recursive case: we need to do the same thing for the children of
// each of the objects in the array
for obj in objects {
// if an update is successful, we can end the loop there!
if updateCustomObject(withId: id, in: &obj.childObjects, newCustomObject: newCustomObject) {
return true
}
}
return false
// technically I think you can also replace the loop with a call to "contains":
// return objects.contains(where: {
// updateCustomObject(withId: id, in: &$0.childObjects, newCustomObject: newCustomObject)
// })
// but I don't like doing that because updateCustomObject has side effects
}
}
You would call this like this, with the in: parameter being allCustomObjects.
updateCustomObject(withId: "...", in: &allCustomObjects, newCustomObject: ...)

Iterator in swift is skipping elements?

I am using iterators to cycle through an array of flash cards, but the iterator is going every other, for example, I tested it by entering in numbers 1-7 as new cards (in order) but when I flip through the deck it only displays 2, then 4, then 6, then back to 2. When I print out the deckIterator in the functions it gives a corresponding integer back, so for card 2 the iterator prints 2. I'm not sure If i'm using the iterator correct, could anyone point me in the right direction?
override func viewDidLoad() {
super.viewDidLoad()
fetchData()
}
func fetchData() {
deckArray.removeAll()
deckIterator = nil
do {
fetched = try context.fetch(Card.fetchRequest())
for each in fetched {
let term = each.term
let definition = each.definition
termLabel.text = each.term!
definitionLabel.text = each.definition!
let Card = card(term: term!, definition: definition!)
deckArray.append(Card)
}
} catch {
print(error)
}
deckIterator = deckArray.makeIterator()
}
#IBAction func leftSwipe(_ sender: UISwipeGestureRecognizer) {
getNextCardPlease()
self.definitionLabel.isHidden = true
alreadyFlipped = false
}
func getNextCardPlease(){
if(deckIterator?.next() == nil){
fetchData()
} else {
let next = deckIterator?.next()
termLabel.text = next?.term
definitionLabel.text = next?.definition
}
}
Every time you call next(), we iterate. So every call to getNextCardPlease iterates twice. Count them:
if(deckIterator?.next() == nil){ // 1
fetchData()
} else {
let next = deckIterator?.next() // 2
The problems was once if(deckIterator?.next() == nil) gets called, the iterator iterates. Iterators do not work like linked lists.

Swift - Shuffling a filtered array of structs doesn't change originating array

I'm writing a card game with a number of cards collected together in one array.
The data structure of the cards is the same, but the data is different.
Note: My shuffle does work.
I'm wanting to filter this array, and only shuffle the cards I have filtered.
However, whilst I can shuffle the cards, I've noticed that my originating array of cards does not change at all.
I believe that this issue is caused because my card model is a struct and not a class.
More background info.
Whilst the data for each card is different, the structure is exactly the same; both types have a name, and a numeric value.
This is modelled thusly;
enum FSCardType: Int {
case property
case anotherType
case yetAnotherType
}
// Card Model
struct FSCard {
let type: FSCardType
let name: String
let value: Int
var description: String {
return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
}
}
I create my cards in a static function like this:
class FSCardAPI: NSObject {
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
cards.append(contentsOf: properties)
return cards
}
public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
return shuffled
}
fileprivate static func createProperties() -> [FSCard] {
var properties:[FSCard] = [FSCard]()
for idx in 1...30 {
let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx, owner: nil)
properties.append(property)
}
return properties
}
}
Okay, so now I only want to shuffle my property cards within this cards array.
So in my XCTest file first filter all cards that are of type: property
func testShuffleProperties() {
var propertyCards = gameModel.cards.filter { (fs:FSCard) -> Bool in
return (fs.type == .property)
}
propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)
print(propertyCards)
print(" ---- ")
print(gameModel.cards)
}
This calls:
// Inside my FSCardAPI
public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
return shuffled
}
The issue:
When I run my XCTest, I notice that whilst the propertyCards is shuffled, my gameModel.cards are not shuffled;
Test Case 'testShuffleProperties' started.
// contents of `propertyCards`
[FSCard(type: FSCardType.property, name: "Property #4", value: 4, owner: nil),
FSCard(type: FSCardType.property, name: "Property #16", value: 16, owner: nil),
// .. etc
// contents of `gameModel.cards`
[FSCard(type: FSCardType.property, name: "Property #1", value: 1, owner: nil),
FSCard(type: FSCardType.property, name: "Property #2", value: 2, owner: nil),
// .. etc
Summary
I have an array of cards (ie: 30 cards)
Cards are separated by types (ie: property)
I want to filter the property cards and shuffle those cards only
I want the original array to reflect these changes
In my test, the array gets shuffled; but the original array remains the same.
One way I can think of is that I do all the shuffling, and then sort the array by card types and then update the gameModel.cards, but that seems a bit over the top.
Another obvious way I can think about solving this is to change my struct to a class or; perhaps I need another layer in between the struct?
So my query is:
How do I filter an array of structs, only shuffle those results and change the state of the originating array?
Many thanks
Edit:
The properties array is the only item to shuffle.
If I add another array to the list it should not shuffle the entire contents.
IE; if I do this:
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
let cheqs:[FSCard] = FSCardAPI.createCheques()
cards.append(contentsOf: properties)
cards.append(contentsOf: cheqs)
return cards
}
I should only shuffle the property cards within themselves without impacting the cheqs.
I guess I could make it easier and just have 2 arrays, but at the same time I think there is no reason to do this because the data structure is the same.
You are not assigning to gameModel.cards and not actually changing the array. so I would not expect gameModel.cards to be shuffled.
You should either just assign the shuffled array back to gameModel.cards like so:
func testShuffleProperties() {
var propertyCards = gameModel.cards.filter { (fs:FSCard) -> Bool in
return (fs.type == .property)
}
propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)
gameModel.cards = propertyCards
print(propertyCards)
print(" ---- ")
print(gameModel.cards)
}
Or if you want to mutate the array directly you should look at passing the cards by reference, or in Swift.. using an inout parameter. An inout parameter passes the value to the function by reference, or passed the memory addres of the value, so that it can be modified directly. Check the shuffle cards function below (how it is defined and used)
(I replaced the shuffle function with a swift extension for ease of use in a playground)
extension MutableCollection where Indices.Iterator.Element == Index {
/// Shuffles the contents of this collection.
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
guard d != 0 else { continue }
let i = index(firstUnshuffled, offsetBy: d)
swap(&self[firstUnshuffled], &self[i])
}
}
}
extension Sequence {
/// Returns an array with the contents of this sequence, shuffled.
func shuffled() -> [Iterator.Element] {
var result = Array(self)
result.shuffle()
return result
}
}
enum FSCardType: Int {
case property
case anotherType
case yetAnotherType
}
// Card Model
struct FSCard {
let type: FSCardType
let name: String
let value: Int
var description: String {
return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
}
}
class FSCardAPI: NSObject {
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
cards.append(contentsOf: properties)
return cards
}
public static func shuffleCards(cards:inout [FSCard]) {
cards = cards.shuffled()
}
fileprivate static func createProperties() -> [FSCard] {
var properties:[FSCard] = [FSCard]()
for idx in 1...30 {
let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx)
properties.append(property)
}
return properties
}
}
var cards = FSCardAPI.createCards()
FSCardAPI.shuffleCards(cards: &cards)
Output:
Read up on inout parameters here in the In-Out parameters section.
Exert from the documentation:
Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.
You write an in-out parameter by placing the inout keyword right before a parameter’s type. An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value. For a detailed discussion of the behavior of in-out parameters and associated compiler optimizations, see In-Out Parameters.
You can only pass a variable as the argument for an in-out parameter. You cannot pass a constant or a literal value as the argument, because constants and literals cannot be modified. You place an ampersand (&) directly before a variable’s name when you pass it as an argument to an in-out parameter, to indicate that it can be modified by the function.
EDIT: Try this updated shuffle function, pass in the whole array and see if does what you need.
public static func shuffleCards(cards:inout [FSCard]) {
let propertyCards = cards.filter({ $0.type == .property })
let indexes = propertyCards.map { Int(cards.index(of: $0)!) }
let shuffledCards = propertyCards.shuffled()
for (idx, val) in indexes.enumerated() {
cards[val] = shuffledCards[idx]
}
}
The problem is that after you shuffle your property cards, you are not doing anything with them. You should replace property cards in your original card list with the ones in your shuffled property card list.
var propertyCardsShuffledOriginalArray = originalArray.map {
var card = $0
if $0.type == .property {
card = shuffledPropertyCards.first as! FSCard
shuffledPropertyCards.removeFirst()
}
return card
}
propertyCardsShuffledOriginalArray is what you need

How to properly add data from Firebase into Array?

I'm trying to get results from Firebase and put them into Array, but it seems I miss something. What I want is to get 'Time' and 'Blood Glucose" values from Firebase and to put them into an arrays which I will use for Charts. I'm able to put the data into 'BG' and 'TIME' arrays, but when I 'append' them into 'FetchedDate' and 'FetchedBG' I see empty arrays (FetchedBG and FetchedDate)
var FetchedDate:[String]! = []
var FetchedBG: [Double]! = []
//GET DATA FROM FB
func GetDetails(){
let posts = rootRef.child("Diary/\(userID!)/\(passedDATE!)")
//let posts = rootRef.queryOrderedByChild(passedDATE!)
posts.observeEventType(FIRDataEventType.Value , withBlock: { (snapshot) in
for list in snapshot.children {
if let BG = list.value.objectForKey("Blood Glucose")!.doubleValue {
self.FetchedBG.append(BG)
print(BG) // SHOWS RESULTS AS EXPECTED
}
if let TIME = list.value.objectForKey("Time") {
self.FetchedDate.append(TIME as! String)
print(TIME) // SHOWS RESULTS AS EXPECTED
}
}
}) { (error) in
print(error.localizedDescription)
}
}
override func viewDidLoad() {
super.viewDidLoad()
GetDetails()
print(FetchedDate) // EMPTY ARRAY
print(FetchedBG) // EMPTY ARRAY
Firebase loads (and synchronizes) the data from your database asynchronously. Since that may take some time, your code continues executing and you print the arrays while they're still empty.
Once a value is available (either for the first time or when the data has changed), your block is invoked. It adds the data to the arrays. But by that time your print statements have long finished.
The solution is to move the code that needs to run when the value is available (or when it has changed) into the block. E.g.
var FetchedDate:[String]! = []
var FetchedBG: [Double]! = []
//GET DATA FROM FB
func StartSynchronizingDetails(){
let posts = rootRef.child("Diary/\(userID!)/\(passedDATE!)")
//let posts = rootRef.queryOrderedByChild(passedDATE!)
posts.observeEventType(FIRDataEventType.Value , withBlock: { (snapshot) in
for list in snapshot.children {
if let BG = list.value.objectForKey("Blood Glucose")!.doubleValue {
self.FetchedBG.append(BG)
print(BG) // SHOWS RESULTS AS EXPECTED
}
if let TIME = list.value.objectForKey("Time") {
self.FetchedDate.append(TIME as! String)
print(TIME) // SHOWS RESULTS AS EXPECTED
}
}
print(FetchedDate)
print(FetchedBG)
}) { (error) in
print(error.localizedDescription)
}
}
override func viewDidLoad() {
super.viewDidLoad()
StartSynchronizingDetails()
This is a very common pattern when your app interacts with (potentially time-consuming) network resources. It is also precisely the reason Firebase's observeEventType takes a withBlock: argument: to isolate the code that starts synchronizing from the code that responds to value updates.

Resources