我正在制作这样的宏:
@attached(peer, names: arbitrary)
public macro Lazify(name: String, lock: Protectable) = #externalMacro(module: "MacroModule", type: "LazifyMacro")
它应该用于附加到函数声明,以生成一些其他变量。
但是我得到了错误
Circular reference expanding peer macros on <function_name>
,代码如下:
class SomeClass {
var _internal_lock: NSLock = .init()
static let classLock: NSLock = .init()
// Error: Circular reference expanding peer macros on 'createLazyVariable()'
@Lazify(name: "internalClassName", lock: SomeClass.classLock)
func createLazyVariable() -> String {
return "__" + NSStringFromClass(type(of: self))
}
}
如果我将
lock: Protectable
更改为 lock: String
那么就可以了,但是使用宏时不会进行类型检查。
此外,如果我使用实例属性锁
_internal_lock
,错误将更改为Instance member '_internal_lock' cannot be used on type 'SomeClass'; did you mean to use a value of this type instead?
所以我想知道为什么我不能在这里使用
classLock
或_internal_lcok
?
如果我使用全局实例锁,那么它可以编译:
let globalLock: NSLock = .init()
class SomeClass {
@Lazify(name: "internalClassName", lock: globalLock)
func createLazyVariable() -> String {
return "__" + NSStringFromClass(type(of: self))
}
}
我不知道为什么会发生这种情况。它可能与 this bug 有关,已在 Swift 5.10 中修复(尽管我还没有 5.10 来检查)。
无论如何,似乎
Lazify
都会添加一个声明,其中包含传递给其 name
参数的任何字符串的名称。这不是一个好的设计,因为至少使用 Xcode 中的“Refactor -> Rename”选项来重命名它会很困难。
您可以使用
suffixed
中的 prefixed
或 @attached(peer, names: ...)
选项,而不是生成用户想要的任何名称,并且始终根据宏所附加的声明的名称生成名称。例如,使用:
@attached(peer, names: suffixed(_lazify))
// you can write multiple "suffixed(...)" here, if you need
那么,
@Lazify(lock: SomeClass.classLock)
func createLazyVariable() -> String { ... }
应该被实现来生成
createLazyVariable_lazify
。
您不能在此处使用实例属性,原因与类似的方法不起作用的原因类似:
class Foo {
let x = 1
let y = x // "self" is not available here!
}
我认为这是在宏扩展之前检查的,所以这本身不是宏的问题。
要允许实例成员,您可以添加如下重载:
public macro Lazify<T, P: Protectable>(lock: KeyPath<T, P>) = ...
用途:
@Lazify(lock: \SomeClass._internal_lock)
// in the macro implementation, you would generate something like this:
self[keyPath: \SomeClass._internal_lock]
// to access the lock.
从您的上一个问题来看,更好的设计似乎是只使用属性包装器:
@propertyWrapper
struct Lazify<T> {
let lock: Protectable
let initialiser: () -> T
var wrappedValue: T {
mutating get {
if let x = lazy {
return x
}
return lock.around {
if let x = self.lazy {
return x
}
let temp = initialiser()
self.lazy = temp
return temp
}
}
set { lazy = newValue }
}
private(set) var lazy: T?
}