在您的具体示例中,解决方案很简单。在解决所有可能的故障之前不要分配任何内存:
class Person {
let aPointer: UnsafeMutablePointer<Int> // Do not allocate here.
let name: String
init(name: String) throws {
// Validate everything here
guard name != "UnsupportName" else {
throw NSError()
}
// After this point, no more throwing:
self.name = name
// Move the allocation here
self.aPointer = UnsafeMutablePointer.allocate(capacity: 1)
}
deinit {
aPointer.deallocate()
}
}
但更通用的解决方案是像其他任何需要管理错误的地方一样使用 do/catch:
class Person {
let aPointer = UnsafeMutablePointer<Int>.allocate(capacity: 1)
let name: String
init(name: String) throws {
do {
if name == "UnsupportName" {
throw NSError()
}
self.name = name
} catch let e {
self.aPointer.deallocate()
throw e
}
}
deinit {
aPointer.deallocate()
}
}
我很想移动.allocate
里面的内容init
,只是为了让它更清楚地看到正在发生的事情。关键是您应该首先分配所有内存,在任何东西都可以抛出之前(所以你知道你可以全部释放它),或者在最后一次抛出之后(所以你知道你没有任何东西可以释放)。
查看您添加的解决方案,没关系,但暗示了围绕它的危险逻辑。最好将其展开以将分配放入他们自己的对象中(这几乎肯定也会摆脱 UnsafeMutablePointers;在一个类中需要很多这些是非常可疑的)。
也就是说,IMO 有更简洁的方法来沿着这条路径构建错误处理。
extension UnsafeMutablePointer {
static func allocate(capacity: Int, withCleanup cleanup: inout [() -> Void]) -> UnsafeMutablePointer<Pointee> {
let result = allocate(capacity: capacity)
result.addTo(cleanup: &cleanup)
return result
}
func addTo(cleanup: inout [() -> Void]) {
cleanup.append { self.deallocate() }
}
}
这让 UnsafeMutablePointers 可以将清理信息附加到一个数组中,而不是创建很多defer
块,这会增加清理过程中丢失一个块的风险。
这样,您的 init 看起来像:
init(name: String) throws {
var errorCleanup: [() -> Void] = []
defer { for cleanup in errorCleanup { cleanup() } }
// deallocate helper for case1
pointer1.addTo(cleanup: &errorCleanup)
// case2
self.pointer2 = UnsafeMutablePointer<Int>.allocate(capacity: 1, withCleanup: &errorCleanup)
// case ...
if name == "UnsupportName" {
throw NSError()
}
self.name = name
// In the end. set deallocate helpers to nil
errorCleanup.removeAll()
}
当然,这设置了调用allocate(capacity:)
而不是allocate(capacity:withCleanup:)
. 因此,您可以通过将其包装成另一种类型来解决此问题;自动释放自身的引用类型。
class SharedPointer<Pointee> {
let ptr: UnsafeMutablePointer<Pointee>
static func allocate(capacity: Int) -> SharedPointer {
return .init(pointer: UnsafeMutablePointer.allocate(capacity: capacity))
}
init(pointer: UnsafeMutablePointer<Pointee>) {
self.ptr = pointer
}
deinit {
ptr.deallocate()
}
}
这样,就变成了(不需要 deinit):
class Person {
// case1
let pointer1 = SharedPointer<Int>.allocate(capacity: 1)
// case2
let pointer2: SharedPointer<Int>
let name: String
init(name: String) throws {
// case2
self.pointer2 = SharedPointer<Int>.allocate(capacity: 1)
if name == "UnsupportName" {
throw NSError()
}
self.name = name
}
}
您可能想要编写各种帮助程序来处理.ptr
.
当然,这可能会导致您构建特定版本的 SharedPointer 来处理各种事情(比如“父亲”而不是“int”)。如果你继续沿着这条路走,你会发现 UnsafeMutablePointers 消失了,问题就消失了。但您不必走那么远,SharedPointer 将为您完成工作。