GCD: Making Sequential Download Calls; ONE BY ONE - ios

SCENARIO
The app downloads user subscriptions one by one. This call will be made in multiple places (in completion block after another network call and from a button press from a UIAlertController). The logic is to download all the subscriptions and once one subscription download is down it goes to the next until all have been downloaded and then our SVProgressHUD dismisses. The code works great when we build and run from Xcode. But when we build the IPA and send to our customer, this logic creates some sort of a stall, and the SVProgressHUD alert keeps spinning:
This is a big problem because our application is focused around downloading content from subscriptions.
Why is this logic stalling after I Archive and build an IPA from it, but not when I build and run from Xcode?
Code Below:
// Making the call
DispatchQueue.main.async {
DGWebService().syncUserSubscribedContent {
DispatchQueue.main.async {
self.finishLogin()
}
}
}
// Sequentially going through each subscription and downloading them
func syncUserSubscribedContent(completion: #escaping Constants.WebService.ContentCompletion) {
let subscriptions = MPTUser.sharedUser.getSubscriptionsForDownload()
DispatchQueue.global().async {
if subscriptions.count > 0 {
var index:Int = 0
var t = subscriptions.count
var downloading: Bool = false
while t != 0 {
if downloading == false {
downloading = true
if index < 0 {
index = 0
}
if index > subscriptions.count - 1 {
index = subscriptions.count - 1
}
if index <= subscriptions.count {
let subscription = subscriptions[index]
if subscription.didDownloadContent == false {
if let subscriptionID = subscription.subscriptionID {
DispatchQueue.main.async {
SVProgressHUD.show(withStatus: "Downloading Documents\nfor\n\(subscription.functionalGroupName!)\n\(index+1) of \(subscriptions.count)")
}
self.getUserSubscribedContent(subscriptionID: subscriptionID, completion: { (success) in
subscription.didDownloadContent = true
index += 1
t -= 1
downloading = false
})
}
else {
index += 1
t -= 1
downloading = false
}
}
}
else {
index += 1
t -= 1
downloading = false
}
}
}
}
completion()
}
}
self.getUserSubscribedContent is a function that downloads the content and sends a completion back in the block.
If someone could help me out here it would be much appreciated.

You can try using a DispatchGroup. Here's a rough (and untested) example:
DispatchQueue.global().async {
let subscriptions = MPTUser.sharedUser.getSubscriptionsForDownload()
let group = DispatchGroup()
var completed = 0
let completion: (Bool) -> Void = {
if $0 {
completed += 1
}
group.leave()
DispatchQueue.main.async {
SVProgressHUD.show(withStatus: "Downloading Documents\nfor\n\(subscription.functionalGroupName!)\n\(completed) of \(subscriptions.count)")
}
}
for subscription in subscriptions {
self.getUserSubscribedContent(subscriptionID: subscription.subscriptionID, completion: completion)
group.enter()
}
// However long you want to wait (in seconds) before timing out
_ = group.wait(timeout: .now() + 30)
}

Related

UITableView scrolling performance problem

I am currently working as a 5 month junior ios developer.
The project I'm working on is an application that shows the prices of 70 cryptocurrencies realtime with websocket connection.
we used websocket connection, UItableview, UITableViewDiffableDataSource, NSDiffableDataSourceSnapshot while developing the application.
But right now there are problems such as slowdown scrolling or not stop scroling and UI locking while scrolling in the tableview because too much data is processed at the same time.
after i check cpu performance with timer profiler I came to the conclusion that updateDataSource and updateUI functions exhausting the main thread.
func updateDataSource(model: [PairModel]) {
var snapshot = DiffableDataSourceSnapshot()
let diff = model.difference(from: snapshot.itemIdentifiers)
let currentIdentifiers = snapshot.itemIdentifiers
guard let newIdentifiers = currentIdentifiers.applying(diff) else {
return
}
snapshot.appendSections([.first])
snapshot.deleteItems(currentIdentifiers)
snapshot.appendItems(newIdentifiers)
dataSource?.apply(snapshot, animatingDifferences: false, completion: nil)
}
func updateUI(data: SocketData) {
guard let newData = data.data else { return }
guard let current = data.data?.price else { return }
guard let closed = data.data?.lastDayClosePrice else { return }
let dailyChange = ((current - closed)/closed)*100
DispatchQueue.main.async { [self] in
if model.filter({ $0.symbol == newData.pairSymbol }).first != nil {
let index = model.enumerated().first(where: { $0.element.symbol == newData.pairSymbol})
guard let location = index?.offset else { return }
model[location].price = current
model[location].dailyPercent = dailyChange
if calculateLastSignalTime(alertDate: model[location].alertDate) > 0 {
//Do Nothing
} else {
model[location].alertDate = ""
model[location].alertType = ""
}
if let text = allSymbolsView.searchTextField.text {
if text != "" {
filteredModel = model.filter({ $0.name.contains(text) || $0.symbol.contains(text) })
updateDataSource(model: filteredModel)
} else {
filteredModel = model
updateDataSource(model: filteredModel)
}
}
}
delegate?.pricesChange(data: self.model)
}
}
Regards.
ALL of your code is running on the main thread. You have to wrap your entire updateUI function inside a DispatchQueue.global(qos:), and then wrap your dataSource.apply(snapshot) line inside a DispatchQueue.main.async. the dataSource.apply(snapshot) line is the only UI work you're doing in all that code you posted.

Screen freezing for large task execution

