DispatchSemaphore cause app frozen, how to resolve it - ios

when i run code, i click iphone home button, then reopen my app,my app is frozen.i feel uncertain,why?
let queue = DispatchQueue(label: "com.aiswei.asw.monitior.search",qos: .default, attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 30)
let minX = 2
let maxX = 254
self.index = minX
self.index = minX
let count = maxX - minX + 1
let lock = NSLock()
for i in minX...maxX {
queue.async {
semaphore.wait()
lock.lock()
// request some server api result
NetCenter.requestAPi { (result) in
semaphore.signal()
}
lock.unlock()
}
}

I would expect this to try to create over 250 threads, which you don't want to do. That said, my suspicion is that the actual problem is with coming in the foreground is elsewhere; possibly in NetCenter. You're probably deadlocking the main queue (especially if NetCenter is written in the same style).
Rather than kicking off a ton of DispatchWorkItems and then blocking them, you can achieve the same thing by using a single DispatchWorkItem on a serial queue, with your semaphore. The serial queue will get rid of the need for the lock. This serializes 30 operations at a time, which is what I think you want.
let queue = DispatchQueue(label: "com.aiswei.asw.monitior.search")
let semaphore = DispatchSemaphore(value: 30)
queue.async {
let minX = 2
let maxX = 254
for _ in minX...maxX {
semaphore.wait()
NetCenter.requestAPi { (result) in
semaphore.signal()
}
}
}
}
I'm assuming here that requestAPi is an async method and will schedule its completion handler on some queue other than queue. (If NetCenter uses the same queue as this function, than that would definitely deadlock.)

Related

SwiftUI State updates in Xcode 11 Playground

I have not been able to find anything through the standard Google search, but is there any reason why the ContentView is not updating through the ObservableObject? Feel like I am missing something but I am not quite sure what.
import SwiftUI
import PlaygroundSupport
let start = Date()
let seconds = 10.0 * 60.0
func timeRemaining(minutes: Int, seconds: Int) -> String {
return "\(minutes) minutes \(seconds) seconds"
}
class ViewData : ObservableObject {
#Published var timeRemaining: String = "Loading..."
}
// View
struct ContentView: View {
#ObservedObject var viewData: ViewData = ViewData()
var body: some View {
VStack {
Text(viewData.timeRemaining)
}
}
}
let contentView = ContentView()
let viewData = contentView.viewData
let hosting = UIHostingController(rootView: contentView)
// Timer
let timer = DispatchSource.makeTimerSource()
timer.schedule(deadline: .now(), repeating: .seconds(1))
timer.setEventHandler {
let diff = -start.timeIntervalSinceNow
let remaining = seconds - diff
let mins = Int(remaining / 60.0)
let secs = Int(remaining) % 60
let timeRemaning = timeRemaining(minutes: mins, seconds: secs)
viewData.timeRemaining = timeRemaning
print(timeRemaning)
}
timer.resume()
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
timer.cancel()
PlaygroundPage.current.finishExecution()
}
PlaygroundPage.current.setLiveView(contentView)
PlaygroundPage.current.needsIndefiniteExecution = true
The reason is that GCD based timer works on own queue, so here is the fix - view model have to be updated on main, UI, queue as below
DispatchQueue.main.async {
viewData.timeRemaining = timeRemaning
}
The main utility of GCD timers over standard timers is that they can run on a background queue. Like Asperi said, you can dispatch the updates to the main queue if your GCD timer isn’t using the main queue, itself.
But, you might as well just schedule your GCD timer on the main queue from the get go, and then you don’t have to manually dispatch to the main queue at all:
let timer = DispatchSource.makeTimerSource(queue: .main)
But, if you’re going to run this on the main thread, you could just use a Timer, or, in SwiftUI projects, you might prefer Combine’s TimerPublisher:
import Combine
...
var timer: AnyCancellable? = nil
timer = Timer.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink { _ in
let remaining = start.addingTimeInterval(seconds).timeIntervalSince(Date())
guard remaining >= 0 else {
viewData.timeRemaining = "done!"
timer?.cancel()
return
}
let mins = Int(remaining / 60.0)
let secs = Int(remaining) % 60
viewData.timeRemaining = timeRemaining(minutes: mins, seconds: secs)
}
When you incorporate your timer within your SwiftUI code (rather than a global like here), it’s nice to stay within the Combine Publisher paradigm.
I also think it’s probably cleaner to cancel the timer when it expires in the timer handler, rather than doing a separate asyncAfter.
Unrelated, but you might consider using DateComponentsFormatter in your timeRemaining function, e.g.:
let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
formatter.unitsStyle = .full
return formatter
}()
func timeRemaining(_ timeInterval: TimeInterval) -> String {
formatter.string(from: timeInterval) ?? "Error"
}
Then,
It gets you out of the business of calculating minutes and seconds yourself;
The string will be localized; and
It will ensure grammatically correct wording; e.g. when there are 61 seconds left and the existing routine will report a grammatically incorrect “1 minutes, 1 seconds”.
DateComponentsFormatter gets you out of the weeds of handling these sorts of edge cases, where you want singular instead of plural or languages other than English.

