Issue #722
With Xcode 12, we can fire up Instrument to profile our app. Select SwiftUI template
There are many profiles in that template, I find SwiftUI and Time Profile very useful. Here’s the profile I run for my app PastePal
SwiftUI View Body
This shows how many instance of View with body invocation are there, both for SwiftUI views and our app views
Taking a look at SwiftUI profile, it shows that ClipboardCell
is taking most of the time, here over 7 seconds
Time Profiler
This shows how much time was spent in each functions and call stack.
Then we drill down to Time Profiler, it shows that NSSharingService.submenu
accounts for 75% of performance issue
With these instruments, I found out that the NSSharingService context menu I added has poor performance.
This below method is used every time Menu
is asked, which causes a significant overload on main thread, resulting in noticeable laggy scrolling
extension NSSharingService {
static func submenu(text: String) -> some View {
return Menu(
content: {
ForEach(NSSharingService.sharingServices(forItems: [""])), id: \.title) { item in
Button(action: { item.perform(withItems: [string]) }) {
Image(nsImage: item.image)
Text(item.title)
}
}
},
label: {
Text("Share")
Image(systemName: SFSymbol.squareAndArrowUp.rawValue)
}
)
}
}
The reason is that NSSharingService.sharingServices
requires quite some time to get sharing services. A quick fix is to cache the items using static
variable. In Swift, static variable is like lazy
attribute, it is computed only once on first asked
private static let items = NSSharingService.sharingServices(forItems: [""])
There are more to optimize in my case, for example calculation of image and dominant colors. But we should only optimize when seeing real performance issue and after proper instruments
Fix and instrument again
After the fix, the time reduces from over 7 seconds to just less than 200ms