I am trying to use completion block but still screen is freezing may I know what is wrong here? Or how can I improve?
func generateRandom(text: String, frame: CGRect,
stackFont: [StackTextFont], completion: #escaping (StackableTextStyle) -> Void) {
var style = generateStyle(text: text, frame: frame, stackFont: stackFont)
var maxCounter = 0
while style == nil || !isValidStyleForFrameSize(style!.0, frame: frame, stackFonts: style!.1.fonts) {
style = generateStyle(text: text, frame: frame, stackFont: stackFont)
maxCounter = maxCounter + 1
if maxCounter > loopBreakerAt {
break
}
print(maxCounter)
}
completion(style ?? (text, StackTextGroup(fonts: stackFont)))
}
Note: Assume that loop might execute more than 100000 time to get the validate style
Calling the function
var i = 10
var counter = 1
let group = DispatchGroup()
while i > 0 {
group.enter()
QuoteNeuralParser.shared.generateRandom(text: text, frame: frame, stackFont: stackFont) { (style) in
/// Avoid duplicate styles
if !self.data.contains(where: {$0.1.1.fonts == style.1.fonts}) {
if let img = self.getIMStackLayer(frame: frame, style: style).toUIImage() {
self.data.append((img, style))
i = i - 1
}
}
/// Loop breaker for infinite attempt
counter += 1
if counter > 30 { i = -1 }
group.leave()
}
}
group.notify(queue: .main) { [weak self] in
guard let self = self else {
return
}
}

Requiring consent before proceeding with survey in ResearchKit

I'm currently creating a program that wants to utilize ResearchKit. Before getting into survey questions, I will need to require consent. I am using the Ray Wenderlich (https://www.raywenderlich.com/1820-researchkit-tutorial-with-swift-getting-started) tutorial and have this code set up. I've already tried to go on with the survey part of my program, and I can access it without even going through consent.
import ResearchKit
public var ConsentTask: ORKOrderedTask {
var ctr = 0
let Document = ORKConsentDocument()
Document.title = "Consent"
//section types
let sectionTypes: [ORKConsentSectionType] = [
.overview,
.dataGathering,
.privacy
]
let consentSections: [ORKConsentSection] = sectionTypes.map { contentSectionType in
let consentSection = ORKConsentSection(type: contentSectionType)
if ctr < sectionTypes.count {
if ctr == 0 {
consentSection.summary = "Summary"
consentSection.content = "Content"
}
else if ctr == 1 { //Data Gathering
consentSection.summary = "Summary"
consentSection.content = "Content"
}
else if ctr == 2 { //Privacy
consentSection.summary = "Summary"
consentSection.content = "Content"
}
ctr = ctr + 1
}
return consentSection
}
Document.sections = consentSections
Document.addSignature(ORKConsentSignature(forPersonWithTitle: nil, dateFormatString: nil, identifier: "UserSignature"))
var steps = [ORKStep]()
let visualConsentStep = ORKVisualConsentStep(identifier: "VisualConsent", document: Document)
steps += [visualConsentStep]
let signature = Document.signatures!.first! as ORKConsentSignature
let reviewConsentStep = ORKConsentReviewStep(identifier: "Review", signature: signature, in: Document)
reviewConsentStep.text = "Review the consent"
reviewConsentStep.reasonForConsent = "I agree"
steps += [reviewConsentStep]
//Completion
let completionStep = ORKCompletionStep(identifier: "CompletionStep")
completionStep.title = "Welcome"
completionStep.text = "Thank you for helping!"
steps += [completionStep]
return ORKOrderedTask(identifier: "ConsentTask", steps: steps)
}
And in my view controller, I have
#IBAction func consentTask(_ sender: Any) {
if consentDone == false {
let taskViewController = ORKTaskViewController(task: ConsentTask, taskRun: nil)
taskViewController.delegate = self
present(taskViewController, animated: true, completion: nil)
}
else if consentDone == true {
//would they like to leave the study
}
}
With my efforts of putting a consentDone flag here.
func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) {
taskViewController.dismiss(animated: true, completion: {() in self.consentDone = true})
print(consentDone)
}
However, what happens is that if the user presses cancel on the top right or done at the very end of the consent, it will trigger this always. Is there a way to be able to make sure that a block of code is executed only after everything is finished? Ideally, after this, I would like to make this a flag that the user has already finished the consent. I will redirect the user to a different page every time afterwards until the user leaves the study.
After some trial and error, I found the answer here: https://github.com/ResearchKit/ResearchKit/issues/919
By knowing that the signature of the user means that the user has finished the form, we can do
if result.identifier == "UserSignature"{
print(result)
let consentDoneAnswerResult = result as! ORKConsentSignatureResult
let consentDone = consentDoneAnswerResult.consented
print(consentDone)
}
And this gives consentDone as true when the form is done, not canceled.

Function in Swift will run, but will not execute "completion" in Swift

I am trying to call a function in Swift. The first time I call it, it runs as intended, but the second time the completion handler if and else statements don't run. Despite this, I still know the function runs because I had it print "RAN" every time it ran. I have no idea what I am doing wrong. Any help?
My Code:
[other code]
self.movieNum = Int(arc4random_uniform(50) + 1)
// Int movieNum is turned into String
let movieNumNSNumber = self.movieNum as NSNumber
let movieNumString: String = movieNumNSNumber.stringValue
self.checkIfMovieHasAlreadyBeenShown(movieID: movieNumString, completion: { seen in
if seen {
var i = 1
var haveAllBeenSeen = 1
while i <= 50 {
let iNSNumber = i as NSNumber
let iString: String = iNSNumber.stringValue
self.checkIfMovieHasAlreadyBeenShown(movieID: movieNumString, completion: { seen in
if seen {
print("SEEN")
}
else {
print("NOT SEEN")
haveAllBeenSeen = 0
}
})
i += 1
}
if haveAllBeenSeen == 0 {
self.swipingView()
}
else {
[other code]
}
else {
return
}
})

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.

Resources