【问题标题】:NSUrlSession updating UI Control only the first timeNSUrlSession 仅第一次更新 UI 控件
【发布时间】:2020-05-08 02:39:06
【问题描述】:

我有一个 ViewController,我从中下载 pdf 文档。

在下载时,我显示了一个 UIAlertController,其中包含一个 UIProgressView,我正在更新下载进度。第一次一切正常。

现在下载后,我按下导航栏中的后退按钮转到上一个 ViewController。然后当我再次前进到下载控制器并尝试再次下载时,进度没有更新,UIAlertController 也没有关闭。

问题只是当我回到以前的控制器时。如果我留在同一个控制器中并再次尝试下载,它可以工作。

public partial class WAReportController : UITableViewController
{

    const string Identifier = "com.gch.DownloadDocument.BackgroundSession";

    public NSUrlSessionDownloadTask downloadTask;

    public NSUrlSession session;

    public void DownloadReport()
    {
        if (session == null)
            session = InitBackgroundSession();

        using (var url = NSUrl.FromString(RestApiPaths.REPORT_DOWNLOAD_PATH))
        using (var request = new NSMutableUrlRequest(url)) {
            request.Headers = CommonUtils.GetHeaders();

            downloadTask = session.CreateDownloadTask(request);
            downloadTask.Resume();
            ShowAlert();
        }
    }

    public NSUrlSession InitBackgroundSession()
    {
        Console.WriteLine("InitBackgroundSession");
        using (var configuration = NSUrlSessionConfiguration.CreateBackgroundSessionConfiguration(Identifier)) {
            return NSUrlSession.FromConfiguration(configuration, (INSUrlSessionDelegate)new ReportDownloadDelegate(this), new NSOperationQueue());
        }
    }

    public class ReportDownloadDelegate : NSUrlSessionDownloadDelegate
    {

        private WAReportController _vc;

        public ReportDownloadDelegate(WAReportController vc)
        {
            _vc = vc;
        }

        public override void DidWriteData(NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
        {
            float progress = totalBytesWritten / (float)totalBytesExpectedToWrite;
            Console.WriteLine(string.Format("progress: {0}", progress));

            DispatchQueue.MainQueue.DispatchAsync(() => {
                _vc.UpdateDownloadProgress(progress); // updates successfully only the first time
            });
        }

        public override void DidFinishDownloading(NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location)
        {
            _vc.DismissDownloadProgressAlert(); 
        }

    }

    UIAlertController downloadProgressAlert;
    UIProgressView downloadProgress;

    void ShowAlert()
    {
        downloadProgressAlert = UIAlertController.Create("Downloading", "\n\n", UIAlertControllerStyle.Alert);
        downloadProgressAlert.AddAction(UIAlertAction.Create("Cancel", UIAlertActionStyle.Cancel, (action) => {
            downloadTask.Cancel();
        }));

        PresentViewController(downloadProgressAlert, true, () => {
            nfloat margin = 8.0f;
            var rect = new CGRect(margin, 72.0f, downloadProgressAlert.View.Frame.Width - margin * 2.0f, 2.0f);
            downloadProgress = new UIProgressView(rect) {
                Progress = 0.0f,
                TintColor = UIColor.Blue
            };
            downloadProgressAlert.View.AddSubview(downloadProgress);
        });
    }

    public void UpdateDownloadProgress(float progress)
    {
        if (downloadProgress != null) {
            downloadProgress.Progress = 50;
        }
    }

    public void DismissDownloadProgressAlert()
    {
        if (downloadProgressAlert != null) {
            InvokeOnMainThread(() => {
                downloadProgressAlert.DismissViewController(false, null);
            });
        }
    }

}

【问题讨论】:

    标签: ios xamarin.ios nsurlsession


    【解决方案1】:

    问题是您在NSURLSessionDelegate 对象中持有对NSURLSession 实例的强引用,并且永远不会使会话无效。根据Apple's Documentation

    会话对象保持对此委托的强引用,直到您的应用退出或显式使会话无效。如果您不使会话无效,则您的应用会泄漏内存,直到退出。

    您应该在某个时候使会话无效。此外,您似乎在这里创建了一个强大的参考循环:

    WAReportControllerNSUrlSession有强引用; NSURLSessionReportDownloadDelegate 有强引用;并且ReportDownloadDelegate 强烈引用WAReportController;。你看到这里的问题了吗?

    您应该尽可能使用弱引用。试试这样的:

    public class ReportDownloadDelegate : NSUrlSessionDownloadDelegate
    {
    
        private WeakReference<WAReportController> _vc;
    
        public ReportDownloadDelegate(WAReportController vc)
        {
            _vc = vc;
        }
    
        public override void DidWriteData(NSUrlSession session, NSUrlSessionDownloadTask downloadTask, long bytesWritten, long totalBytesWritten, long totalBytesExpectedToWrite)
        {
            if (_vc != null) {
                 float progress = totalBytesWritten / (float)totalBytesExpectedToWrite;
                Console.WriteLine(string.Format("progress: {0}", progress));
    
                DispatchQueue.MainQueue.DispatchAsync(() => {
                    _vc.UpdateDownloadProgress(progress); // updates successfully only the first time
                });
            }
        }
    
        public override void DidFinishDownloading(NSUrlSession session, NSUrlSessionDownloadTask downloadTask, NSUrl location)
        {
            if (_vc != null) {
                _vc.DismissDownloadProgressAlert();
            }
        }
    
    }
    

    在你的视图控制器类中:

    public void DismissDownloadProgressAlert()
    {
        if (downloadProgressAlert != null) {
            InvokeOnMainThread(() => {
                downloadProgressAlert.DismissViewController(false, null);
            });
        }
        session.InvalidateAndCancel() // I didn't type in an IDE so not sure if this is the exact method signature.
        session = null;
    }
    

    这样,一旦下载任务完成,您将关闭进度警报并使会话无效。 NSURLSession 将依次释放 WAReportController 实例。现在,您不必使DismissDownloadProgressAlert() 方法中的会话无效。但是当您使用完会话后,您应该使会话无效。可能是当您关闭视图控制器或您认为合适的时候。我这里只是举个例子。

    现在这一切都基于 Apple 的文档和我对 iOS 内存管理的了解。我从未在 Xamarin 上工作过,并且与原生 iOS 开发相比,使用弱引用的方式可能是错误的。希望这会有所帮助!

    【讨论】:

    猜你喜欢
    • 2022-11-11
    • 1970-01-01
    • 2017-08-30
    • 1970-01-01
    • 1970-01-01
    • 2014-07-22
    • 1970-01-01
    • 1970-01-01
    • 2012-08-17
    相关资源
    最近更新 更多