iOS 16真机NET MAUI应用崩溃:未手动释放Observer对象
.NET MAUI iOS端相册加载崩溃问题解决
问题现象
在iOS 16的iPhone 8真机热重启测试场景下,页面每次OnAppearing都会执行相册图片加载方法,但应用最小化后返回触发OnAppearing时,应用直接终止,报错信息如下:
2022-11-22 13:58:31.287 Xamarin.PreBuilt.iOS[1018:30754] Warning: observer object was not disposed manually with Dispose()
Android端实现无此异常,图片可正常添加到ObservableCollection并展示。
核心问题分析
报错直接指向未手动释放观察者对象,结合代码细节排查出三个关键点:
PHCachingImageManager作为iOS相册缓存管理类,属于NSObject子类,必须手动调用Dispose()释放资源,否则会引发内存泄漏和观察者未清理问题。- 页面重复触发加载时,旧的
PHCachingImageManager实例未被释放,叠加新实例导致资源冲突。 - 页面消失时未终止正在进行的相册加载任务,后台任务持有已销毁页面的引用,触发崩溃。
修复方案
1. 让MediaService实现资源释放逻辑
修改MediaService继承IDisposable接口,手动管理PHCachingImageManager和任务取消令牌:
public class MediaService : IDisposable { bool requestStop = false; bool _isLoading = false; public bool IsLoading => _isLoading; public event EventHandler<MediaEventArgs> OnMediaAssetLoaded; #if IOS private PHCachingImageManager _imageManager; private CancellationTokenSource _cts; // 用于取消正在进行的加载任务 #endif // ... 原有RequestPermissionAsync等代码保持不变 ... public void Dispose() { #if IOS requestStop = true; _cts?.Cancel(); _cts?.Dispose(); // 停止缓存并释放图片管理器资源 _imageManager?.StopCaching(); _imageManager?.Dispose(); #endif } public async Task<IList<MediaAsset>> RetrieveMediaAssetsAsync(CancellationToken? cancelToken = null) { #if IOS requestStop = false; // 先取消之前未完成的任务 _cts?.Cancel(); _cts?.Dispose(); _cts = new CancellationTokenSource(); // 合并外部传入令牌与内部取消令牌 var combinedToken = CancellationTokenSource.CreateLinkedTokenSource( cancelToken ?? CancellationToken.None, _cts.Token).Token; var taskCompletionSource = new TaskCompletionSource<IList<MediaAsset>>(); combinedToken.Register(() => { requestStop = true; taskCompletionSource.TrySetCanceled(); }); _isLoading = true; var task = LoadMediaAsync(); var completedTask = await Task.WhenAny(task, taskCompletionSource.Task); _isLoading = false; return await completedTask; #endif // Android端原有代码保持不变 List<MediaAsset> mediaAssets = new List<MediaAsset>(); return mediaAssets; } async Task<IList<MediaAsset>> LoadMediaAsync() { IList<MediaAsset> assets = new List<MediaAsset>(); #if IOS // 初始化新的图片管理器,旧实例已在Dispose中释放 _imageManager = new PHCachingImageManager(); var hasPermission = await RequestPermissionAsync(); if (hasPermission) { await Task.Run(async () => { // ... 原有缩略图配置、资源获取逻辑保持不变 ... }); _imageManager.StopCaching(); } #endif return assets; } }
2. 页面消失时释放ViewModel资源
让ViewModel实现IDisposable,绑定Disappearing事件触发资源清理:
public class YourViewModel : INotifyPropertyChanged, IDisposable { private MediaService _mediaService; public ObservableCollection<MediaAsset> MediaAssets { get; set; } = new(); public YourViewModel() { _mediaService = new MediaService(); _mediaService.OnMediaAssetLoaded += MediaService_OnMediaAssetLoaded; } [RelayCommand] public async void LoadNewPictures() { if (_mediaService.IsLoading) return; // 防止重复触发加载 var fetch = await _mediaService.RetrieveMediaAssetsAsync(); // 处理加载结果到ObservableCollection } [RelayCommand] public void DisatachCommand() { Dispose(); } private void MediaService_OnMediaAssetLoaded(object sender, MediaEventArgs e) { // 在主线程更新UI集合 MainThread.BeginInvokeOnMainThread(() => { MediaAssets.Add(e.Asset); }); } public void Dispose() { _mediaService.OnMediaAssetLoaded -= MediaService_OnMediaAssetLoaded; _mediaService.Dispose(); } }
3. 优化异步回调的同步逻辑
原有图片请求使用异步回调,可能导致asset未完全初始化就被添加到集合,改用TaskCompletionSource等待回调完成:
// 替换原有缩略图请求逻辑 var thumbnailTcs = new TaskCompletionSource<UIImage>(); imageManager.RequestImageForAsset(phAsset, thumbnailSize, PHImageContentMode.AspectFit, thumbnailRequestOptions, (image, info) => { thumbnailTcs.TrySetResult(image); }); var thumbnailImage = await thumbnailTcs.Task; if (thumbnailImage != null) { // 处理缩略图保存逻辑 NSData imageData = thumbnailImage.CGImage.RenderingIntent == CGColorRenderingIntent.Default ? thumbnailImage.AsJPEG(0.8f) : thumbnailImage.AsPNG(); if (imageData != null) { var fileName = Path.Combine(tmpPath, $"tmp_thumbnail_{Path.GetFileNameWithoutExtension(name)}.jpg"); NSError error = null; imageData.Save(fileName, true, out error); if (error == null) { asset.PreviewPath = fileName; } } }
关键注意事项
- iOS平台的
NSObject子类(如PHCachingImageManager、PHAsset)必须手动调用Dispose(),否则会引发内存泄漏和观察者未清理问题。 - 页面切换时必须终止正在进行的异步任务,避免后台任务持有已销毁页面的引用导致崩溃。
- 热重启场景下iOS的资源回收机制更敏感,必须严格遵循内存管理规范。
内容的提问来源于stack exchange,提问作者Alexander Petrov
相关产品推荐
相关产品推荐

