我注意到在Objective-C中,除了释放非GC内存之外,在-finalize
中做任何事情都是不受欢迎的。我遇到的具体原因是:
a)你冒险通过发送一些自我引用来复活最终的对象 b)您不知道您正在发送消息的对象是否也已完成。
还有其他原因吗?特别应该避免什么?我特别想知道分配新对象,锁定gc线程,使用gcd分派一个块,以及发送一个单一对象一个performSelectorOnMainThread:消息。
为了提供一些上下文,我正在编写一个类,它从C API包装一个类型,在分配或释放时对全局状态进行修改。我很想做这样的事情:
@interface MyWrapper : NSObject
{
C_API_Type* data;
}
@end
static const* NSString GlobalLock = @"GlobalLock";
@implementation MyWrapper
- (id)init
{
if((self = [super init]))
{
@synchronized( GlobalLock )
{
data = C_API_Allocate();
}
}
return self;
}
- (void) finalize
{
@synchronized( GlobalLock )
{
C_API_Deallocate(data);
}
[super finalize];
}
@end
避免了这两个问题,但警钟仍在消失。我可能只需要接近我尝试做的不同的事情,但如果是这样,这种模式有什么问题?
答案 0 :(得分:3)
一般规则实际上是“不要在dealloc
(手动保留 - 释放)或finalize
”中进行繁重的工作。
两种情况下的问题恰恰在于最终确定或解除分配的对象的本地对象图必须处于未定义状态或,您必须强制执行对象销毁的特定顺序,这是一个可怕的事情。
“无重举”是表达特定细节的简单规则; free()
malloc内存非常安全,因为它不会触及对象图。但触摸其他物体很糟糕,会导致脆弱和/或崩溃。
(当将Xcode从非GC移植到GC时,唯一最令人讨厌的问题来源之一是订单依赖 - 已知和未知 - 都在dealloc
方法中。通过移动到更多正式的“无效”模式,可以明确表达和强制执行依赖性,消除脆弱性。)
答案 1 :(得分:0)
基本上,因为Objective-C垃圾收集器本身就是垃圾: - /但这不是一个咆哮的地方......
基本上,垃圾收集器标记所有可到达的对象,然后在所有无法访问的对象上调用-finalize,没有特定的顺序。因此,如果当前正在执行其-finalize方法的对象x具有对另一个对象y的引用,则当x尝试访问y时,y可能已经完成。因此,您可以访问已经完成的对象。
访问最终对象可能看起来并不像现在这样糟糕。访问死(收集)对象更糟糕。为什么会这样? Objective C使用世代垃圾收集器。这意味着有可能在某个时间点,收集器只收集一些的无法访问的对象。但是,最终,对象x调用了-finalize方法,而x具有对y的强引用,这已经在早期的GC轮次中收集了。因此,如果x以任何有用的方式访问y,则该对象已经消失。如果GC很好,你会得到一个“复活”错误(是的,你不必让一个无法到达的对象可以获得复活错误。)如果它不好,就会发生任意的事情。
为了使问题更复杂,Cocoa使用了许多所谓的“弱”引用。因此,如果我创建一个窗口并且不保持指向它的指针,它将突然消失:-o在经历了一些这样的经历后,我将GC从我的项目中驱逐出去。现在,我可以创建一个窗口,并且我不必保持它的指针,它仍然保持活着直到它关闭。我认为,每个@property (assign)
都会转换为__weak
引用。 (不是字面意思,但这似乎是实施了多少AppKit。从技术上讲,在GC管理的代码中,@property (retain)
和property (assign)
是等效的。)虽然一开始看起来很明智,但我认为这样做在GC下编程要困难得多,因为程序的语义变得相当不确定。
垃圾收集器不会收集C类型,因此您可以在-finalize方法中安全地释放C类型。如果你不释放它们,你将有内存泄漏。