我正在尝试在 Swift 3 中创建一个空合并赋值运算符。换句话说,而不是这个:
x = x ?? y
我想要这个:
x ??= y
Swift 3 似乎不喜欢我的操作员。这是它的定义:
infix operator ??= : AssignmentPrecedence
func ??=(lhs: inout Any?, rhs: @autoclosure () -> Any?) {
if lhs != nil { return }
lhs = rhs()
}
var x = 3
x ??= 7 // Cannot convert value of type 'Int' to expected argument type 'inout Any?'.
我也已经在没有
@autoclosure
的情况下完成了它。我的理解是优先组 AssignmentPrecedence
已经包含 assignment
声明,所以这不太可能是问题。
如何在 Swift 3 中执行此操作?
首先,使运算符通用,而不是使用
Any
:
infix operator ??= : AssignmentPrecedence
func ??=<T>(lhs: inout T?, rhs: @autoclosure () -> T?) {
if lhs != nil { return }
lhs = rhs()
}
其次,左操作数必须是可选的(否则它 无法针对
nil
进行测试):
var x: Int? = 3
x ??= 7
我想出了我自己的空合并赋值运算符的“风格”,与上面类似,但是通过添加返回类型,您可以同时进行赋值和返回,这更符合正常的“?” ?行为,如果 LHS 为空,则添加从 RHS 到 LHS 的分配。它非常适合可重置的惰性变量。这是我的版本...
// Null-coalescing assignment operator
infix operator ??= : AssignmentPrecedence
@discardableResult
func ??= <T>(lhs: inout T?, rhs: @autoclosure () -> T) -> T {
if let lhs {
return lhs
}
let rhsResult = rhs()
lhs = rhsResult
return rhsResult
}
有了上面的内容,我现在可以像这样执行可重置的惰性变量......
private var qCache:Int?
var q:Int {
return qCache ??= {
print("Lazy-calculating q...")
return 44
}()
}
func resetQ() { qCache = nil }
print("1: q is \(q)")
print("2: q is \(q)")
print("3: q is \(q)")
print("Resetting lazy q...")
resetQ()
print("4: q is \(q)")
输出...
Lazy-calculating q...
1: q is 44
2: q is 44
3: q is 44
Resetting lazy q...
Lazy-calculating q...
4: q is 44
如果您愿意,您也可以实现设置器以及分离出“惰性计算”函数...
func lazyCalcQ() -> Int {
print("Lazy-calculating q...")
return 44
}
private var qCache:Int?
var q:Int {
get { return qCache ??= lazyCalcQ() }
set { qCache = newValue }
}
func resetQ() { qCache = nil }
更进一步,您可以使用隐式解包的数据类型,这样您就可以使用
nil
的赋值来进行重置,但始终可以保证 getter 提供一个值。唯一的缺点是,有时您仍然必须强制解开它以消除一些警告,如下面的打印语句中所示,但同样,您可以保证一个值,所以这样做是安全的。
注意:我个人更喜欢上面的方法,使用非隐式解包版本和额外的“重置”功能——特别是如果它是只读属性——因为它使 API 更加清晰,但我正在分享这是为了完整性,因为它展示了 nil-as-reset 的创造性用途。
func lazyCalcR() -> Int {
print("Lazy-calculating r (i.e. the default when reset with 'nil')...")
return 10
}
private var rCache:Int?
var r:Int! {
get { return rCache ??= lazyCalcR() }
set { rCache = newValue }
}
print("1: r is \(r!)")
r += 10
print("2: r is \(r!)")
r += 10
print("3: r is \(r!)")
print("Resetting r to default...")
r = nil
print("4: r is \(r!)")
输出...
Lazy-calculating r (i.e. the default when reset with 'nil')...
1: r is 10
2: r is 20
3: r is 30
Resetting r to default...
Lazy-calculating r (i.e. the default when reset with 'nil')...
4: r is 10
当然,上面都是使用 int 的简单示例,但我用它来做一些事情,比如根据边界计算复杂路径等。
我的下一个尝试是将所有这些隐藏在属性包装器中,从而消除对该运算符本身的需要。