Why is popViewControllerAnimated taking so long to run? - ios

I have a secondary viewController that allows me to delete images from the camera roll. The problem is, the completionHandler fires like it's suppose to, but the popViewController doesn't actually seem to run for about 8 seconds. It definitely fires, because I can see the optional output. And I checked just doing the pop, and it runs correctly. I checked the viewWillDisapear event, and it fires late as well, which I expected considering the nav controller hadn't popped the view current viewController yet.
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
PHAssetChangeRequest.deleteAssets(assetsToDelete)
return
}, completionHandler: { success, error in
if success {
println("success")
println(navigationController.popViewControllerAnimated(true))
println("so slow")
}
if let error = error {
println(error)
}
return
})

This is what the documentation says:
Photos executes both the change block and the completion handler block
on an arbitrary serial queue. To update your app’s UI as a result of a
change, dispatch that work to the main queue.
The navigation controller needs to be executed from the main thread, so you need to wrap the call to something like
dispatch_async(dispatch_get_main_queue()) {
navigationController.popViewControllerAnimated(true)
}

For Swift 3
DispatchQueue.main.async() {
self.navigationController?.popViewController(animated: true)
}

Related

Display a progressHud before the network call in swift

In my code I am executing a network call which takes few seconds to complete. While it is executing I want to display a progresshud in the background. But the problem is the progresshud does not appears before the network call. It appears right after the network call finishes. I can not understand the issue.
My code is below.
func draw() {
if !self.drawing) {
self.progressHud.show(in: self.view)
self.drawing = true
self.drawImage() // this is the function that takes time to execute
}
else if (self.isTransformViewEnabled){
self.drawing = false
}
}
Please help
I think you are calling the network call synchronously. So it waits the request to complete for showing hud. You can call in on Main thread on async
DispatchQueue.main.async {
//your code here
}
This is happening because you are making the network call on the main queue synchronously. Hence the UI is updated after the call is completed.
Please make the network call asynchronously in a different queue and the HUD will show up on the screen.
DispatchQueue.main.async {
self.drawImage()
}
Let me know if it works for you.
Happy to help.
Thanks.

How do I ensure that all network calls have been made before accessing my core data model?

