使用Xcode Profiler,我刚刚发现JSON解码上出现了一个不太必要的内存峰值。显然,这是一个已知的问题,我应该用一个有帮助的autoreleasepool
来结束通话:
//extension..
var jsonData: Data? {
return autoreleasepool{ try? JSONSerialization.data(withJSONObject: self, options: []) }
}
我发现了另外几大块并不是真正需要的分配,所以我把我新学到的技巧也应用到了其他代码中,比如:
var protoArray = [Proto_Bit]()
for bit in data {
autoreleasepool{
if let str = bit.toJSONString() {
if let proto = try? Proto_Bit(jsonString: str) {
protoArray.append(proto)
}
}
}
}
现在,在我将代码中的每一条指令(或者至少在我认为合适的地方)封装在autoreleasepool
中之前,我想问一下它是否有任何风险或缺点
有了这两个包装,我能够将峰值内存消耗从500mb减少到170mb。我知道斯威夫特在幕后也会做这些事情,可能有一些警卫,但我宁愿安全也不愿道歉。
autoreleasepool
是否有CPU开销?如果是5%,我会同意的,因为这听起来是一个很好的权衡,如果是更多,我将不得不调查我能用
autoreleasepool
搞砸什么吗?空指针、线程锁定等,因为块结构看起来有点可怕。。或者这只是告诉硬件";在支架的末端清理并关上你身后的门";而不影响其他对象?
自动释放池是一种来自Objective-C的机制,用于帮助自动化内存管理并确保对象和资源被释放"最终";,其中";最终";当池排水时出现。即,一旦在线程上创建了一个自动释放池,它就会在池处于活动状态时捕获(保留)所有-autorelease
对象——当池耗尽时,所有这些对象都会被释放。(请注意,这是一个与Objective-C运行时结合的基础功能,并且没有直接与硬件集成:它的方式,方式比这更高。)
作为直接管理自动释放池(避免直接创建NSAutoreleasePool
实例)的捷径,Objective-C引入了@autoreleasepool
语言关键字,它有效地在作用域的开头创建了一个自动释放池,并在结尾耗尽它:
@autoreleasepool /* create an autorelease pool to capture autoreleased objects */ {
// ... do stuff ...
} /* release the autoreleasepool, and all objects that were in it */
以这种方式手动引入自动发布池可以让您更好地控制何时有效地清理基于自动发布的对象:如果您知道一个代码块创建了许多不需要比该代码块更长时间的基于自动发布对象,那么这可能是一个很好的@autoreleasepool
封装方案。
自动释放池早于ARC,它以确定性的方式自动化引用计数,它的引入使自动释放池在大多数代码中变得基本上不必要:如果一个对象可以确定性地保留和释放,就没有必要依赖于自动释放它"在某个时刻";。(事实上,与-retain
和-release
等常规内存管理调用一样,ARC也不允许直接在对象上调用-autorelease
。)
Swift遵循ARC内存管理模型,也不依赖于自动释放对象——所有对象都是在上次使用后确定释放的。然而:Swift仍然需要与Objective-C代码进行互操作,值得注意的是,并不是所有的Objective-C码(包括Foundation中的许多代码)都使用ARC。许多苹果内部框架仍然使用Objective-C的手动内存管理,因此仍然依赖于自动发布的对象。
在Swift可能需要与Objective-C代码进行互操作的平台上,不需要显式地就可以最终发布基于自动发布的对象:Darwin平台上的每个Swift应用程序在捕获基于自动发布对象的进程的根上都至少有一个隐式自动发布池。然而,正如你所注意到的:;最终的";Objective-C对象的发布可能会保持较高的内存使用率,直到池耗尽为止。为了帮助缓解高内存使用率,Swift有autoreleasepool { ... }
(与Objective-C的@autoreleasepool { ... }
相匹配),它允许您显式地热切地捕获那些自动释放的对象,并在作用域结束时释放它们。
直接回答您的问题,但按相反顺序:
我能用自动释放池搞砸任何东西吗对于正确编写的代码,否。您所做的只是帮助Objective-C运行时比其他情况更早地清理这些对象。需要注意的是:这些对象只会被池释放——如果在池释放它们后它们的保留计数仍然为正,那么它们必须在某个地方仍在使用,并且在持有对象的其他所有者也释放它们之前,不会被释放。
自动释放池的引入会导致一些以前没有的意外行为发生吗?绝对地错误编写的代码可能会意外工作,因为一个对象被意外地保持了足够长的生存时间,以防止意外行为的发生,并且尽早释放对象可能会触发它。但是,这既是不太可能的(考虑到苹果框架之外实际手动内存管理的工作量很小),也是你不能依赖的:如果代码在新引入的
autoreleasepool
中表现不佳,那么一开始就不正确,可能会以其他方式对你产生反作用。自动释放池是否有CPU开销是的,与应用程序执行的实际工作相比,它可能微不足道。但是,这并不意味着把
autoreleasepool
洒得到处都是有用的:- 考虑到Swift项目中基于自动发布的对象数量随着从Objective-C转换的代码数量的增加而减少,越来越少看到大量需要迫切清理的基于自动发布对象。你可以到处撒
autoreleasepool
,但这些池完全有可能是空的,没有什么可清理的 autoreleasepool
s不影响原生Swift分配:只有Objective-C对象可以自动释放,这意味着对于Swift代码的大部分来说,autoreleasepool
s完全被浪费了
- 考虑到Swift项目中基于自动发布的对象数量随着从Objective-C转换的代码数量的增加而减少,越来越少看到大量需要迫切清理的基于自动发布对象。你可以到处撒
那么,何时应该使用autoreleasepool
s?
- 当您使用来自Objective-C的代码时
- 您已经测量了,以表明由于
autorelease
d对象,这有助于提高内存使用率 - 您还测量了通过引入
autoreleasepool
进行适当清理
换句话说,正是你在问题中做了什么。所以,荣誉。
然而,尽量避免货物在各处插入autoreleasepool
:如果没有实际测量和了解可能发生的情况,它极不可能有效。
[题外话:你怎么知道对象/代码何时可能来自Objective-C?你不可能,很容易。一个很好的经验法则是,许多苹果框架仍然是在幕后用Objective-C编写的,或者可能在某个层返回一个Objective-C对象桥接(或不桥接)Swift——所以如果你测量了一些可操作的东西,他们可能是调查的罪魁祸首如今,第三方库也不太可能包含Objective-C,但您也可以访问它们的源代码进行确认。]
关于优化和autoreleasepool
的另一个注意事项:通常,您不应该期望构建的Release配置在自动发布的对象方面与Debug配置表现不同。
与ARC代码(Swift和Objective-C中都有)不同,编译器可以在编译时为代码插入内存管理优化,自动释放池是一个运行时功能,由于任何保留都必须使对象实例保持活动,即使将对象插入自动释放池也会使其保持活动,直到在运行时处理掉。因此,即使编译器可以积极优化Release配置中大多数对象的保留和释放的特定位置,对于自动释放的对象也无能为力。
(好吧,如果ARC优化器对使用对象的所有代码、它所属的自动发布池的上下文等都有足够的可见性,那么它可以围绕自动发布对象进行一些优化,但这通常非常有限,因为对象最初是-autorelease
d的范围通常与自动发布池所在的范围相差ddefinition[否则它将成为常规内存管理的候选者]。)