I have been designing an app that analyzes lines of text, and I want to use SVProgressHUD to show progress of it.
This is my code:
let total = text.count
for line in text{
count = count + 1
DispatchQueue.global(pos: .background).async{
//Analyzing line
DispatchQueue.main.async{
SVProgressHUD.showProgress(count/total)
}
}
}
The analyzing works, and the HUD shows properly, when the count reaches total, the process gets stuck, and SVProgressHUD stops at max status, and the program stops. What is the issue with the program?
Am I using Dispatchqueue wrong?
Do I have to call something other to free the background process or something?
I have tried exchanging //Analyzing line and SVProgressHUD.show..., but it still doesn't work. I initially used SVProgress within the loop without the dispatchqueue, but then the progress hud moves only after the analyzation(full loop) has been completed, which is a problem.
Any help would be appreciated.
Thank you.
Try using this code. It does not use loop but implements recursive calls to a function for processing your string data.
func processLines() {
SVProgressHUD.showProgress(0)
// sample data
var text = Array("abcdefghijklmnop")
var lines : [Line] = []
let count : [Int] = Array(0..<text.count)
count.forEach({ pos in lines.append(Line(char: text[pos])) })
var currentIndex : Int = 0
func processLine(at index: Int) {
DispatchQueue.global(qos: .background).async{
//Analyzing line
let line = lines[index]
print("Processing Line CHAR: \(line.char)")
DispatchQueue.main.async{
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
guard currentIndex < lines.count-1 else {
SVProgressHUD.showProgress(1)
return
}
currentIndex += 1
startLineProces(at: currentIndex)
}
}
}
}
func startLineProces(at index: Int) {
processLine(at: index)
SVProgressHUD.showProgress(Float(index) / Float(lines.count))
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
startLineProces(at: currentIndex)
}
}
struct Line {
var char: Character
init(char: Character) {
self.char = char
}
}
Use the below string extension to analyse your string. It has a completion block which will return progress as well as status of completion.
extension String {
func analyseString(completion: #escaping (Bool, Float) -> ()) {
let totalCountOfString = self.count
for (index, _) in self.enumerated() {
if index == totalCountOfString - 1 {
completion(true, Float(index)/Float(totalCountOfString))
} else {
completion(false, Float(index)/Float(totalCountOfString))
}
}
}
}
You can call the above method to show your progress as below (maybe on a button click). self.yourString is your input string that you need to analyse.
#IBAction func clicked(_ sender: UIButton) {
DispatchQueue.main.async {
self.yourString.analyseString { (isCompleted, progress) in
if isCompleted {
SVProgressHUD.dismiss()
print("Ending")
} else {
SVProgressHUD.showProgress(progress, status: "Analysing (\(progress)%)")
}
}
}
}
Related
In Swift, I have a recursive function which I want it to be executed every minute. It looks something like below.
func someFunc() {
// business logics...
DispatchQueue.global().asyncAfter(deadline: .now() + 60) {
self.someFunc()
}
}
The thing is that this someFunc() can be initiated by multiple callers, but I only want to allow one instance of someFunc() running or waiting to be executed in the future.
What is the best way to guarantee that at most 1 someFunc() will be running or queued at any given timeeeee?
(I am using Swift 5.3)
You can also use NSLock for this:
import PlaygroundSupport
let lock = NSLock()
func someFunc(msg: String) {
guard lock.try() else { return }
print(msg)
DispatchQueue.global().asyncAfter(deadline: .now() + 5) {
lock.unlock()
someFunc(msg: msg)
}
}
someFunc(msg: "call1")
someFunc(msg: "call2") // Ignored
someFunc(msg: "call3") // Ignored
PlaygroundPage.current.needsIndefiniteExecution = true
You can use a boolean to keep track of the function call is queued (assuming you're not calling someFunc() from different threads)
var isQueued = false
func someFunc() {
if isQueued { return }
// business logics...
isQueued = true
DispatchQueue.global().asyncAfter(deadline: .now() + 60) {
self.isQueued = false
self.someFunc()
}
}
Example
class Example {
var isQueued = false
func someFunc(_ i: Int) {
if isQueued { return }
// business logics...
if i > 500 {
return
}
print(i)
isQueued = true
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
self.isQueued = false
self.someFunc(i * 10)
}
}
}
let t = Example()
t.someFunc(1)
sleep(2)
t.someFunc(2)
sleep(1)
t.someFunc(3)
sleep(1)
t.someFunc(4)
sleep(1)
t.someFunc(5)
prints
1
10
100
4
40
400
I am creating a simple card game (Set) in SwiftUI. I have a button that will deal X new cards when tapped. Currently, it makes all cards show up at once. I was wondering how I could make them come out one at a time.
Deal works by appending a new card to a Deck array in the model. ContentView displays each card in the grid.
This is what I currently have after looking online. Displays first card then next all at once
func deal(_ numberOfCards: Int) {
withAnimation(Animation.easeInOut(duration: 1)) {
viewModel.deal()
}
for _ in 1..<numberOfCards {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
withAnimation(.easeInOut) {
viewModel.deal()
}
}
}
}
Try this
func deal(_ numberOfCards: Int) {
withAnimation(Animation.easeInOut(duration: 1)) {
viewModel.deal()
}
for i in 1..<numberOfCards {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.7) {
withAnimation(.easeInOut) {
viewModel.deal()
}
}
}
}
The problem is that you’re starting all of them 0.7 seconds from now. You want to multiply that interval by the for loop index. You can probably also simplify it a bit, e.g.:
func deal(_ numberOfCards: Int) {
for i in 0..<numberOfCards {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.7) {
withAnimation(.easeInOut) {
viewModel.deal()
}
}
}
}
This pattern isn’t ideal, though, because if you dismiss the view in question, it’s still going to be trying to flip cards on view that isn’t present anymore. Also, this pattern of issuing multiple asyncAfter is not great because it’s subject to timer coalescing (where latter calls may be coalesced together to save battery). This latter issue might not be an issue here, but as a general rule, we use Timer to prevent coalescing and to be able to cancel the timer when the view is dismissed.
func deal(_ numberOfCards: Int) {
var cardNumber = 0
let timer = Timer.scheduledTimer(withTimeInterval: 0.7, repeats: true) { timer in
withAnimation(.easeInOut) {
viewModel.deal()
}
cardNumber += 1
if cardNumber >= numberOfCards {
timer.invalidate()
}
}
timer.fire()
}
If this was in a class, I might use [weak self] in the timer closure with
guard let self = self else {
timer.invalidate()
return
}
I have the following extension which I want to make usable for both an UITextView and an UILabel.
extension UITextView {
func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 2.5) {
text = ""
var writingTask: DispatchWorkItem?
writingTask = DispatchWorkItem { [weak weakSelf = self] in
for character in typedText {
DispatchQueue.main.async {
weakSelf?.text!.append(character)
}
Thread.sleep(forTimeInterval: characterDelay/100)
}
}
if let task = writingTask {
let queue = DispatchQueue(label: "typespeed", qos: DispatchQoS.userInteractive)
queue.asyncAfter(deadline: .now() + 0.05, execute: task)
}
}
}
I tried to set the type to UIView found here: Stack Overflow: Single extension for UITextView and UITextField in Swift
extension UIView {
func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 2.5) {
if self is UILabel || self is UITextView {
text = ""
var writingTask: DispatchWorkItem?
writingTask = DispatchWorkItem { [weak weakSelf = self] in
for character in typedText {
DispatchQueue.main.async {
weakSelf?.text!.append(character)
}
Thread.sleep(forTimeInterval: characterDelay/100)
}
}
if let task = writingTask {
let queue = DispatchQueue(label: "typespeed", qos: DispatchQoS.userInteractive)
queue.asyncAfter(deadline: .now() + 0.05, execute: task)
}
}
}
}
That of course doesn't work because UIView has no text property so I get the following error:
Use of unresolved identifier 'text'
How can I solve this?
That of course doesn't work because UIView has no text property
This is exactly the piece you want to think about. What do you need in order to write this method? Two things: it needs a text property. And it needs to be a class type (because you use a weak modifier on it). So say that.
protocol TypeAnimated: AnyObject {
var text: String? { get set }
}
Except that for historical reasons, UITextView has a String! while UILabel has a String?. That's very frustrating, but we can bridge the two with a new property:
protocol TypeAnimated: AnyObject {
var animatedText: String { get set }
}
Now, given that protocol, you can write you method:
extension TypeAnimated {
func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 2.5) {
animatedText = ""
var writingTask: DispatchWorkItem?
writingTask = DispatchWorkItem { [weak weakSelf = self] in
for character in typedText {
DispatchQueue.main.async {
weakSelf?.animatedText!.append(character)
}
Thread.sleep(forTimeInterval: characterDelay/100)
}
}
if let task = writingTask {
let queue = DispatchQueue(label: "typespeed", qos: DispatchQoS.userInteractive)
queue.asyncAfter(deadline: .now() + 0.05, execute: task)
}
}
}
Now, you just need to label any types you want to conform to this protocol, and they'll get the extension.
extension UILabel: TypeAnimated {
var animatedText: String {
get { return text ?? "" }
set { text = newValue }
}
}
extension UITextView: TypeAnimated {
var animatedText: String {
get { return text ?? "" }
set { text = newValue }
}
}
As a side note, generating a new queue every time this is executed is almost certainly not what you mean. You should probably just set this up as a series of asyncAfter calls to the main queue, or use a Timer, or call asyncAfter inside the asyncAfter block. But none of this really impacts your question.
I haven't tested this, but this is how I would probably approach the problem:
extension TypeAnimated {
func setTextWithTypeAnimation(typedText: String, characterDelay: TimeInterval = 2.5/100) {
func addNextCharacter(from string: Substring) {
DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay) { [weak self] in
if let self = self, let nextChar = string.first {
self.animatedText.append(nextChar)
addNextCharacter(from: string.dropFirst())
}
}
}
animatedText = ""
addNextCharacter(from: typedText[...])
}
}
I have a secondary LaunchScreenViewController for an App that has some animation whilst it gathers three types of background data.
Everything works but the order in which the DispatchQueues.async are run is random. However if I change them to DispatchQueues.sync everything happens in the right order but runs so fast (even with sleeps) you don't see the animations.
This needs to be .sync but how do I control the U/I so that I can see the animation? (Shown here as, e.g. self.subLogo1View.isHidden = true)
Here's the code:
// Queuing Variables
var semaphore = DispatchSemaphore(value: 1)
var semaphoreSub = DispatchSemaphore(value: 1)
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global().async {
self.semaphore.wait()
self.gatherData()
self.semaphore.signal()
}
DispatchQueue.global().async {
self.semaphore.wait()
self.checkNetworkAvailability()
self.semaphore.signal()
}
DispatchQueue.global().async {
self.semaphore.wait()
self.checkSomething()
self.semaphore.signal()
}
}
func gatherData() {
DispatchQueue.main.async {
self.semaphoreSub.wait()
print ("1")
self.subLogo1View.isHidden = true
self.subLogo1View.setNeedsDisplay()
self.semaphoreSub.signal()
}
}
func checkNetworkAvailability() {
DispatchQueue.main.async {
self.semaphoreSub.wait()
print ("2")
self.subLogo2View.isHidden = true
self.subLogo2View.setNeedsDisplay()
self.semaphoreSub.signal()
}
}
func checkSomething() {
DispatchQueue.main.async {
self.semaphoreSub.wait()
print ("3")
self.subLogo3View.isHidden = true
self.subLogo3View.setNeedsDisplay()
self.semaphoreSub.signal()
}
}
Instead of manually serializing your closures with a bunch of semaphores, you maybe better use a custom serial queue. For animation, user UIView.animate
Something like this:
func gatherData() {
DispatchQueue.main.async { // or sync, depending on your animation needs
print ("1: gather Data")
UIView.animate(withDuration: 0.5) {
self.subLogo1View.alpha = 0 // instead of isHidden
}
}
}
func viewDidLoad() {
var mySerialQueue = DispatchQueue (label:"my.serial")
mySerialQueue.async {
self.gatherData()
}
mySerialQueue.async {
self.checkNetworkAvailability()
}
// ...
}
I have a few unit tests in which I'd like to test if a callback is called on the correct dispatch queue.
In Swift 2, I compared the label of the current queue to my test queue. However in Swift 3 the DISPATCH_CURRENT_QUEUE_LABEL constant no longer exists.
I did find the dispatch_assert_queue function. Which seems to be what I need, but I'm not sure how to call it.
My Swift 2 code:
let testQueueLabel = "com.example.my-test-queue"
let testQueue = dispatch_queue_create(testQueueLabel, nil)
let currentQueueLabel = String(UTF8String: dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL))!
XCTAssertEqual(currentQueueLabel, testQueueLabel, "callback should be called on specified queue")
Update:
I got confused by the lack of autocomplete, but it is possible to use __dispatch_assert_queue:
if #available(iOS 10.0, *) {
__dispatch_assert_queue(test1Queue)
}
While this does work for unit tests, it annoyingly stops the whole process with a EXC_BAD_INSTRUCTION instead of only failing a test.
Use dispatchPrecondition(.onQueue(expectedQueue)), the Swift 3 API replacement for the dispatch_assert_queue() C API.
This was covered in the WWDC 2016 GCD session (21:00, Slide 128):
https://developer.apple.com/videos/play/wwdc2016/720/
Answering my own question:
Based on KFDoom's comments, I'm now using setSpecific and getSpecific.
This creates a key, sets it on the test queue, and later on, gets it again:
let testQueueLabel = "com.example.my-test-queue"
let testQueue = DispatchQueue(label: testQueueLabel, attributes: [])
let testQueueKey = DispatchSpecificKey<Void>()
testQueue.setSpecific(key: testQueueKey, value: ())
// ... later on, to test:
XCTAssertNotNil(DispatchQueue.getSpecific(key: testQueueKey), "callback should be called on specified queue")
Note that there's no value associated with the key (its type is Void), I'm only interested in the existence of the specific, not in it's value.
Important!
Make sure to keep a reference to the key, or cleanup after you're done using it. Otherwise a newly created key could use the same memory address, leading to weird behaviour. See: http://tom.lokhorst.eu/2018/02/leaky-abstractions-in-swift-with-dispatchqueue
Tests based on KFDoom's answer:
import XCTest
import Dispatch
class TestQueue: XCTestCase {
func testWithSpecificKey() {
let queue = DispatchQueue(label: "label")
let key = DispatchSpecificKey<Void>()
queue.setSpecific(key:key, value:())
let expectation1 = expectation(withDescription: "main")
let expectation2 = expectation(withDescription: "queue")
DispatchQueue.main.async {
if (DispatchQueue.getSpecific(key: key) == nil) {
expectation1.fulfill()
}
}
queue.async {
if (DispatchQueue.getSpecific(key: key) != nil) {
expectation2.fulfill()
}
}
waitForExpectations(withTimeout: 1, handler: nil)
}
func testWithPrecondition() {
let queue = DispatchQueue(label: "label")
let expectation1 = expectation(withDescription: "main")
let expectation2 = expectation(withDescription: "queue")
DispatchQueue.main.async {
dispatchPrecondition(condition: .notOnQueue(queue))
expectation1.fulfill()
}
queue.async {
dispatchPrecondition(condition: .onQueue(queue))
expectation2.fulfill()
}
waitForExpectations(withTimeout: 1, handler: nil)
}
}
One option is to set a precondition to test directly for the queue or set "specific" on it and retrieve it later. Further, one could use setSpecific and getSpecific. Alternatively, you can use a precondition check if you're on a queue so that should fulfill the "get current" need. src: https://github.com/duemunk/Async/blob/feature/Swift_3.0/AsyncTest/AsyncTests.swift
and
https://github.com/apple/swift/blob/master/stdlib/public/SDK/Dispatch/Dispatch.swift
One related option is to set a Main Queue / UI Queue precondition:
dispatchPrecondition(condition: .onQueue(DispatchQueue.main))
/*
Dispatch queue and NSOperations in Swift 3 Xcode 8
*/
protocol Container {
associatedtype ItemType
var count: Int { get }
mutating func pop()
mutating func push(item: ItemType)
mutating func append(item: ItemType)
subscript(i: Int) -> ItemType { get }
}
//Generic Function
struct GenericStack<Element> : Container {
mutating internal func push(item: Element) {
items.append(item)
}
mutating internal func pop() {
items.removeLast()
}
var items = [ItemType]()
internal subscript(i: Int) -> Element {
return items[i]
}
mutating internal func append(item: Element) {
self.push(item: item)
}
internal var count: Int { return items.count }
typealias ItemType = Element
}
var myGenericStack = GenericStack<String>()
myGenericStack.append(item: "Narendra")
myGenericStack.append(item: "Bade")
myGenericStack.count
myGenericStack.pop()
myGenericStack.count
//Some NSOperation
class ExploreOperationAndThread {
func performOperation() {
//Create queue
let queue = OperationQueue()
let operation1 = BlockOperation {
var count = myGenericStack.count
while count > 0 {
myGenericStack.pop()
count -= 1
}
}
operation1.completionBlock = {
print("Operation 1")
}
let operation2 = BlockOperation {
var count = 0
while count == 10 {
myGenericStack.append(item: "ItemAdded")
count += 1
}
}
operation2.completionBlock = {
print("Operation 2")
print(myGenericStack.items)
}
//Suppose operation 3 is related to UI
let operation3 = BlockOperation {
//run on main thread
DispatchQueue.main.async {
print(myGenericStack.items.count)
}
}
operation3.completionBlock = {
print("Operation 3")
print(myGenericStack.items.count)
}
//add operation into queue
queue.addOperation(operation3)
queue.addOperation(operation1)
queue.addOperation(operation2)
//Limit number of concurrent operation in queue
queue.maxConcurrentOperationCount = 1
//add dependancies
operation1.addDependency(operation2)
operation2.addDependency(operation3)
if myGenericStack.items.count == 0 {
//remove dependency
operation1.removeDependency(operation2)
}
}
}
//Other ways of using queues
DispatchQueue.global(qos: .userInitiated).async {
ExploreOperationAndThread().performOperation()
}
DispatchQueue.main.async {
print("I am performing operation on main theread asynchronously")
}
OperationQueue.main.addOperation {
var count = 0
while count == 10 {
myGenericStack.append(item: "Narendra")
count += 1
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5 , execute: {
ExploreOperationAndThread().performOperation()
})
let queue2 = DispatchQueue(label: "queue2") //Default is serial queue
queue2.async {
print("asynchronously")
}