Xamarin iOS F#设置UICollectionViewDelegateFlowLayout引发空引用异常
你遇到的崩溃问题根源在于错误地将控制器的handle传递给了CollectionViewFlowDelegate的构造函数,以及后续的Delegate实例生命周期管理问题,具体原因和解决办法如下:
为什么设置代理后会崩溃?
在Xamarin iOS中,带IntPtr handle的构造函数是用来绑定已经由Objective-C Runtime创建的对象(比如从故事板/XIB加载的实例)。你把LandlordHome控制器的handle传给CollectionViewFlowDelegate的构造函数,相当于让这个Delegate实例绑定到控制器的Objective-C对象上,这会导致Runtime混淆两个对象的身份,进而引发内存管理异常,最终导致你的listings变量在后续调用中被意外置为null,触发NullReferenceException。
另外,就算构造函数没问题,如果你只是临时创建Delegate实例并赋值给CollectionView.Delegate,而没有保留强引用,.NET的GC可能会回收这个实例(因为UICollectionView对Delegate的引用是弱引用),同样会引发各种异常。
解决办法
这里提供两种可靠的解决方案,你可以根据需求选择:
方案1:修复Delegate的构造与生命周期管理
首先修改CollectionViewFlowDelegate的定义,添加无参数构造函数(手动创建实例时不需要依赖IntPtr handle):
type CollectionViewFlowDelegate () = inherit UICollectionViewDelegateFlowLayout () override x.GetSizeForItem(collectionView : UICollectionView, layout : UICollectionViewLayout, indexPath : NSIndexPath) = CGSize(100.0, 300.0)
然后在控制器中保留对Delegate实例的强引用,避免被GC回收:
[<Register ("LandlordHome")>] type LandlordHome (handle:IntPtr) = inherit UICollectionViewController (handle) let addToList (listan:List<Listing>) (element:Listing) = element::listan let mutable listings : List<Listing> = List.Empty // 强引用保存Delegate实例,防止被GC回收 let flowDelegate = CollectionViewFlowDelegate() override x.ViewDidLoad () = base.ViewDidLoad () x.View.BackgroundColor <- UIColor.Gray for i in 1..10 do let listing : Listing = new Listing(....code....) listings <- listing::listings x.CollectionView.BackgroundColor <- UIColor.Green // 使用保留的Delegate实例赋值 x.CollectionView.Delegate <- flowDelegate x.CollectionView.RegisterClassForCell(typeof<ChatViewCell>, new NSString("ChatCell")) override x.ViewDidAppear(animated) = x.CollectionView.ReloadData() override x.NumberOfSections(collectionView : UICollectionView) = Conversions.nint(1) override x.GetItemsCount(collectionView : UICollectionView, section : nint) = Conversions.nint(listings.Length)
方案2:让控制器直接实现UICollectionViewDelegateFlowLayout接口
这种方式更简洁,不需要额外创建Delegate类,直接让你的LandlordHome控制器实现UICollectionViewDelegateFlowLayout接口,重写需要的方法即可:
[<Register ("LandlordHome")>] type LandlordHome (handle:IntPtr) = inherit UICollectionViewController (handle) // 实现UICollectionViewDelegateFlowLayout接口 interface UICollectionViewDelegateFlowLayout with override x.GetSizeForItem(collectionView : UICollectionView, layout : UICollectionViewLayout, indexPath : NSIndexPath) = CGSize(100.0, 300.0) let addToList (listan:List<Listing>) (element:Listing) = element::listan let mutable listings : List<Listing> = List.Empty override x.ViewDidLoad () = base.ViewDidLoad () x.View.BackgroundColor <- UIColor.Gray for i in 1..10 do let listing : Listing = new Listing(....code....) listings <- listing::listings x.CollectionView.BackgroundColor <- UIColor.Green // 不需要手动设置Delegate,控制器会自动作为Delegate x.CollectionView.RegisterClassForCell(typeof<ChatViewCell>, new NSString("ChatCell")) override x.ViewDidAppear(animated) = x.CollectionView.ReloadData() override x.NumberOfSections(collectionView : UICollectionView) = Conversions.nint(1) override x.GetItemsCount(collectionView : UICollectionView, section : nint) = Conversions.nint(listings.Length)
这种方式避免了单独管理Delegate实例的麻烦,也符合Xamarin iOS的开发习惯,推荐使用。
内容的提问来源于stack exchange,提问作者Alk

