NSUrlSession updating UI Control only the first time
NSUrlSession updating UI Control only the first time
我有一个 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);
});
}
}
}
问题是您在 NSURLSessionDelegate
对象中持有对 NSURLSession
实例的强引用,并且从未使会话无效。根据Apple's Documentation
The session object keeps a strong reference to this delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session, your app leaks memory until it exits.
您应该在某个时候使会话无效。另外,您似乎在这里创建了一个强大的参考循环:
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();
}
}
}
在你的视图控制器中 class:
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 开发相比,弱引用的使用方式可能是错误的。希望这对您有所帮助!
我有一个 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);
});
}
}
}
问题是您在 NSURLSessionDelegate
对象中持有对 NSURLSession
实例的强引用,并且从未使会话无效。根据Apple's Documentation
The session object keeps a strong reference to this delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session, your app leaks memory until it exits.
您应该在某个时候使会话无效。另外,您似乎在这里创建了一个强大的参考循环:
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();
}
}
}
在你的视图控制器中 class:
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 开发相比,弱引用的使用方式可能是错误的。希望这对您有所帮助!