NSUrlSession обновляет UI Control только в первый раз - PullRequest
0 голосов
/ 15 ноября 2018

У меня есть 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);
            });
        }
    }

}

}

1 Ответ

0 голосов
/ 15 ноября 2018

Проблема в том, что вы держите строгую ссылку на экземпляр NSURLSession в своем объекте NSURLSessionDelegate и никогда не отменяете сеанс. Согласно Документация Apple

Объект сеанса сохраняет строгую ссылку на этот делегат до тех пор, пока ваше приложение не закроет или явно не сделает недействительным сеанс. Если вы не аннулируете сеанс, ваше приложение будет терять память до тех пор, пока оно не выйдет.

Вы должны сделать недействительным сеанс в какой-то момент. Также похоже, что вы создаете сильный ссылочный цикл здесь:

WAReportController имеет сильную ссылку на NSUrlSession; NSURLSession имеет сильную ссылку на ReportDownloadDelegate; и 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. Надеюсь, это поможет!

...