Swift2 Huge delay in many UIEvents - ios

I usually don't ask questions here, but this makes no sense.
After I load and parse a Json (with swiftyjson) I call this methods:
//remove a loadin screen
uiviewLoader?.removeFromSuperview()
print("uiview loader removed")
self.collectionView.reloadData()
print("refresh table")
And thats its.
The thing is, it prints boths msgs (both in emulator and in physical device) but it takes like 15 seconds to see the effects of this lines.
Do I need an extra call?? I have the latest xcode..
Thanks for any tips or advices.
****** More Info ******
I load a json in another class. when completed loading and parsing, it calls a method in my viewController
func jsonParseado(res:Bool){
print("json result = \(res)")
if res {
uiviewLoader?.removeFromSuperView()
print("loading uiview removed from superview")
self.collectionView.reloadData()
print("refresh data with new values")
...
else{
...
}
And after I see the results prints, it takes about 10-15 seconds to remove the loading view and show the collection controller.

Ok. luk2302 u were right.
the problem is that Im no longer in the main thread.
I solve the problem with
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.reloadData()
self.uiviewLoader?.removeFromSuperview()
print("reload in main thread..")
}
//this actually works!!
I did load the json with swifty json, wich uses another thread to do so.
but im not sure when I left the main thread...
Thank you all.

Related

Swift: Crash when loading a Table many times

I have following issue:
In my App, you have online recipes now imagine a TabViewController. On the first two pages of this TabViewController you have a view displaying recipes stored on the Realtime Database of Firebase. On the third one you have a simple view with some buttons, but no Firebase used and imported. The problem now is, when I slam the Bottom Bar multiple times and therefore switch the TabViewController multiple times in a second the app crashes. This probably because of Firebase reloading everytime since there is a TabViewController change, maybe resulting in a overload.
Now I get following error:
Fatal error: Index out of range: file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
2020-05-22 16:44:28.057640+0200 GestureCook[10451:3103426] Fatal error: Index out of range: file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.2.25.8/swift/stdlib/public/core/ContiguousArrayBuffer.swift, line 444
It highlights this code let recipe = myRecipes[indexPath.row] with the index out of range. Now how can I either reduce load on the server or avoid this error?
The reason why there is an increased load is probably since I have to fetch multiple recipes from different locations at once like this simplified example:
// dict is a list of recipe IDs
// And the GetRecipeService.getRecipe is a service which gets a recipe using a .observeSingleEvent (this causes these requests)
for child in dict {
GetRecipeService.getRecipe(recipeUID: child.key) { recipe in
self.myRecipes.append(recipe ?? [String:Any]())
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
How could I reduce load? Is there something as multipath udpates in Firebase, but just as a get, so I don't have to load 10-20 recipes with a .observeSingleEvent?
First of all the DispatchQueue block is at the wrong place. It must be inside the closure
GetRecipeService.getRecipe(recipeUID: child.key) { recipe in
self.myRecipes.append(recipe ?? [String:Any]())
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
To manage multiple asynchronous requests in a loop there is an API: DispatchGroup
let group = DispatchGroup()
for child in dict {
group.enter()
GetRecipeService.getRecipe(recipeUID: child.key) { recipe in
self.myRecipes.append(recipe ?? [String:Any]())
group.leave()
}
}
group.notify(queue: .main) {
self.tableView.reloadData()
}

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.

Keep my function from blocking functionality while getting info

I have a function with a completion handler that works to download videos from youtube's API.
On the first run, it works great. On subsequent runs, since the videos are stored in the device, and shown before the array of videos is updated, I would like for the user to be able to interact with the table, while it is updated (if required). However, while the information is being downloaded, the interaction with the table or the app is blocked.
I guess this has something to do with Grand Central Dispatch, but I don't know how to use it.
Networking().getPlaylists() { (result: String) -> () in
self.activityView.removeFromSuperview()
print("should reload data")
self.tableView.reloadData()
}
}
Could somebody give me some pointers?
Ideally your network code would execute on a background thread:
func getPlaylists() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//Do network stuff
})
}
but also before you call your completion, make sure you flip back to the main thread before you do UI logic like updating your tableview:
func getPlaylists() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
//Do network stuff
dispatch_async(dispatch_get_main_queue()) {
completion(results)
}
})
}
It was actually not Alamofire that was blocking my UI (it is actually an async function), but it was SwiftyJSON while I was parsing. I solved it by dispatching_async but over the parsing section and not the rest of the function!
See more here: Alamofire http json request block ui
Thanks!

Why is popViewControllerAnimated taking so long to run?

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)
}

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