How can I block the function until I get the result in swift? - swift

class ProductManaged {
static let shared: ProductManaged = .init()
private init() { }
private let semaphore = DispatchSemaphore(value: 0)
var a:Int = 10
func getUpdated(_ aa: Int) -> Int {
print("thread \(aa) started. Value: \(self.a)")
// network call
DispatchQueue.global(qos: .background).async {
sleep((2...5).randomElement()!)
self.a += 100
print("thread \(aa) ended: Value: \(self.a)")
self.semaphore.signal()
}
semaphore.wait()
return a
}
}
class ScreenA {
func test() {
let a1 = ProductManaged.shared.getUpdated(1)
print(a1)
let a2 = ProductManaged.shared.getUpdated(2)
print(a2)
}
}
class ScreenB {
func test() {
let b = ProductManaged.shared.getUpdated(3)
print(b)
}
}
DispatchQueue.global().async {
let screenA = ScreenA()
screenA.test()
}
DispatchQueue.global().async {
let screenB = ScreenB()
screenB.test()
}
// ------ current result ----
thread 3 started. Value: 10
thread 1 started. Value: 10
thread 3 ended: Value: 110
110
thread 1 ended: Value: 210
210
thread 2 started. Value: 210
thread 2 ended: Value: 310
310
// ---- i have to reach like this
thread 3 started. Value: 10
thread 3 ended: Value: 110
110
thread 1 started. Value: 10
thread 1 ended: Value: 210
210
thread 2 started. Value: 210
thread 2 ended: Value: 310
310

Related

Swift PropertyWrapper for waiting on a value to set for the first time not working

I intend to wait for the value is set for the first time and then the get method should return. But I am getting wrong result on the first time. Here is the code
import Foundation
import OSLog
#propertyWrapper
struct WaitTillSet<T> {
private var value: T
private let group: DispatchGroup
init(wrappedValue value: T) {
self.group = DispatchGroup()
self.value = value
group.enter()
}
var wrappedValue: T {
get { getValue() }
set { setValue(newValue: newValue) }
}
mutating func setValue(newValue: T) {
value = newValue
group.leave()
}
func getValue() -> T {
group.wait()
return value
}
}
let logger = Logger()
func testFunc() {
#WaitTillSet var data = 0
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + 2) {
logger.info("Setting value to 10")
data = 10
}
logger.info("data = \(data)")
logger.info("dataAgain = \(data)")
}
testFunc()
And here is the output
2022-08-23 10:57:39.867967-0700 Test[68117:4747009] Setting value to 10
2022-08-23 10:57:39.868923-0700 Test[68117:4746644] data = 0
2022-08-23 10:57:39.869045-0700 Test[68117:4746644] dataAgain = 10
Program ended with exit code: 0
I also tried DispatchSemaphore. That gives the same result. Instead of propertyWrapper if I use class in the similar way I see the same problem. There is something fundamentally wrong with this.

Escaping closure captures mutating 'self' parameter, Swift

I have been working on a count down timer and it was going ok, but when i decided to restructure the code in MVC and moved part of the code i got this error
(Escaping closure captures mutating 'self' parameter).
i tried changing the struct to a class but then the timer stoped working and wouldn't update the label. any solution for either issue would help.
struct CountDownLogic {
var hours: Int = 0
var mins: Int = 0
var secs: Int = 0
.
.
.
mutating func startTimer() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { Timer in
if self.secs > 0 {
self.secs = self.secs - 1
} else if self.mins > 0 {
self.mins = self.mins - 1
self.secs = 59
} else if self.hours > 0 {
self.hours = self.hours - 1
self.mins = 59
self.secs = 59
}
self.updateLabel()
}
}
mutating func updateLabel() {
dateSet = "\(hours):\(mins):\(secs)"
}
}
any help is much appreciated ..

How to exit a for loop with data from an async function in swift?

