Post

Replies

Boosts

Views

Activity

Why does `DisclosureGroup` retains object references when content is hidden?
I'm displaying file structure using DisclosureGroup. And I encountered a memory leak problem. Code Node Node represents a file or a folder. isExpanded is used to indicate if the child nodes are visible. If true, it will find its child nodes, which are set into children. If false, it will clear children for releasing references. class Node: ObservableObject, Identifiable, Hashable, CustomStringConvertible { // ... @Published var name: String @Published var children: [Node]? @Published var isExpanded = false { willSet { if self.isFile { // This node represents a file. // It does not have any children. return } if newValue { if children?.count == 0 { DispatchQueue.main.async { // get child nodes self.children = childrenOf(self.url) } } } else { if children?.count != 0 { DispatchQueue.main.async { // collapse child nodes self.children?.forEach { child in child.isExpanded = false } // clear children when this node is collapsed self.children = [] } } } } } init(/*...*/) { // ... print("init \(name)") } deinit { // ... print("deinit \(name)") } // ... } For convenience, I print some messages when initializing Node and deinitializing Node. TreeNode TreeNode displays Node using DisclosureGroup. struct TreeNode: View { @ObservedObject var parent: Node @ObservedObject var node: Node var body: some View { if node.isFile { Text(node.name) } else { DisclosureGroup( isExpanded: $node.isExpanded, content: { if node.isExpanded { ForEach(node.children ?? []) { child in TreeNode(parent: node, node: child) } } }, label: { FolderNodeView(node: node) } ) } } } struct FolderNodeView: View { @ObservedObject var node: Node var body: some View { Label( title: { Text(node.name) }, icon: { Image(systemName: "folder.fill") } ) } } I use if node.isExpanded for lazy loading. When node.isExpanded is true, it will show node's children and print initialization messages. Otherwise, it will hide child nodes and print deinitialization messages. But unexpectedly it does not print any deinitialization messages when the node is collapsed. This indicates that it retains references and therefore these Node objects still exists in memory causing memory leak. Demo When the node is expanded, its child nodes will be displayed after loading is completed. The code works correctly. Then I collapsed the node, it didn't print any deinitialization messages. And when I expanded it again, it initialized new nodes and deinitialized the old nodes at this time. Deinitialization seems to be delayed. So I guess TreeNode retains references when content is hidden. Then I deleted TreeNode in ForEach. DisclosureGroup( isExpanded: $node.isExpanded, content: { if node.isExpanded { ForEach(node.children ?? []) { child in // TreeNode(parent: node, node: child) } } }, label: { FolderNodeView(node: node) } ) It cannot display the child nodes. But it released reference correctly. So the code works expectedly. After that, I tried to replace TreeNode with Text or Label. I found that none of them released references immediately when I collapsed the node. Why did this happen? Any idea how to fix it?
0
0
447
Feb ’24
Why is the image captured by `SCScreenshotManager.captureImage` so blurry?
I am using ScreenCaptureKit to create a screenshot software, but I found that the screenshot captured by the new API, SCScreenshotManager.captureImage, is very blurry. This is my screenshot. It is so blurry. But I hope it's like this. My code is as follows. func captureScreen(windows: [SCWindow], display: SCDisplay) async throws -> CGImage? { let availableWindows = windows.filter { window in Bundle.main.bundleIdentifier != window.owningApplication?.bundleIdentifier } let filter = SCContentFilter(display: display, including: availableWindows) if #available(macOS 14.0, *) { let image = try? await SCScreenshotManager.captureImage( contentFilter: filter, configuration: SCStreamConfiguration.defaultConfig( width: display.width, height: display.height ) ) return image } else { return nil } } extension SCStreamConfiguration { static func defaultConfig(width: Int, height: Int) -> SCStreamConfiguration { let config = SCStreamConfiguration() config.width = width config.height = height config.showsCursor = false if #available(macOS 14.0, *) { config.captureResolution = .best } return config } }
1
0
869
Oct ’23