Swift Macro
[iOS - swift] Swift Macro 예제 - toDouble, isNumber (문자열로 된 숫자 판단하는 Macro, toDouble)
jake-kim
2023. 9. 16. 01:32
toDouble 매크로 소개
- String타입의 숫자를 Double 형태로 바꾸는 매크로이며, 컴파일 타임에 숫자가 아닌 문자열을 미리 컴파일 에러를 발생하게하는것이 목표

toDouble 매크로 구현
- Swift macro 프로젝트 생성
- ToDouble 매크로 선언
// ToDouble.swift
import Foundation
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
public struct ToDouble: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
// TODO ...
}
}
- SwiftSyntax에 의하여 구문이 tree형태 자료구조로 표현되어 node로 부터 입력된 문자열 값 획득이 가능
- 문자열을 가지고 하나의 인수만 입력되었는지 판단
guard
let argument = node.argumentList.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
case .stringSegment(let literalSegment)? = segments.first
else {
throw CustomError.message("#ToDouble requires a static string literal")
}
- literalSegment.context.text로 입력된 문자열값을 가져와서 isNumber로 비교
- isNumber의 로직은 regularExpression을 이용한 방법이 성능이 CharacterSet보다 좋기 때문에 이것을 사용
- (regularExpression과 CharacterSet 성능 비교는 이전 포스팅 글 참고)
let inputString = literalSegment.content.text
if inputString.isNumber {
return "Double(\(argument))!"
} else {
throw CustomError.message("is not number: \(argument)")
}
private extension String {
var isNumber: Bool {
range(
of: "^[0-9]*$",
options: .regularExpression
) != nil
}
}
- 전체 코드
// ToDouble.swift
import Foundation
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
public struct ToDouble: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
guard
let argument = node.argumentList.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
case .stringSegment(let literalSegment)? = segments.first
else {
throw CustomError.message("#ToDouble requires a static string literal")
}
let inputString = literalSegment.content.text
if inputString.isNumber {
return "Double(\(argument))!"
} else {
throw CustomError.message("is not number: \(argument)")
}
}
}
private extension String {
var isNumber: Bool {
range(
of: "^[0-9]*$",
options: .regularExpression
) != nil
}
}
- 플러그인에 추가
// Plugins.swift
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
@main
struct ExNumberPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
StringifyMacro.self, // 프로젝트 생성시 자동으로 추가된 것
ToDouble.self
]
}
- 인터페이스 정의
// ExNumber.swift
// MARK: - ToDouble
@freestanding(expression)
public macro toDouble<T>(_ value: T) -> (T, String) = #externalMacro(module: "ExNumberMacros", type: "ToDouble")
- 사용하는 쪽 테스트를 위해 main.swift에 예제 코드 작성
// MARK: - toDouble
let numberValue = #toDouble("123")
let numberValue2 = #toDouble("123.456")
(빌드하면 제대로 구현된 것 확인 완료)
