Issue #722

With Xcode 12, we can fire up Instrument to profile our app. Select SwiftUI template

Screenshot 2020-12-26 at 00 02 03

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

Screenshot 2020-12-25 at 23 59 12

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

Screenshot 2020-12-25 at 23 59 57

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

Screenshot 2020-12-26 at 00 09 51 Screenshot 2020-12-26 at 00 09 01