提问者:小点点

通过SwiftUI使用UIDocumentPickerViewController的Memleak


该应用程序是为iOS构建的。 我想让用户从它的目录中选择一个文件,并获得所选文件的url。 Apple建议UIDocumentPickerViewController解决此问题。 我将它包装在UIViewControllerRepresentable中,因此它是第一个位置,我可能会出错

import SwiftUI
import MobileCoreServices

struct DocumentPickerView: UIViewControllerRepresentable {
    func makeCoordinator() -> Coordinator {
        return DocumentPickerView.Coordinator()
    }

    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
        let picker = UIDocumentPickerViewController(documentTypes: [String(kUTTypeItem)], in: .import)
        picker.allowsMultipleSelection = false
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {
    }

    class Coordinator: NSObject, UIDocumentPickerDelegate {
        func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
            print("url: \(urls[0].absoluteString )")
        }
    }
}

struct ContentView: View {
    @State var showPicker = false

    var body: some View {
        Button(action: {
            self.showPicker.toggle()
        }, label: {
            Text("Push me")
        })
        .sheet(isPresented: self.$showPicker) {
            DocumentPickerView()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

我认为存在问题的原因是Xcode仪器显示内存泄漏,这里是跟踪:

 32 Bytes  100.0%   1       start
 32 Bytes  100.0%   1        main
 32 Bytes  100.0%   1         UIApplicationMain
 32 Bytes  100.0%   1          GSEventRunModal
 32 Bytes  100.0%   1           CFRunLoopRunSpecific
 32 Bytes  100.0%   1            __CFRunLoopRun
 32 Bytes  100.0%   1             __CFRunLoopDoObservers
 32 Bytes  100.0%   1              __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
 32 Bytes  100.0%   1               _afterCACommitHandler
 32 Bytes  100.0%   1                _cleanUpAfterCAFlushAndRunDeferredBlocks
 32 Bytes  100.0%   1                 _runAfterCACommitDeferredBlocks
 32 Bytes  100.0%   1                  __56-[UIPresentationController runTransitionForCurrentState]_block_invoke.478
 32 Bytes  100.0%   1                   _UIViewControllerTransitioningRunCustomTransition
 32 Bytes  100.0%   1                    +[UIView(Animation) _setAlongsideAnimations:toRunByEndOfBlock:]
 32 Bytes  100.0%   1                     ___UIViewControllerTransitioningRunCustomTransition_block_invoke.648
 32 Bytes  100.0%   1                      +[UIInputResponderController _pinInputViewsForInputResponderController:onBehalfOfResponder:duringBlock:]
 32 Bytes  100.0%   1                       ___UIViewControllerTransitioningRunCustomTransition_block_invoke_2
 32 Bytes  100.0%   1                        -[UIViewControllerBuiltinTransitionViewAnimator animateTransition:]
 32 Bytes  100.0%   1                         -[UITransitionView transition:fromView:toView:removeFromView:]
 32 Bytes  100.0%   1                          -[UITransitionView _startTransition:withDuration:]
 32 Bytes  100.0%   1                           +[UIView(UIViewAnimationWithBlocks) conditionallyAnimate:withAnimation:layout:completion:]
 32 Bytes  100.0%   1                            __50-[UITransitionView _startTransition:withDuration:]_block_invoke.169
 32 Bytes  100.0%   1                             +[UIView(UIViewAnimationWithBlocks) animateWithDuration:delay:options:animations:completion:]
 32 Bytes  100.0%   1                              +[UIView(UIViewAnimationWithBlocks) _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:]
 32 Bytes  100.0%   1                               +[UIViewAnimationState popAnimationState]
 32 Bytes  100.0%   1                                -[UIViewAnimationState pop]
 32 Bytes  100.0%   1                                 -[UIViewAnimationState _runAlongsideAnimations]
 32 Bytes  100.0%   1                                  __63+[UIView(Animation) _setAlongsideAnimations:toRunByEndOfBlock:]_block_invoke
 32 Bytes  100.0%   1                                   -[_UIViewControllerTransitionContext __runAlongsideAnimations]
 32 Bytes  100.0%   1                                    -[_UIViewControllerTransitionCoordinator _applyBlocks:releaseBlocks:]
 32 Bytes  100.0%   1                                     __54-[_UISheetPresentationController transitionWillBegin:]_block_invoke.345
 32 Bytes  100.0%   1                                      +[UIView(Animation) performWithoutAnimation:]
 32 Bytes  100.0%   1                                       __54-[_UISheetPresentationController transitionWillBegin:]_block_invoke_2
 32 Bytes  100.0%   1                                        -[_UISheetLayoutInfo _layout]
 32 Bytes  100.0%   1                                         -[_UISheetPresentationController _sheetLayoutInfoLayout:]
 32 Bytes  100.0%   1                                          -[UIView(Hierarchy) layoutBelowIfNeeded]
 32 Bytes  100.0%   1                                           CA::Layer::layout_if_needed(CA::Transaction*)
 32 Bytes  100.0%   1                                            -[CALayer layoutSublayers]
 32 Bytes  100.0%   1                                             -[UIView(CALayerDelegate) layoutSublayersOfLayer:]
 32 Bytes  100.0%   1                                              @objc _UIHostingView.layoutSubviews()
 32 Bytes  100.0%   1                                               _UIHostingView.layoutSubviews()
 32 Bytes  100.0%   1                                                ViewRendererHost.render(interval:updateDisplayList:)
 32 Bytes  100.0%   1                                                 closure #1 in ViewRendererHost.render(interval:updateDisplayList:)
 32 Bytes  100.0%   1                                                  closure #1 in closure #1 in ViewRendererHost.render(interval:updateDisplayList:)
 32 Bytes  100.0%   1                                                   ViewGraph.updateOutputs(at:)
 32 Bytes  100.0%   1                                                    closure #1 in ViewGraph.updateOutputs(at:)
 32 Bytes  100.0%   1                                                     ViewGraph.runTransaction(in:)
 32 Bytes  100.0%   1                                                      AG::Subgraph::update(unsigned int)
 32 Bytes  100.0%   1                                                       AG::Graph::update_attribute(unsigned int, bool)
 32 Bytes  100.0%   1                                                        AG::Graph::UpdateStack::update()
 32 Bytes  100.0%   1                                                         partial apply
 32 Bytes  100.0%   1                                                          protocol witness for static UntypedAttribute._update(_:graph:attribute:) in conformance PlatformViewChild<A>
 32 Bytes  100.0%   1                                                           PlatformViewChild.update(context:)
 32 Bytes  100.0%   1                                                            closure #1 in PlatformViewChild.update(context:)
 32 Bytes  100.0%   1                                                             swift_weakInit
 32 Bytes  100.0%   1                                                              swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1> >::formWeakReference()
 32 Bytes  100.0%   1                                                               operator new(unsigned long)
 32 Bytes  100.0%   1                                                                malloc

通过在分配时进行多个调用,我有这样的实体丢失了它们的内存:

Category                                            Persistent  # Persistent    # Transient Total Bytes
UIDocumentBrowserViewController                     22,50 KiB       15              0       22,50 KiB
DOCRemoteViewController                             22,50 KiB       15              0       22,50 KiB
_UIResilientRemoteViewContainerViewController       13,36 KiB       15              0       13,36 KiB
_UIRemoteView                                       6,80 KiB        15              0       6,80 KiB
_UISizeTrackingView                                 6,56 KiB        15              0       6,56 KiB
DOCRemoteBarButtonTrackingView                      5,62 KiB        15              0       5,62 KiB
DOCConfiguration                                    2,58 KiB        15              0       2,58 KiB
DOCRemoteContext                                    1,88 KiB        15              0       1,88 KiB
_UIViewServiceInterface                             1,41 KiB        15              0       1,41 KiB
__NSXPCInterfaceProxy__NSExtensionContextVending    1,17 KiB        15              0       1,17 KiB
UISystemDefaultTextInputAssistantItem               1,17 KiB        15              0       1,17 KiB
UIKeyboardBIUImageGenerator                         960 Bytes       15              0       960 Bytes
_UIViewServiceReplyControlProxy                     720 Bytes       15              0       720 Bytes
Swift.__SwiftDeferredNSArray                        480 Bytes       15              0       480 Bytes
DOCRemoteBarButton                                  480 Bytes       15              0       480 Bytes
NSWeakObjectValue                                   480 Bytes       15              0       480 Bytes
_UIRemoteViewService                                480 Bytes       15              0       480 Bytes
_UIViewServiceImplicitAnimationEncodingProxy        480 Bytes       15              0       480 Bytes
DOCWeakProxy                                        240 Bytes       15              0       240 Bytes

共1个答案

匿名用户

这绝对是苹果的bug,所以值得向他们提交反馈。

同时,这里有一个适用于大多数用例的安全解决方案。 使用Xcode 11.4/iOS 13.4进行测试。 其思想是使用共享文档拾取器实例,并在每次调用时重新配置它。

struct DocumentPickerView: UIViewControllerRepresentable {
    static let picker = UIDocumentPickerViewController(documentTypes: [String(kUTTypeItem)], in: .import)

    func makeUIViewController(context: Context) -> UIDocumentPickerViewController {
        Self.picker.allowsMultipleSelection = false
        Self.picker.delegate = context.coordinator
        return Self.picker
    }

    // ... other code no changes