I am making multiple api calls in succession and when I finally push to my next view controller my data comes up completely blank from my core data model. In ViewController A I have made the following requests in this order:
Api.verifyOtp(email, otp).continueWith { (task) -> Any? in
if task.succeed {
self.apiCallOne()
self.apiCallTwo()
self.apiCallThree()
self.apiCallFour()
self.apiCallFive()
} else {
Hud.hide()
task.showError()
}
return nil
}
Now all of these calls are made asynchronously. However the last method which is self.apiCallFive() is the method that pushes to ViewController B. Here is the call:
Api.apiCallFive().continueOnSuccessWith { (task) -> Any? in
Hud.hide()
if task.succeed {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewB storyboard.instantiateViewController(withIdentifier: "ViewControllerB" self.navigationController?.pushViewController(viewB, animated: true)
}
My guess is that since all of these requests are happening asynchronously then there's no guarantee on which call will finish first. So the apiCallFive() is pushing and loading ViewController B before the others are able to finish. How can I make it so the next view will not be loaded or pushed to until all of the tasks have been completed?
Thank you!
I have faced the same issue. Fix it by using DispatchGroup.
Code:
Define as property
let APIGroup = DispatchGroup()
Execute below code when any API Calling starts.
APIGroup.enter()
Execute below code when any API Calling Completed.
downloadGroup.leave()
Notify Block:
APIGroup.notify(queue: DispatchQueue.main) {
print("All APIs called successfully: Perform required operation")
}
There no need to manage by any counter or other variables. notify block call automatically when all task completed successfully.
What’s really important here is the enter-leave pairs. You have to be
very careful and make sure that you leave the group. It would be easy
to introduce a bug in the code above. Let’s say that we didn’t leave
the group in that guard statement above, just before the return. If
the API called failed, or the JSON was malformed, the number of groups> entries would not match the number of leaves. So the group completion
handler would never get called. If you’re calling this method from the
UI and displaying an activity indicator while your networking requests
are running, you would never get a callback from the method, and you
would keep on spinning 🙂
Apple documents
To solve this you need a way of getting notified when each call is finished.
The easiest way of doing this is using completion blocks on each call.
func apiCall(completion: #escaping () -> Void) {
....
}
After adding completion blocks to the api calls, your blocks could look like this:
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
apiCallOne {
dispatchGroup.leave()
}
dispatchGroup.enter()
apiCallTwo {
dispatchGroup.leave()
}
...
dispatchGroup.enter()
apiCallN {
dispatchGroup.leave()
}
dispatchGroup.wait(timeout: Constants.timeout)
Keep in mind that the wait statement will block the thread where you call it until all the leave() statements are executed, so be careful that you don't end up with a deadlock.

Swift iOS -Difference in calling a function on the main queue from what's inside the function on the main queue?

Inside my app I have several things that get enabled in the completion handler below but for simplicity purposes I just used one button for this example. I only say this because someone will look at the example and say since there is only 1 button to make things simple use the 1st option which would make sense for 1 button but not several. Also this question can pertain to anything that runs on a different thread then then main thread and not a CaptureSesion.
I have an AVFoundation CaptureSession completion handler that is on a different thread then the main queue. When it runs it updates a button to .isEnabled = true.
I can either
•1. update the function on the main queue directly:
... completionHandler{(
DispatchQueue.main.async { [weak self] in
self?.recordButton.isEnabled = true
}
)}
•2. put the button inside a function and then update that function on the main queue:
... completionHandler{(
DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}
)}
func enableRecordButton(){
recordButton.isEnabled = true
}
•3. update both the function and what's inside the function on the main queue:
... completionHandler{(
DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}
)}
func enableRecordButton(){
DispatchQueue.main.async { [weak self] in
self?.recordButton.isEnabled = true
}
}
What's the difference between the 3?
As a rule you should update the UI components only on main thread. Hence
DispatchQueue.main.async { [weak self] in
self?.recordButton.isEnabled = true
}
Makes absolute sense. Because you would like the button to be enabled and it is a UI modification you would want it to be in main thread. So all the statements in the closure of DispatchQueue.main.async will be executed on main thread.
Where as in
DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}
The method enableRecordButton will be executed on main thread. That means all the statements in enableRecordButton method and all the subsequent methods it calls will be executed on main thread.
In your case, if all that you wanna achieve is enabling a button than putting it in a separate function would not make much sense. Only benefit that you get is that you would be able to enable the button by calling enableRecordButton function from wherever you want rather than repeating same statement again n again.
Finally
... completionHandler{(
DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}
)}
func enableRecordButton(){
DispatchQueue.main.async { [weak self] in
self?.recordButton.isEnabled = true
}
}
This in your case makes no sense. Statement
DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}
will already ensure that all statements in enableRecordButton would execute on main thread so adding DispatchQueue.main.async in enableRecordButton makes no sense.
func enableRecordButton(){
DispatchQueue.main.async { [weak self] in
self?.recordButton.isEnabled = true
}
}
This might come handy only if you have multiple points in your code from where you might call enableRecordButton and might call it on non-main thread in that case you can safely remove
DispatchQueue.main.async { [weak self] in
self?.enableRecordButton()
}
and simply call self?.enableRecordButton() in your completion handler and DispatchQueue.main.async in enableRecordButton function will ensure that all the statements will be executed on main thread.
Hope it helps
I believe adding DispatchQueue.main.async { } again in the function is useless because you are already on the main thread, which you need to update the user interface. Suppose you have certain task which you need to schedule after update of button you can add another DispatchQueue.main.async { } to put it in serial queue which will be executed after updation of button. But in every case updations will be done on main thread only and those will be serial.
Technically, all three would achieve the same result. It's more about code organization, maintainability, and readability.
For #1, you're likely doing business logic in your completion handler that has nothing to do with updating UI. It'd be cleaner to move the UI updating to its own method, say, in the UIViewController whose view owns that button (and the other UI controls you mentioned). Benefits of doing so are that you can enable your UI controls via the same call from elsewhere in your app, and you can write tests against it. You can also better test your business logic of this completion handler, if you had a way to redirect the UI update call (such as via a protocol, to a mock).
And #3 is a waste, in that you're wasting a yield, adding a task to main thread, just to ensure the UI code will be on the main thread. You should instead organize your classes and code to always have a good understanding of what thread they're to be called from. If you're writing a public method in an SDK, you can certainly check the thread and assert if caller calls on a thread other than you expect, but that's about the only time I'd bother with that.
So my answer is #2 is the best.

