正如其他人指出的那样,问题在于Calendar.current.component(_:from:)
是在幕后引入一个自动释放对象,该对象在自动释放池耗尽之前不会被释放。
回到引用计数 Objective-C 代码的早期,返回一个新分配的对象(当调用者使用完该对象后将自动释放该对象)的常见方法是返回一个“自动释放”对象。这是一个只有在返回到运行循环时才会被释放的对象,这会耗尽自动释放池 https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html#//apple_ref/doc/uid/20000047-CJBFBEDI。您可以通过添加自己的手动自动释放池来控制重复创建自动释放对象的大型循环的高水位线。
Swift 本身并不创建自动释放对象,因此这个问题有点像 Objective-C 的不合时宜,我们在自己的 Swift 代码中通常不会遇到这种情况。但是每当编写循环和调用可能在幕后使用自动释放对象的 Cocoa API 的代码时(例如在本例中),我们必须对此保持敏感。
在深入研究解决方案之前,我将把您的示例调整为保证最终退出的内容。例如,让我们编写一个循环运行的例程,直到minute
与当前时间变化相关(例如当前分钟结束和下一分钟开始时)。我们假设previousValue
包含当前的minute
value.
诀窍是我们需要把autoreleasepool https://developer.apple.com/documentation/objectivec/2299644-autoreleasepool inside循环。在下面的示例中,我们利用了以下事实:autoreleasepool
是通用的,返回其闭包内返回的任何内容:
while autoreleasepool(invoking: { Calendar.current.component(.minute, from: Date()) }) == previousValue {
// do something
}
请注意,如果您发现该模式难以阅读(需要一段时间才能习惯闭包作为方法的参数),您可以使用repeat
-until
循环,完成大致相同的事情:
var minute: Int!
repeat {
minute = autoreleasepool {
Calendar.current.component(.minute, from: Date())
}
// do something
} while minute == previousValue
顺便说一句,像这样快速旋转的循环的过程效率非常低。当然,正如您所提到的,您永远不会在主线程上执行此操作(因为我们不想阻塞主线程)。但你通常不会这样做any线程,因为旋转的计算量非常大。有时你必须这样做(例如,无论如何在后台线程上进行一些复杂的计算,并且你希望它在某个特定时间停止),但十有八九,这是设计中某些更深层次问题的代码味道。通常,明智地使用计时器等可以实现所需的效果,而无需计算开销。
很难就您的情况向您提供最佳解决方案的建议,因为我们不知道您要解决的更广泛的问题。但请注意,通常不建议在线程上旋转。
You ask:
好吧,但如果我运行一个仅进行日期比较的循环,我不会遇到同样的问题。这是因为优化器介入了吗?
不,这只是因为 Date() 根本不向混合中引入任何自动释放对象,例如Calendar.current.component(_:from:)
显然是这样。 (顺便说一句,Apple 一直擅长在整个代码库中缓慢删除自动释放对象,因此您可能会在未来的某个日期发现,即使这可能不需要手动自动释放池。)