Hello,
I created a macro layer to generate boiler code.
Initially I used @Published
to have both variables and a publisher to subscribe to if needed.
When I introduced a Protocol
abstraction layer i needed have an additional way to forward the publisher since both @Published
and $variableName
are prohibited on protocols
The peer macro itself creates AnyPublisher
for @Published
properties it is attached to and works for reference types and protocols.
On a protocol level the macro has the following expansion:
protocol ExampleProtocol: AnyObject {
@Publisher
var someVariable: String { get }
}
protocol ExampleProtocol: AnyObject {
var someVariable: String { get }
var someVariablePublisher: AnyPublisher<String, Never> { get }
}
On a class level the macro has the following expansion:
class ExampleClass: ExampleProtocol {
@Published @Publisher
var someVariable = "Test"
}
class ExampleClass: ExampleProtocol {
@Published
var someVariable = "Test"
var someVariablePublisher: AnyPublisher<String, Never> {
$someVariable.eraseToAnyPublisher()
}
}
An example consumer for the protocol would look like this
class ExampleConsumer {
private let service: any ExampleProtocol
private var cancellables = Set<AnyCancellable>()
init(with service: some ExampleProtocol) {
self.service = service
bind(to: service)
}
private func bind(to service: some ExampleProtocol) {
service.someVariablePublisher
.sink { someVariable in
print(someVariable)
}
.store(in: &cancellables)
}
}
This compiles fine and inspection of the someVariablePublisher
correctly shows the expanded macro.
Accessing the someVariablePublisher
during runtime will lead to a crash EXC_BAD_ACCESS
during runtime. I observed error codes 1, 2 and 257.
I can resolve the crash by removing the macro from the protocol and manually write the someVariablePublisher
.
How can i resolve the crash while still retaining macro functionality ?
Or is this a bug within swift itself ?
I can't share the full macro code but here would be an example that creates the publisher for this specific example:
public struct ExampleMacro: PeerMacro {
public static func expansion(of node: SwiftSyntax.AttributeSyntax,
providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol,
in context: some SwiftSyntaxMacros.MacroExpansionContext) throws -> [SwiftSyntax.DeclSyntax] {
let isProtocol = context.lexicalContext.contains(where: { $0.is(ProtocolDeclSyntax.self) })
let isClass = context.lexicalContext.contains(where: { $0.is(ClassDeclSyntax.self) })
if isClass {
return ["var someVariablePublisher: AnyPublisher<String, Never> { $someVariable.eraseToAnyPublisher() }"]
} else if isProtocol {
return ["var someVariablePublisher: AnyPublisher<String, Never> { get }"]
} else {
throw MacroError.nonClassOrProtocol
}
}
}
public extension ExampleMacro {
enum MacroError: CustomStringConvertible, Error {
case nonClassOrProtocol
public var description: String {
switch self {
case .nonClassOrProtocol:
return "This macro can only applied to classes or protocols"
}
}
}
}