Argument labels "(attributes:)" do not match any available overloads in DispatchQueue

I am trying to make the enemy in my game move using DispatchQueue. I tried fixing this error and it keeps telling me attributes do not match any available overloads.
func makeAIMove() {
let strategistTime = CFAbsoluteTimeGetCurrent()
DispatchQueue.global(attributes: .qosUserInitiated).async { [unowned self] in
guard let move = self.strategist.bestMoveForActivePlayer() as? Move else { return }
let delta = CFAbsoluteTimeGetCurrent() - strategistTime
DispatchQueue.main.async{ [unowned self] in
self.rows[move.row][move.col].setPlayer(.choose)
let aiTimeCeiling = 2.0
let delay = min(aiTimeCeiling - delta, aiTimeCeiling)
DispatchQueue.main.after(when: .now() + delay) { [unowned self] in
self.makeMove(row: move.row, col: move.col)
}
}
}
}
I think what you want is DispatchQueue.global(qos: .userInitiated); indeed, there is no overload taking an attributes argument.

GCD Cancel and restart queue when button pressed again

Simply, I have a button that adds to a queue, or I believe that's how it works - I've only just started to use GCD. If I press the button twice in my app, it runs these two processes in parallel and the progress bar races back and forth between them, I'd like to stop all the other instances of the queue and execute a new one if that makes sense, I've not got to grips with the lingo...
#IBAction func makePi(_ sender: UIButton) {
piProgress.progress = 0.0
let queue1 = DispatchQueue(label: "com.appcoda.queue1", qos: DispatchQoS.userInitiated)
queue1.async {
let currentAmount = Int(self.randNum.value)
var gcdCounter = 0
for n in 1...currentAmount {
if self.gcd(self.random(max: Int(self.randRange.value)), self.random(max: Int(self.randRange.value))) == 1{
gcdCounter += 1
}
if n % 1000 == 0 {
DispatchQueue.main.async {
self.piProgress.setProgress(Float(Double(n)/Double(currentAmount)), animated: true)
}
} else if n == currentAmount {
DispatchQueue.main.async {
self.piProgress.setProgress(1.0, animated: true)
}
}
}
let piFinal = sqrt(Double((6/Double((Double(gcdCounter)/Double(currentAmount))))))
let roundedAccuracy: Double = ((piFinal-Double.pi)/Double.pi*100).roundTo(places: 6)
DispatchQueue.main.async {
self.piLabel.text = String(piFinal)
self.piAccuracy.text = "Accuracy: \(roundedAccuracy)%"
}
}
}
I just need all the other queue1.async ones to stop...
P.s. If I've done anything obviously wrong, please help ^^ Thanks.

Wait for other processes

