Making an Asynchronous Web Request in an Init Function [Swift] - arrays

I'm trying to return some JSON data from a class "RestApiManger" that handles HTTP/JSON requests and pass it into an array in my class "CardList", but I'm running into trouble.
Here is my CardList Class:
class CardList {
var cards: [Card] = []
var c = NSMutableArray()
var items = NSMutableArray()
static var sharedInstance = CardList()
//MARK - Initalize
private init(){
//Dummy Data
let helm = Card(name: "Helm of Testing", cost: 10, type: "Equipment", subType: "Head Armor", description: "Some say the Helmet of Testing helps keep it's wearers mind clear.")
//Add to Array
let c = [helm]
//Get API Array
RestApiManager.sharedInstance.getElements { (json: JSON) in
let results = json["results"]
for (_, subJson) in results {
let card:AnyObject = subJson["card"].object
self.items.addObject(card)
}
print(self.items.count) // <---returns 1 (Web Data)
}
print(self.items.count)// <---returns 0 (Nothing)
//Set to Returnable Array
self.cards = c.sort { $0.cost < $1.cost }
}
//END Class
}
Seems like it's not passing to the items array on initialize until after it's called. It's printing the array count inside the RestApiBlock, outside the block, it returns 0 (See the comments in the code with the arrows). I'm a little unfamiliar with Swift and unsure why this happens.
Is there any way I can make this work?
Thank you in advance.

This call to your API manager:
RestApiManager.sharedInstance.getElements
which takes a closure returns immediately, which means that execution of your code moves to this line:
print(self.items.count)
before the closure for completion of the request is called. This means that your items array is empty when you print its count that's why you're getting 0 there.
What you need to do is to do whatever you want with the items array inside that closure after the for loop.

Related

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: ...)

For loop with closure

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)") }
}
}

Swift - Update Value of a specific item in a nested dictionary

I have a tableview and each cells are meant to be linked to an array inside a dictionary.
var buttonAction : [String: [ButtonAction]] = [:]
below is the struct of the buttonAction
struct ButtonAction: Codable {
var action: String
var array_linked_of_buttons: [[String:String]]
init(action: String, array_linked_of_buttons: [[String:String]]) {
self.action = action
self.array_linked_of_buttons = array_linked_of_buttons
}
}
It gets a bit complicated to explain the whole code but when I connect two buttons together, I can get the data for the variable "singleAction" which then can be added to the button action dictionary "array_linked_of_buttons".
let singleAction = [linkedButtonUUID: connectorMarker.UUIDpic.uuidString, linkedButtonCategory: "", linkedButtonName: connectorMarker.name]
let mainMarkerAction = mainMarker.buttonAction["button actions array"]!
for existingMarker in mainMarkerAction {
actionArray.append(existingMarker)
}
var actionSub = actionArray[indexRowTag].array_linked_of_buttons
if let addAction = actionSub.filter({$0[linkedButtonUUID] == connectorMarker.UUIDpic.uuidString}).first {
print("MARKER Exists", addAction)
} else {
actionSub.append(singleAction)
print("UPDATED MARKER", actionSub)
}
let action = ButtonAction(action: actionArray[indexRowTag].action, array_linked_of_buttons: actionSub)
//ISSUE--?? mainMarker.buttonAction.updateValue(VALUE forKey: "button actions array")
saveData()
I can workout which item of the dictionary needs to be edited but how do I update that specific value? I am really confused has it just creates a new item but I want to update a previous one.
So for example, I want to append the "array_linked_buttons" of the item 0 and have 4 items instead of 3.
This is how my dictionary looks like if that helps too
I have searched other questioned but I still work it out.
Any help pointing me in the right direction is much appreciated!
Copying instead of changing value happened because you're using struct for ButtonAction which has value semantics and swift create copy on assigning to any variable.
You need to use class instead. Classes have reference semantics, so it won't create new instance on assigning to variable so you'll be able to update property of needed instance.
I worked out i was updating the main dictionary array and not the nested array.
The code below is what I used in the end.
let singleAction = [linkedButtonUUID: connectorMarker.UUIDpic.uuidString, linkedButtonCategory: "", linkedButtonName: connectorMarker.name]
var mainMarkerAction = mainMarker.buttonAction["button actions array"]!
for existingMarker in mainMarkerAction {
actionArray.append(existingMarker)
}
var actionSub = actionArray[indexRowTag].array_linked_of_buttons
if let addAction = actionSub.filter({$0[linkedButtonUUID] == connectorMarker.UUIDpic.uuidString}).first {
print("MARKER HERE", addAction)
} else {
actionArray[indexRowTag].array_linked_of_buttons.append(singleAction)
}
mainMarkerAction[indexRowTag] = actionArray[indexRowTag]

Swift Realm [[String]] object

I'm new to Realm and have been through the documentation a few times. I need to persist a [[String]] and have not found a way to do it yet
var tableViewArray = [[String]]()
I see the documentation pointing to Lists but I've been unsuccessful at implementing them. I'm showing my whole process here but just need help persisting my var tableViewArray = [[String]]()in Realm
This is my class
class TableViewArrays {
var tableViewArray = [[String]]() // populates the Main Tableview
/// add picker selection to tableview array
func appendTableViewArray(title: String, detail: String, icon: String ) {
var newRow = [String]()
newRow.append(title)
newRow.append(detail)
newRow.append(icon)
tableViewArray.append(newRow)
}
In the View Controller I instantiate the object
var tableViewArrays = TableViewArrays()
Then call the class function to populate the object
var tableViewArrays.appendTableViewArray(title: String, detail: String, icon: String )
Thank you for taking a look
I would make two Realm objects to be persisted, then nest them. Here's an example:
class RealmString: Object {
dynamic var value = ""
}
class RealmStringArray: Object {
let strings = List<RealmString>()
}
class TableViewArray{
let stringArrays = List<RealmStringArray>()
}
I can't say much about the efficiency of this method, but I suppose it should work for your purpose. Also, if you have a large amount of data, it may become a pain to persist each individual string, then string collection, the string collection collection.
create the classes
class TableViewRow: Object {
dynamic var icon = ""
dynamic var title = ""
dynamic var detail = ""
override var description: String {
return "TableViewRow {\(icon), \(title), \(detail)}" }
}
class EventTableView: Object {
let rows = List<TableViewRow>()
}
then instantiate the objects and append
let defaultTableview = EventTableView()
let rowOne = TableViewRow()
rowOne.icon = "man icon" ; rowOne.title = "War Hans D.O.P." ; rowOne.detail = "Camera Order Nike 2/11/17"
defaultTableview.rows.append(objectsIn: [rowOne])

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