我目前正在移植一个基于 Winforms 的小型 .NET 应用程序,以便将本机 Mac 前端与 MonoMac 一起使用。该应用程序有一个带有图标和文本的 TreeControl,这在 Cocoa 中不存在。
到目前为止,我已经在 Apple 的 DragNDrop 示例中移植了几乎所有的 ImageAndTextCell 代码:https ://developer.apple.com/library/mac/#samplecode/DragNDropOutlineView/Listings/ImageAndTextCell_m.html#//apple_ref/doc/uid /DTS40008831-ImageAndTextCell_m-DontLinkElementID_6,分配给 NSOutlineView 作为自定义单元格。
它似乎工作得几乎完美,除了我还没有弄清楚如何正确移植该copyWithZone
方法。不幸的是,这意味着 NSOutlineView 正在制作的内部副本没有图像字段,并导致图像在展开和折叠操作期间短暂消失。有问题的objective-c代码是:
- (id)copyWithZone:(NSZone *)zone {
ImageAndTextCell *cell = (ImageAndTextCell *)[super copyWithZone:zone];
// The image ivar will be directly copied; we need to retain or copy it.
cell->image = [image retain];
return cell;
}
第一行是什么让我失望,因为 MonoMac 没有公开 copyWithZone 方法,我不知道如何调用它。
更新
根据当前的答案和额外的研究和测试,我提出了多种复制对象的模型。
static List<ImageAndTextCell> _refPool = new List<ImageAndTextCell>();
// Method 1
static IntPtr selRetain = Selector.GetHandle ("retain");
[Export("copyWithZone:")]
public virtual NSObject CopyWithZone(IntPtr zone) {
ImageAndTextCell cell = new ImageAndTextCell() {
Title = Title,
Image = Image,
};
Messaging.void_objc_msgSend (cell.Handle, selRetain);
return cell;
}
// Method 2
[Export("copyWithZone:")]
public virtual NSObject CopyWithZone(IntPtr zone) {
ImageAndTextCell cell = new ImageAndTextCell() {
Title = Title,
Image = Image,
};
_refPool.Add(cell);
return cell;
}
[Export("dealloc")]
public void Dealloc ()
{
_refPool.Remove(this);
this.Dispose();
}
// Method 3
static IntPtr selRetain = Selector.GetHandle ("retain");
[Export("copyWithZone:")]
public virtual NSObject CopyWithZone(IntPtr zone) {
ImageAndTextCell cell = new ImageAndTextCell() {
Title = Title,
Image = Image,
};
_refPool.Add(cell);
Messaging.void_objc_msgSend (cell.Handle, selRetain);
return cell;
}
// Method 4
static IntPtr selRetain = Selector.GetHandle ("retain");
static IntPtr selRetainCount = Selector.GetHandle("retainCount");
[Export("copyWithZone:")]
public virtual NSObject CopyWithZone (IntPtr zone)
{
ImageAndTextCell cell = new ImageAndTextCell () {
Title = Title,
Image = Image,
};
_refPool.Add (cell);
Messaging.void_objc_msgSend (cell.Handle, selRetain);
return cell;
}
public void PeriodicCleanup ()
{
List<ImageAndTextCell> markedForDelete = new List<ImageAndTextCell> ();
foreach (ImageAndTextCell cell in _refPool) {
uint count = Messaging.UInt32_objc_msgSend (cell.Handle, selRetainCount);
if (count == 1)
markedForDelete.Add (cell);
}
foreach (ImageAndTextCell cell in markedForDelete) {
_refPool.Remove (cell);
cell.Dispose ();
}
}
// Method 5
static IntPtr selCopyWithZone = Selector.GetHandle("copyWithZone:");
[Export("copyWithZone:")]
public virtual NSObject CopyWithZone(IntPtr zone) {
IntPtr copyHandle = Messaging.IntPtr_objc_msgSendSuper_IntPtr(SuperHandle, selCopyWithZone, zone);
ImageAndTextCell cell = new ImageAndTextCell(copyHandle) {
Image = Image,
};
_refPool.Add(cell);
return cell;
}
方法一:增加非托管对象的保留计数。非托管对象将永远持续存在(我认为?dealloc 从未调用过),托管对象将被提早收获。似乎是全能双输,但在实践中运行。
方法二:保存管理对象的引用。非托管对象被单独留下,调用者似乎在合理的时间调用了 dealloc。此时托管对象被释放和处置。这似乎是合理的,但不利的一面是基本类型的 dealloc 不会运行(我认为?)
方法 3:增加保留计数并保存引用。非托管和托管对象永远泄漏。
方法 4:通过添加定期运行的清理函数(例如在每个新 ImageAndTextCell 对象的 Init 期间)扩展方法 3。清理函数检查存储对象的保留计数。保留计数为 1 意味着调用者已释放它,所以我们也应该这样做。理论上应该消除泄漏。
方法 5:尝试在基类型上调用 copyWithZone 方法,然后使用生成的句柄构造一个新的 ImageAndTextView 对象。似乎做对了(克隆了基础数据)。在内部,NSObject 会增加这样构造的对象的保留计数,因此我们还使用 PeriodicCleanup 函数在不再使用这些对象时释放它们。
基于以上所述,我认为方法 5 是最好的方法,因为它应该是唯一一种能够生成真正正确的基本类型数据副本的方法,但我不知道该方法是否具有内在危险(我也在制作关于 NSObject 底层实现的一些假设)。到目前为止,“还”没有发生任何不好的事情,但如果有人能够审查我的分析,那么我会更有信心继续前进。