I have to wait for multiple background process before doing other, I am looking for and trying to do something with this, but I can't. Here is the code. I commented the parts to explain what I want to reach. I could do it with completion handler but this function is called for a few times, depending on number of photos.
func prepareArray(stext: NSMutableAttributedString, completionHandler: CompletionHandler) {
self.finalForUpload = [String]()
var attributedArray = [AnyObject]()
var lastRng = 0
let range:NSRange = NSMakeRange(0,stext.length)
stext.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions(rawValue: 0), usingBlock: {(dic: [String:AnyObject]?, range :NSRange!, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let attachement = dic as NSDictionary!
attachement.enumerateKeysAndObjectsUsingBlock({ (key:AnyObject?, obj:AnyObject?, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let stri = key as! NSString!
if stri == "NSAttachment"{
let att:NSTextAttachment = obj as! NSTextAttachment
if att.image != nil{
print("range location: \(range.location)")
if range.location != lastRng {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, range.location-lastRng)))
lastRng = range.length
}
let im:UIImage = att.image!
self.httpManager.uploadPhoto(im, completionHandler: { (imgName) -> Void in
let imName = imgName.characters.dropFirst()
let imageName = String(imName.dropLast())
//WAIT FOR THIS
dispatch_async(dispatch_get_main_queue(), {
print("append")
//stext.attributedSubstringFromRange(range)
attributedArray.append(imageName)
lastRng = range.location
})
})
}
}
})
})
//BEFORE DOING THIS
dispatch_async(dispatch_get_main_queue(), {
if lastRng == 0 && attributedArray.count < 1{
attributedArray.append(stext)
} else {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, stext.length-lastRng)))
}
for var i = 0; i<attributedArray.count; i++ {
print("\n \n \(i) : \(attributedArray[i])")
}
})
}
You can do this using dispatch_group methods.
However I would recommend using something like PromiseKit or similar which gives you a much nicer way of controlling dependencies of multiple async blocks.
Here is your code using dispatch_group methods:
func prepareArray(stext: NSMutableAttributedString, completionHandler: CompletionHandler) {
// create the dispatch group
dispatch_group_t aGroup = dispatch_group_create();
self.finalForUpload = [String]()
var attributedArray = [AnyObject]()
var lastRng = 0
let range:NSRange = NSMakeRange(0,stext.length)
stext.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions(rawValue: 0), usingBlock: {(dic: [String:AnyObject]?, range :NSRange!, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let attachement = dic as NSDictionary!
attachement.enumerateKeysAndObjectsUsingBlock({ (key:AnyObject?, obj:AnyObject?, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let stri = key as! NSString!
if stri == "NSAttachment"{
let att:NSTextAttachment = obj as! NSTextAttachment
if att.image != nil{
print("range location: \(range.location)")
if range.location != lastRng {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, range.location-lastRng)))
lastRng = range.length
}
let im:UIImage = att.image!
// use dispatch_group to coordinate.
// must call before uploadPhoto as we don't know how long it will take
dispatch_group_enter(aGroup);
self.httpManager.uploadPhoto(im, completionHandler: { (imgName) -> Void in
let imName = imgName.characters.dropFirst()
let imageName = String(imName.dropLast())
dispatch_async(aGroup, dispatch_get_main_queue(), {
print("append")
//stext.attributedSubstringFromRange(range)
attributedArray.append(imageName)
lastRng = range.location
// indicate block has completed (reduced group count)
dispatch_group_leave(aGroup);
})
})
}
}
})
})
// wait for all items in group to finish before running this block
dispatch_group_notify(aGroup, dispatch_get_main_queue(), {
if lastRng == 0 && attributedArray.count < 1{
attributedArray.append(stext)
} else {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, stext.length-lastRng)))
}
for var i = 0; i<attributedArray.count; i++ {
print("\n \n \(i) : \(attributedArray[i])")
}
})
}
I have to wait for multiple background process before doing other
You should really look at using NSOperationQueue for this requirement as it allows you to configure dependencies between all of the registered operations. In this way you could configure and add a number of downloads and then configure a post download action which is dependent on all of those downloads. The operation queue will deal with starting that operation when all others are complete.
The operations also give you a nice way to cancel the pending uploads / downloads if you don't need them any more.
You could alternatively look at using dispatch_groups to manage this. It's harder to ensure that what you've done is entirely correct.
The main queue is a serial queue, meaning that it executes on block at a time in first-in-first-out order. All of the dispatches in the enumerate should be done before the one at the bottom is run.
EDIT: just noticed that you async in a completion handler.
One simple thing to do is to just count down (because you know how many there are).
Just add var expectedCompletions = attachement.count before the enumerate and then count them down (expectedCompletions--) inside the block. When it gets to 0, run the code at the end.

Single SKAction Animation on several nodes using a loop with completion handler

Objective: Apply a single SKAction Animation on several nodes using a loop, while waiting for completion handler.
func animateShuffle(cards: [Card], completionHandler:(success: Bool) -> Void) {
var rotationInRadians:CGFloat = CGFloat(M_PI)
var rotateFirstDeck = SKAction.rotateByAngle(rotationInRadians, duration: 1.0)
var moveFirstDeck = SKAction.moveToX(-150, duration: 1.0)
var returnToOrigPosition = SKAction.moveToX(-125, duration: 1.0)
var firstDeckSequence = SKAction.sequence([moveFirstDeck, rotateFirstDeck, returnToOrigPosition])
var rotateSecondDeck = SKAction.rotateByAngle(-rotationInRadians, duration: 1.0)
var moveSecondDeck = SKAction.moveToX(-100, duration: 1.0)
var secondDeckSequence = SKAction.sequence([moveSecondDeck, rotateSecondDeck, returnToOrigPosition])
for (index, Card) in enumerate(cards){
if index % 2 == 0{
Card.image.runAction(firstDeckSequence)
}
else{
Card.image.runAction(secondDeckSequence)
}
}
//hack
cards[0].image.runAction(firstDeckSequence, completion: { () -> Void in
completionHandler(success: true)
})
}
Here I call the function which kicks off another Animation:
animateShuffle(newDeck.deck, completionHandler: { (success) -> Void in
print("entered")
var drawnCards = [Card]()
if let areThereCards = newDeck.drawCards(4){
drawnCards = areThereCards
}
self.dealCards(drawnCards)
})
I used a hack to get to 99% of where I want to be but my gut tells me there is better way to do this. How can I tell when the last node has finished animating?
Both sequences run for 3 seconds so when the first completion block runs you can safely assume they are all done. To be 100% certain set a bool ivar in the completion handler and then run your post-sequence code in the didEvaluateActions method of the scene.
Alternatively count how many sequences you run, then increase a counter in completion block, and run your code when both counters match.

Resources