I'm working on a screen in my app that allows users to leave comments. Additionally, they can view previous comments by tapping a button. The most recent comments are always at the end of the comments list, and the oldest at the top.
When the view more
button is tapped, I fetch 10 previous comments from the database. My fetching code looks like this:
func fetchMoreComments(for gameId: Int, isLoadingMoreComments: Binding<Bool>) {
guard self.canLoadMoreComments else { return }
APIManager
.CommentsAPI
.getMoreComments(for: gameId, lastCommentId: self.lastCommentId!)
.sink(receiveCompletion: { completion in
print("load more comments completion:", completion)
},
receiveValue: {[weak self] response in
if response.statusCode == 200 {
// self?.comments.reserveCapacity((self?.comments.count)! + response.items.count)
let comments = response.items
if let lastCommentId = comments.map({ $0.id! }).min() {
self?.lastCommentId = lastCommentId
}
self?.comments.insert(contentsOf: comments.sorted(), at: 0)
isLoadingMoreComments.wrappedValue = false
self?.canLoadMoreComments = response.items.count >= 1
}
})
.store(in: &subscriptions)
}
Also, I should mention, when the screen is first loaded, there is another function I use to pull the 3 most recent comments to display on the screen the first time the user loads that particular screen.
The problem
As more comments are loaded, the navigation slows down. What I mean is, if I tap the back button to go back to the previous screen, there is a delay of about 4-5 seconds before I'm actually taken back to that screen. The reverse also has the same problem. It takes about 4-5 seconds to load the screen with the comments.
What I've tried
1) I spent days sorting out memory leaks and cleaning up my code as I tried to find the problem. What I found was that, if I commented out the forloop that reads the array from my view model (the one function I pasted above lives in), then the laggy navigation issue disappears. I've pasted the forloop below. I've tried using .id from my commments object and also .self to improve the performance, but it didn't make a difference.
ForEach(self.liveGameViewModel.comments, id: \.self) { comment in
LiveGameCommentCell(comment: comment)
}
.id(UUID())
2)
I further narrowed things down to a NavigationLink
inside the LiveGameCommentCell
. That navigation link was a link to the commenter's profile screen. I noticed, once I commented out this navigation link, the navigation lag was partially cured. I could load up to 100 comments (10 at a time), then leave the screen (there would still be a few seconds of lag), however, once navigating back into the screen with the comments, the lag would be completely gone.
3) Since I could no longer use the navigation, I decided to use a sheet to present a user's profile screen instead. Here's where I noticed another issue, or maybe just the same issue I've had all along. I would load the screen with the comments, and tap on a commenter's image, and the sheet would present itself with no lag. I'd then dismiss the sheet and load a few more comments and tap another commenter's profile image. I noticed, the more comments I loaded, the longer the sheet would take to present itself.
4) Next, I decided to tweak how many comments are pulled from the database initially. I upped the limit to 100. Without loading more comments, I tapped on a commenter's profile image again, and the lag was there again. The problem is definitely related to the comments.
5)
I tried setting Swift array's reserveCapacity(_:)
to 10 on the initial load of comments, then each time more comments were loaded, I'd set the capacity to the current number of comments in the array + the number received in the response. This didn't make much of a noticable difference.
I can't seem to track down this issue, but I know it's directly related to the number of comments on the screen. The more comments there are on the screen, the longer the sheet takes to present, or the longer it takes to navigate back to the previous screen.
I've refactored my code and fixed memory leaks I encountered. Most of the view models related to each screen only live for the life of that screen. For example, when a screen is dismissed, the view model is deinitialised.
Inside instruments, LiveGameCommentCell
has the highest count and total duration. The count increases as I load more comments.
What could be causing this issue? I use the same type of functionality on other screens and don't have this issue. The only difference is, the other screens are entirely lists which contain foreach loops, whereas this problematic screen, is made up of the 2 images (host, challenger), some game info, such as, a short descriptive sentence, a deadline, comment count, the category, and then underneath all of this, a section for the list of comments and a text view to post new comments.
I even changed things around so that the API response for the comments would provide all of the info I would need for the comments, rather than utilising foreign keys and making multiple requests from the app. Everything that is needed is delivered in the getMostRecentComments
and getMoreComments
requests.
There were a few memory leaks I didn't get around to solving because they are most likely related to something else because they show regardless of visiting the screen that shows comments.
Also, I noticed when I first arrive the screen that shows the comments and tap a commenter's profile image, there are no errors. If I tap the next set of 10 comments that are loaded, there are no errors, but as the lagging increases, I notice an error starts to show when I tap on commenter's images. The error is:
[Common] _BSMachError: port 1c433; (os/kern) invalid capability (0x14) "Unable to insert COPY_SEND"
I'd really appreciate some clues that may help me solve this issue. At present, it's not at all obvious to me what is causing this issue.
Thanks in advance
I had a ForEach loop inside a ScrollView, and not inside a List. Replacing the ScrollView with a list, and wrapping my NavigationLinks inside a LazyView solved the issue.