strange behaviours viewDidAppear not called

I call a function to open a subview from my parent view.
I see that viewDidLoad is called. In my viewDidLoad is no code.
The strange behaviour is, that sometimes viewDidAppear: is not called even I have not change any code.
Of course there can be a lot of reasons, but I do not know how to debug. I am looking for a way to find out where the process hangs at the time when viewDidLoad is finished.
Does anyone have a hint for me or does anyone know what could be the problem? I mean sometimes it works sometime not. Is viewDidAppear: depending on the parentsview or is there something wrong in the subview?
func showSubview() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("Subview") as! SubViewController
self.presentViewController(vc, animated: true, completion: nil)
//self.showViewController(vc, sender: self);
}
UPDATE
I am calling the showSubview from a async task , I guess this is not good. What do you think and how shall I do it better ? Is there any complition handler with dispatch_async ?
dispatch_async(backgroundQueue, {
dosomething_long();
showSubview();
})
UPDATE 2
The problem is, that I am opening the subview from a background task. I should not do this. But the question is how can I call the subview when the background task is finished. Is there any completion handler for dispatch_async call ?
SOLVED WITH HELP OF GANDALF
I am stupid :-) I should call the subview in the main thread:
dispatch_async(backgroundQueue, {
dosomething_long();
dispatch_async(dispatch_get_main_queue(), {
showsubview();
});
})
The very obvious reason for why program counter may not go inside methods is that app could be running in the release mode.
Now as we can see after updated question that this is not the reason and there is a UI operation happening at background queue.
As per iOS development guidelines all UI operation should happen on main thread, you should try executing that on main thread instead of a background thread.
Try the below code:
dispatch_async(backgroundQueue, {
dosomething_long();
dispatch_async(dispatch_get_main_queue(), {
showsubview();
});
})
The question is; is the view created before? Is it caused when you reopen your app from background?
You can check the lifecycle from here and I think your problem is occurred because of that.
iOS 7 - Difference between viewDidLoad and viewDidAppear

My iOS app freezes but no error appears

Does any body know what I need to check if app freezes after some time? I mean, I can see the app in the iPhone screen but no view responds.
I did some google and i found that, i've blocked the main thread somehow.
But my question is how to identify which method causes blocking of main thread? is there any way to identify?
Launch your app and wait for it to freeze. Then press the "pause" button in Xcode. The left pane should show you what method is currently running.
Generally, it is highly recommended to perform on the main thread all animations method and interface manipulation, and to put in background tasks like download data from your server, etc...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//here everything you want to perform in background
dispatch_async(dispatch_get_main_queue(), ^{
//call back to main queue to update user interface
});
});
Source : http://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks
Set a break point from where the freeze occurs and find which line cause that.
Chances may be,Loading of large data,disable the controls,overload in main thread,Just find out where that occurs using breakpoints and rectify based on that.
I believe it should be possible to periodically check to see if the main thread is blocked or frozen. You could create an object to do this like so:
final class FreezeObserver {
private let frequencySeconds: Double = 10
private let acceptableFreezeLength: Double = 0.5
func start() {
DispatchQueue.global(qos: .background).async {
let timer = Timer(timeInterval: self.frequencySeconds, repeats: true) { _ in
var isFrozen = true
DispatchQueue.main.async {
isFrozen = false
}
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + self.acceptableFreezeLength) {
guard isFrozen else { return }
print("your app is frozen, so crash or whatever")
}
}
let runLoop = RunLoop.current
runLoop.add(timer, forMode: .default)
runLoop.run()
}
}
}
Update October 2021:
Sentry now offers freeze observation, if you don't wanna roll this yourself.
I reached an error similar to this, but it was for different reasons. I had a button that performed a segue to another ViewController that contained a TableView, but it looked like the application froze whenever the segue was performed.
My issue was that I was infinitely calling reloadData() due to a couple of didSet observers in one of my variables. Once I relocated this call elsewhere, the issue was fixed.
Most Of the Time this happened to me when a design change is being called for INFINITE time. Which function can do that? well it is this one:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
Solution is to add condition where the function inside of viewDidLayoutSubviews get calls only 1 time.
It could be that another view is not properly dismissed and it's blocking user interaction! Check the UI Debugger, and look at the top layer, to see if there is any strange thing there.

Resources