How can the function findMediaLimit return the highest i, received from cURL ?
class func findMediaLimit(IgBusinessAccount: String, token: String) {
let igBId = IgBusinessAccount
for i in 1...12 {
guard let encodedUrl = self.buildURLAPIGraph(IgBusinessAccount: igBId, token: token, i: i) else { return }
//Async function that returns different i's in random order
self.cURL(urlT: encodedUrl) { (result) in
print(i)
//return the highest i
}
}
}
I have created this function in order to filter media that have been posted after conversion to a business Instagram account.
that's my cURL function
class func cURL (urlT: String,Completion block: #escaping ((OfficialProfile) -> ())) {
//visuJson (urlT: urlT)
GetJson.loadJson(fromURLString: urlT) { (result) in
switch result {
case .success(let data):
//Parse
do {
let decodedData = try JSONDecoder().decode(OfficialProfile.self, from: data)
if decodedData.username != nil {
block(decodedData)
}
} catch {
print("decode error: ",error)
}
case .failure(let error):
print("loadJson error:", error)
}
}
}
and that is my loadJson func
class func loadJson(fromURLString urlString: String,
completion: #escaping (Result<Data, Error>) -> Void) {
if let url = URL(string: urlString) {
let urlSession = URLSession(configuration: .default).dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
}
if let data = data {
completion(.success(data))
}
}
urlSession.resume()
}
}
To see what the issue is with what you're apparently trying to do, let's simulate it in a simpler way. I'll use an asynchronous random number generator. Try this in a playground:
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
func asyncRand(_ completion: #escaping (Int) -> ()) {
let d = Double.random(in: 1...3)
delay(d) {
let i = Int.random(in: 1...100)
completion(i)
}
}
func go() {
for i in 1...12 {
asyncRand() { result in
print(i, result)
}
}
print("finished")
}
go()
The result in a typical run might be:
finished
8 13
1 15
9 9
10 56
7 57
3 87
2 70
11 88
6 82
12 16
4 81
5 46
So there are two issues here, because of the asynchronous nature of the central asyncRand call: the results come back in no order, over time, and the go method itself (the loop) finishes before any results come back, so there is no opportunity to find out which result is the maximum.
To straighten that out, while we are waiting for async/await to appear in Swift (possibly as soon as next week), you can use a dispatch group. Ignoring any threading issues, we might do this:
func go() {
let group = DispatchGroup()
var pairs = [[Int]]()
for i in 1...12 {
group.enter()
asyncRand() { result in
print(i, result)
pairs.append([i,result])
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print("finished")
if let maximum = pairs.max(by: {$0[1] < $1[1]}) {
print(maximum)
}
}
}
Now the result is (for example):
11 52
8 6
2 1
4 6
12 77
1 88
7 45
9 36
6 25
3 22
10 78
5 33
finished
[1, 88]
So you see we have "waited" until all the results have come back, and we have picked out the pair where the largest value was returned.
So you could do something along those lines. But whether that's a good idea is another question.

Swift Code Running Twice When It Should Not

I have an escaping function, which completes once a condition is met:
private func xxxfastLoadLSecurityDescriptions(session: URLSession, mySymbols: [String]?, completion: #escaping(Bool) ->()) {
var counter = mySymbols?.count ?? 0
if counter == 0 { completion(false) }
var doubleCount = 0
// print("DESCRIPTION Starting Counter = \(counter)")
for symbolIndex in 0..<(mySymbols?.count ?? 0) {
guard let mySymbol = mySymbols?[symbolIndex] else { print("ERROR in fastLoadLSecurityDescriptions loop: No Symbol") ; continue }
guard let myGetDescriptionRequest = GenericDataRequest(dataToSend: [mySymbol], token: sessionToken, mgbRoute: MGBServerRoutes.retrieveSecurityDescriptionsRoute!)
else { print("Error Getting Security Description Request for \(mySymbol)") ; return }
mySessionSendMGBGenericRequest(session: session, request: myGetDescriptionRequest) { [weak self] success, serverMessage, returnUNISecurityDescription in
guard let self = self else { print("ERROR: self is nil") ; return }
if returnUNISecurityDescription?.count == 0 { print("nil returnUniSecurityDescription for \(mySymbol)") }
// print("DESCRIPTIONS COUNTER = \(counter)")
counter -= 1
var myDescription = UNISecurityDescription()
if returnUNISecurityDescription != nil, returnUNISecurityDescription?.count != 0 { myDescription = returnUNISecurityDescription![0] }
if myDescription.name == nil || myDescription.name == "" { print("Error: No Name for \(String(describing: mySymbol))") }
let myContainersIndices = self.myUNIList.singleContainer.indices.filter({ self.myUNIList.singleContainer[$0].ticker?.symbol == mySymbol })
var myPathArray = [IndexPath]()
for index in 0..<myContainersIndices.count {
self.myUNIList.singleContainer[myContainersIndices[index]].name = myDescription.name
self.myUNIList.singleContainer[myContainersIndices[index]].currencySymbol = myDescription.currencySymbol
self.myUNIList.singleContainer[myContainersIndices[index]].fillFundamentals() // --> Fills the outputs for sortdata
myPathArray.append(IndexPath(row: myContainersIndices[index], section: 0))
}
DispatchQueue.main.async {
self.filteredData = self.myUNIList.singleContainer
self.myCollection?.reloadItems(at: myPathArray)
}
if counter == 0 { // THIS IS TRUE MORE THAN ONCE WHILE IT SHOULD NOT BE TRU MORE THAN ONCE
if doubleCount > 0 { print("WHY!!!!") }
doubleCount += 1
print("DESCRIPTIONS counter = \(counter) -> \(self.myUNIList.listName) - symbols: \(String(describing: mySymbols?.count)) \n==================================================\n")
DispatchQueue.main.async { self.sortNormalTap("Load") { _ in self.displayAfterLoading() } }
completion(true)
return
}
}
}
}
The condition to be met is counter == 0. Once this is met, the function completes and exits a DispatchGroup. The problem is that counter == 0 is true multiple times (with obvious crash in exiting the DispatchGroup). I really cannot understand why this condition is met more than once. The code is pretty linear and I cannot see what causes this. Any help is very appreciated. This is running me crazy.
You're code isn't thread safe, the counter in particular. I wrote an example using your same logic to show illustrate this. If you run it a few times, you will eventually hit the same condition in which your issue is happening.
override func viewDidLoad() {
super.viewDidLoad()
let mySymbols: [Int] = Array(0...100)
for _ in 0..<100 {
xxxfastLoadLSecurityDescriptions(session: URLSession.shared, mySymbols: mySymbols) { (success, counter, doubleCount) in
print("Completed: \(success), Counter: \(counter), Double Count: \(doubleCount)")
}
}
}
private func xxxfastLoadLSecurityDescriptions(session: URLSession, mySymbols: [Int]?, completion: #escaping(Bool, Int, Int) ->()) {
var counter = mySymbols?.count ?? 0
if counter == 0 {
return completion(false, -1, -1)
}
var doubleCount = 0
for symbolIndex in 0..<(mySymbols?.count ?? 0) {
guard let _ = mySymbols?[symbolIndex] else {
print("Error")
continue
}
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(Int.random(in: 50..<900))) {
counter -= 1
DispatchQueue.main.async {
self.view.layoutIfNeeded()
}
if counter == 0 {
if doubleCount > 0 {
// This will eventually print even though logically it shouldn't
print("*****Counter: \(counter), Double Count: \(doubleCount), Symbol Index: \(symbolIndex)")
}
doubleCount += 1
completion(true, counter, doubleCount)
return
}
}
}
}
Output:
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
*******************************************************************
***** Counter: 0, Double Count: 1, Symbol Index: 15
*******************************************************************
Completed: true, Counter: 0, Double Count: 2
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
*******************************************************************
***** Counter: 0, Double Count: 1, Symbol Index: 26
*******************************************************************
Completed: true, Counter: 0, Double Count: 2
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
Completed: true, Counter: 0, Double Count: 1
*******************************************************************
***** Counter: 0, Double Count: 1, Symbol Index: 57
*******************************************************************
Completed: true, Counter: 0, Double Count: 2
*******************************************************************
***** Counter: 0, Double Count: 1, Symbol Index: 3
*******************************************************************
Completed: true, Counter: 0, Double Count: 2
At the end I solved this by using DispatchGroup as follows (simplified code):
private func xxxFastLoadLSecurityDescriptions(session: URLSession, mySymbols: [String]?, completion: #escaping(Bool) ->()) {
let myGroup = DispatchGroup()
for symbolIndex in 0..<(mySymbols?.count ?? 0) {
myGroup.enter()
mySessionSendMGBGenericRequest(...) { [weak self] returnValue in
guard let self = self else { myGroup.leave() ; return }
// do some stuff with returnValue
myGroup.leave()
}
}
myGroup.notify(queue: .main, execute: {
completion(true)
})
}
My question is: is it possible that I run into the same problem as before? For example, say I have a loop for 2 items. The firs item enters the group and say the before the second item enters the loop the async call returns a value and the first item exits the group. At this point, before the second item enters the group the .notify should be triggered and completion(true) exists the function before the second item is processed. Is this possible?

Core Data attempt to recursively call -save

I'm trying to save my core data context after deleting some data sent to the server-side. I'm getting this error when I try context.save()
Error Domain=NSCocoaErrorDomain Code=132001 "(null)" UserInfo={message=attempt to recursively call -save: on the context aborted, stack trace=(
0 CoreData 0x0000000185e8b22c <redacted> + 156
1 ??? 0x0000000106b0caec 0x0 + 4407216876
2 ??? 0x0000000106b0cbd0 0x0 + 4407217104
3 Bakery 0x0000000100d64868 main + 0
4 Bakery 0x0000000100d601b0 _T0Ix_IyB_TR + 48
5 libdispatch.dylib 0x00000001020352cc _dispatch_call_block_and_release + 24
6 libdispatch.dylib 0x000000010203528c _dispatch_client_callout + 16
7 libdispatch.dylib 0x0000000102046b58 _dispatch_root_queue_drain + 1016
8 libdispatch.dylib 0x00000001020466f4 _dispatch_worker_thread3 + 136
9 libsystem_pthread.dylib 0x00000001832eb06c _pthread_wqthread + 1268
10 libsystem_pthread.dylib 0x00000001832eab6c start_wqthread + 4
)}
The call:
func sendBenefitsIfNeeeded(_ status: Reachability.Connection) {
let hasInternetConnection = status != .none
if hasInternetConnection {
self.syncBenefits { _ in
self.deleteBenefitsAlreadySynced()
}
} }
Error here on context.save()
private func syncBenefits(completion: #escaping (AppError?) -> Void) {
syncEmployees { _ in
let request = NSFetchRequest(entityName: "BenefitEntity")
request.returnsObjectsAsFaults = false
do {
let result = try self.context.fetch(request)
guard let resultArray = result as? [NSManagedObject] else {
return
}
for data in resultArray {
let registration = (data.value(forKey: "registration") as? Int) ?? 0
self.grantBenefit(employeeRegistration: registration) { error, message in
guard let benefitSended = self.fetchBenefit(byRegistration: registration) else {
return
}
benefitSended.synced = true
DispatchQueue.main.async {
do {
try self.context.save() // ERROR HERE!
} catch {
}
completion(error)
}
}
}
} catch {
print("Failed")
}
}
}
I don't have any clue what is happening :(
PS: the syncBenefits is being called by a listener that calls the function when the internet connections is changed.
#EDIT: I tried with DispatchQueue.main.async (main thread) but the same error occurs :(