์๋ ํ์ธ์! ์ค๋์ Swift 5.9์์ ๋์ ๋ '๋งคํฌ๋ก(Macros)'์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค. ๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐ๋ณต์ ์ธ ์ฝ๋ ์์ฑ์ ์ค์ด๊ณ , ์ปดํ์ผ ์์ ์ ์ฝ๋๋ฅผ ์๋ ์์ฑํ ์ ์์ด ๊ฐ๋ฐ ํจ์จ์ฑ๊ณผ ์ฝ๋ ํ์ง์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์์ต๋๋ค.
๐ ๋งคํฌ๋ก๋?
๋งคํฌ๋ก๋ ์ปดํ์ผ ์๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋๊ตฌ๋ก, ๊ฐ๋ฐ์๊ฐ ์ง์ ์์ฑํด์ผ ํ ๋ฐ๋ณต์ ์ธ ์ฝ๋๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด์ค๋๋ค. Swift ์ปดํ์ผ๋ฌ๋ ์ฝ๋๋ฅผ ๋น๋ํ๊ธฐ ์ ์ ๋ชจ๋ ๋งคํฌ๋ก๋ฅผ ํ์ฅํฉ๋๋ค.
๋งคํฌ๋ก์ ์ฃผ์ ํน์ง:
- ํญ์ ์ฝ๋๋ฅผ ์ถ๊ฐ๋ง ํ๊ณ , ๊ธฐ์กด ์ฝ๋๋ฅผ ์ญ์ ํ๊ฑฐ๋ ์์ ํ์ง ์์
- ๋ฌธ๋ฒ์ ์ผ๋ก ์ ํจํ Swift ์ฝ๋๋ฅผ ์์ฑ
- ํ์ ์์ ์ฑ ๋ณด์ฅ
- ๋งคํฌ๋ก ํ์ฅ ์ค ์ค๋ฅ ๋ฐ์ ์ ์ปดํ์ผ ์ค๋ฅ๋ก ์ฒ๋ฆฌ
๐ ๋งคํฌ๋ก์ ์ข ๋ฅ
Swift๋ ๋ ๊ฐ์ง ์ ํ์ ๋งคํฌ๋ก๋ฅผ ์ ๊ณตํฉ๋๋ค:
1. ๋ ๋ฆฝ ๋งคํฌ๋ก (Freestanding Macros)
๋ ๋ฆฝ ๋งคํฌ๋ก๋ ์ด๋ฆ ์์ ํด์ ๊ธฐํธ(#)๋ฅผ ๋ถ์ฌ ํธ์ถํ๋ฉฐ, ๋ค๋ฅธ ์ ์ธ์ ์ฒจ๋ถ๋์ง ์๊ณ ๋ ๋ฆฝ์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
func myFunction() {
print("ํ์ฌ ์คํ ์ค์ธ ํจ์: \(#function)")
#warning("๋ฌธ์ ๊ฐ ์์ต๋๋ค")
}
์ด ์์ ์์:
- #function์ ํ์ฌ ํจ์์ ์ด๋ฆ์ผ๋ก ๋์ฒด๋ฉ๋๋ค.
- #warning์ ์ฌ์ฉ์ ์ ์ ์ปดํ์ผ ๊ฒฝ๊ณ ๋ฅผ ์์ฑํฉ๋๋ค.
2. ์ฒจ๋ถ ๋งคํฌ๋ก (Attached Macros)
์ฒจ๋ถ ๋งคํฌ๋ก๋ ์ด๋ฆ ์์ ์ณ ๊ธฐํธ(@)๋ฅผ ๋ถ์ฌ ํธ์ถํ๋ฉฐ, ํน์ ์ ์ธ์ ์ฒจ๋ถ๋์ด ํด๋น ์ ์ธ์ ์์ ํฉ๋๋ค.
// ๋งคํฌ๋ก ์์ด ์์ฑํ ์ฝ๋
struct SundaeToppings: OptionSet {
let rawValue: Int
static let nuts = SundaeToppings(rawValue: 1 << 0)
static let cherry = SundaeToppings(rawValue: 1 << 1)
static let fudge = SundaeToppings(rawValue: 1 << 2)
}
// ๋งคํฌ๋ก๋ฅผ ์ฌ์ฉํ ๊ฐ๊ฒฐํ ์ฝ๋
@OptionSet<Int>
struct SundaeToppings {
private enum Options: Int {
case nuts
case cherry
case fudge
}
}
@OptionSet ๋งคํฌ๋ก๋ private ์ด๊ฑฐํ์ ์ผ์ด์ค๋ฅผ ์ฝ๊ณ , ๊ฐ ์ต์ ์ ๋ํ ์์๋ฅผ ์์ฑํ๋ฉฐ, OptionSet ํ๋กํ ์ฝ ์ค์์ฑ์ ์๋์ผ๋ก ์ถ๊ฐํฉ๋๋ค.
๐ ๋งคํฌ๋ก ํ์ฅ์ ์๋ ๋ฐฉ์
Swift๊ฐ ๋งคํฌ๋ก๋ฅผ ํ์ฅํ๋ ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ์ฝ๋ ํ์ฑ: ์ปดํ์ผ๋ฌ๊ฐ ์ฝ๋๋ฅผ ์ฝ๊ณ ์ถ์ ๊ตฌ๋ฌธ ํธ๋ฆฌ(AST)๋ผ๋ ๋ฉ๋ชจ๋ฆฌ ํํ์ ์์ฑํฉ๋๋ค.
- ๋งคํฌ๋ก ์คํ: ์ปดํ์ผ๋ฌ๊ฐ AST์ ์ผ๋ถ๋ฅผ ๋งคํฌ๋ก ๊ตฌํ์ ์ ์กํ์ฌ ํ์ฅ์ ์์ฒญํฉ๋๋ค.
- ์ฝ๋ ๋์ฒด: ํ์ฅ๋ ์ฝ๋๋ก ๋งคํฌ๋ก ํธ์ถ์ ๋์ฒดํฉ๋๋ค.
- ์ปดํ์ผ ์งํ: ํ์ฅ๋ ์์ค ์ฝ๋๋ก ์ปดํ์ผ์ ๊ณ์ํฉ๋๋ค.
์๋ฅผ ๋ค์ด, #fourCharacterCode("ABCD") ๋งคํฌ๋ก๋ 4๊ธ์ ๋ฌธ์์ด์ ํด๋นํ๋ ๋ถํธ ์๋ 32๋นํธ ์ ์๋ก ๋ณํํฉ๋๋ค:
let magicNumber = #fourCharacterCode("ABCD")
// ํ์ฅ ํ: let magicNumber = 1145258561 as UInt32
๋งคํฌ๋ก ํ์ฅ ์ ์ค์ํ ๋ณด์ ์กฐ์น:
- ๋งคํฌ๋ก ๊ตฌํ์ ๋งคํฌ๋ก ํธ์ถ ๋ถ๋ถ์ AST๋ง ์ ๊ทผ ๊ฐ๋ฅ
- ํ์ผ ์์คํ ์ด๋ ๋คํธ์ํฌ์ ์ ๊ทผํ ์ ์๋ ์๋๋ฐ์ค ํ๊ฒฝ์์ ์คํ
- ๋งคํฌ๋ก ํ์ฅ์ ์ ๋ ฅ ์ธ์ ์ธ๋ถ ์์(์: ํ์ฌ ์๊ฐ)์ ์์กดํ์ง ์์์ผ ํจ
๐ ๋งคํฌ๋ก ์ ์ธํ๊ธฐ
๋งคํฌ๋ก๋ macro ํค์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ธํฉ๋๋ค:
@attached(member)
@attached(extension, conformances: OptionSet)
public macro OptionSet<RawType>() =
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
๋งคํฌ๋ก ์ ์ธ์ ์ฃผ์ ์์:
- ๋งคํฌ๋ก ์ญํ (Role): @attached ๋๋ @freestanding ์์ฑ์ผ๋ก ์ง์
- ์ด๋ฆ๊ณผ ์ ๋ค๋ฆญ ๋งค๊ฐ๋ณ์: ์ ์์์์๋ OptionSet<RawType>
- ๊ตฌํ ์์น: #externalMacro ๋งคํฌ๋ก๋ก ๊ตฌํ ๋ชจ๋๊ณผ ํ์ ์ ์ง์
๋งคํฌ๋ก ์ญํ ์ง์
์ฒจ๋ถ ๋งคํฌ๋ก๋ @attached ์์ฑ์ผ๋ก ์ญํ ์ ์ง์ ํฉ๋๋ค:
@attached(member, names: named(RawValue), named(rawValue),
named(`init`), arbitrary)
@attached(extension, conformances: OptionSet)
public macro OptionSet<RawType>() =
#externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
๋ ๋ฆฝ ๋งคํฌ๋ก๋ @freestanding ์์ฑ์ผ๋ก ์ญํ ์ ์ง์ ํฉ๋๋ค:
@freestanding(expression)
public macro line<T: ExpressibleByIntegerLiteral>() -> T =
/* ... ๋งคํฌ๋ก ๊ตฌํ ์์น... */
๐ ๋งคํฌ๋ก ๊ตฌํํ๊ธฐ
๋งคํฌ๋ก๋ฅผ ๊ตฌํํ๊ธฐ ์ํด์๋ ๋ ๊ฐ์ง ๊ตฌ์ฑ ์์๊ฐ ํ์ํฉ๋๋ค:
- ๋งคํฌ๋ก ํ์ฅ์ ์ํํ๋ ํ์
- ๋งคํฌ๋ก๋ฅผ API๋ก ๋ ธ์ถํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
Swift Package Manager๋ก ๋งคํฌ๋ก ์์ฑํ๊ธฐ
์ ๋งคํฌ๋ก ํจํค์ง ์์ฑ:
swift package init --type macro
๊ธฐ์กด ํ๋ก์ ํธ์ ๋งคํฌ๋ก ์ถ๊ฐ:
- Package.swift ํ์ผ์์ Swift ๋๊ตฌ ๋ฒ์ ์ 5.9 ์ด์์ผ๋ก ์ค์
- CompilerPluginSupport ๋ชจ๋ ๊ฐ์ ธ์ค๊ธฐ
- ์ต์ ๋ฐฐํฌ ๋์์ macOS 10.15 ํฌํจ
- ๋งคํฌ๋ก ๊ตฌํ ๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ๊ฒ ์ถ๊ฐ
// swift-tools-version: 5.9
import PackageDescription
import CompilerPluginSupport
let package = Package(
name: "MyPackage",
platforms: [.iOS(.v17), .macOS(.v13)],
// ...
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax", from: "509.0.0")
],
targets: [
// ๋งคํฌ๋ก ๊ตฌํ ํ๊ฒ
.macro(
name: "MyProjectMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
// ๋งคํฌ๋ก๋ฅผ API๋ก ๋
ธ์ถํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ๊ฒ
.target(name: "MyProject", dependencies: ["MyProjectMacros"]),
]
)
๋งคํฌ๋ก ๊ตฌํ ์์
#fourCharacterCode ๋งคํฌ๋ก ๊ตฌํ:
import SwiftSyntax
import SwiftSyntaxMacros
public struct FourCharacterCode: 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("์ ์ ๋ฌธ์์ด์ด ํ์ํฉ๋๋ค")
}
let string = literalSegment.content.text
guard let result = fourCharacterCode(for: string) else {
throw CustomError.message("์ ํจํ์ง ์์ 4๊ธ์ ์ฝ๋์
๋๋ค")
}
return "\(raw: result) as UInt32"
}
}
private func fourCharacterCode(for characters: String) -> UInt32? {
guard characters.count == 4 else { return nil }
var result: UInt32 = 0
for character in characters {
result = result << 8
guard let asciiValue = character.asciiValue else { return nil }
result += UInt32(asciiValue)
}
return result
}
enum CustomError: Error { case message(String) }
๋งคํฌ๋ก ํ๋ฌ๊ทธ์ธ ์ง์ ์ :
import SwiftCompilerPlugin
@main
struct MyProjectMacros: CompilerPlugin {
var providingMacros: [Macro.Type] = [FourCharacterCode.self]
}
๐ ๋งคํฌ๋ก ๊ฐ๋ฐ๊ณผ ๋๋ฒ๊น
๋งคํฌ๋ก๋ ํ ์คํธ ๊ธฐ๋ฐ ๊ฐ๋ฐ์ ์ ํฉํฉ๋๋ค:
- ์ธ๋ถ ์ํ์ ์์กดํ์ง ์์
- ๋ฌธ์์ด์์ ์ฝ๊ฒ ๊ตฌ๋ฌธ ๋ ธ๋ ์์ฑ ๊ฐ๋ฅ
- AST์ description ์์ฑ์ผ๋ก ๊ฒฐ๊ณผ ํ์ธ ๊ฐ๋ฅ
ํ ์คํธ ์์ :
let source: SourceFileSyntax =
"""
let abcd = #fourCharacterCode("ABCD")
"""
let file = BasicMacroExpansionContext.KnownSourceFile(
moduleName: "MyModule",
fullFilePath: "test.swift"
)
let context = BasicMacroExpansionContext(sourceFiles: [source: file])
let transformedSF = source.expand(
macros:["fourCharacterCode": FourCharacterCode.self],
in: context
)
let expectedDescription =
"""
let abcd = 1145258561 as UInt32
"""
precondition(transformedSF.description == expectedDescription)
๐ ์ค์ฉ์ ์ธ ๋งคํฌ๋ก ํ์ฉ ์ฌ๋ก
๋งคํฌ๋ก๋ ๋ค์๊ณผ ๊ฐ์ ์์ ์ ํนํ ์ ์ฉํฉ๋๋ค:
- ์์ฉ๊ตฌ ์ฝ๋ ์์ฑ: JSON ์ง๋ ฌํ, ์ฝ๋ฉ ํค ์์ฑ ๋ฑ
- ๋์์ธ ํจํด ๊ตฌํ: ์ฑ๊ธํค, ํฉํ ๋ฆฌ, ๋น๋ ํจํด ๋ฑ
- ํ๋กํ ์ฝ ์ค์ ์๋ํ: Equatable, Hashable, Codable ๋ฑ
- ๋ก๊น ๋ฐ ๋๋ฒ๊น ์ง์: ํจ์ ์ถ์ , ์ฑ๋ฅ ์ธก์ ๋ฑ
- DSL(Domain-Specific Language) ๋ง๋ค๊ธฐ: SwiftUI ์คํ์ผ์ ์ ์ธ์ ๊ตฌ๋ฌธ ๋ฑ
๐ ๋งคํฌ๋ก ์ฌ์ฉ ์ ์ฃผ์์ฌํญ
- ๋งคํฌ๋ก๋ฅผ ๊ณผ๋ํ๊ฒ ์ฌ์ฉํ์ง ๋ง์ธ์: ํ์ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉ
- ๋งคํฌ๋ก ํ์ฅ ์ฝ๋๋ฅผ ์ดํดํ์ธ์: ์์ฑ๋ ์ฝ๋๊ฐ ๋ฌด์์ธ์ง ์์์ผ ํจ
- ๋งคํฌ๋ก ๋ฌธ์ํ๋ฅผ ์ฒ ์ ํ ํ์ธ์: ๋์ ๋ฐฉ์๊ณผ ์์ฑํ๋ ์ฝ๋๋ฅผ ๋ช ํํ ์ค๋ช
- ํ ์คํธ๋ฅผ ์์ฑํ์ธ์: ๋ค์ํ ์ ๋ ฅ์ ๋ํ ๋งคํฌ๋ก ๋์์ ํ ์คํธ
- ๋งคํฌ๋ก ๊ตฌํ์์ ์ธ๋ถ ์ํ์ ์์กดํ์ง ๋ง์ธ์: ๊ฒฐ์ ์ ์ธ ๊ฒฐ๊ณผ ์์ฑ
๐ ์ ๋ฆฌ
Swift ๋งคํฌ๋ก๋ ์ปดํ์ผ ์๊ฐ์ ์ฝ๋๋ฅผ ์์ฑํ์ฌ ๋ฐ๋ณต์ ์ธ ์์ ์ ์๋ํํ๊ณ ์ฝ๋ ํ์ง์ ํฅ์์ํค๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ๋ ๋ฆฝ ๋งคํฌ๋ก์ ์ฒจ๋ถ ๋งคํฌ๋ก๋ผ๋ ๋ ๊ฐ์ง ์ ํ์ ํตํด ๋ค์ํ ์ฌ์ฉ ์ฌ๋ก๋ฅผ ์ง์ํ๋ฉฐ, ์๊ฒฉํ ์์ ์ฑ ๋ณด์ฅ์ ์ ๊ณตํฉ๋๋ค.
๋งคํฌ๋ก๋ฅผ ์ ์ ํ ํ์ฉํ๋ฉด:
- ์ฝ๋ ์ค๋ณต์ ์ค์ผ ์ ์์ต๋๋ค
- ์ค์๋ฅผ ๋ฐฉ์งํ ์ ์์ต๋๋ค
- ์ฝ๋ ๊ฐ๋ ์ฑ์ ํฅ์์ํฌ ์ ์์ต๋๋ค
- ๊ฐ๋ฐ์ ์์ฐ์ฑ์ ๋์ผ ์ ์์ต๋๋ค
Swift ๋งคํฌ๋ก๋ ์์ง ๋ฐ์ ์ค์ธ ๊ธฐ๋ฅ์ด์ง๋ง, ์ด๋ฏธ ๋ง์ ์ ์ฌ๋ ฅ์ ๋ณด์ฌ์ฃผ๊ณ ์์ต๋๋ค. ๋ณต์กํ ์์ฉ๊ตฌ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ๊ฒ ๋ง๋ค๊ณ ๋ ํํ๋ ฅ ์๋ API๋ฅผ ์ค๊ณํ๋ ๋ฐ ๋งคํฌ๋ก๋ฅผ ํ์ฉํด ๋ณด์ธ์!
'๐ฅ Bread Basics > Swift' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Swift ๊ณต์ ๋ฌธ์ ์ ๋ฆฌ - ํ์ฅ (Extensions) (0) | 2025.04.13 |
---|---|
Swift ๊ณต์ ๋ฌธ์ ์ ๋ฆฌ - ์ค์ฒฉ๋ ํ์ (Nested Types) (1) | 2025.04.12 |
Swift ๊ณต์ ๋ฌธ์ ์ ๋ฆฌ - ํ์ ์บ์คํ (Type Casting) (0) | 2025.04.12 |
Obj-C ๊ณต์ ๋ฌธ์ ์ ๋ฆฌ - ๊ฐ์ฒด, ํด๋์ค, ๋ฉ์์ง (0) | 2025.04.11 |
Swift ๊ณต์ ๋ฌธ์ ์ ๋ฆฌ - ๋์์ฑ (Concurrency) (0) | 2025.04.11 |
Swift ๊ณต์ ๋ฌธ์ ์ ๋ฆฌ - ์๋ฌ ์ฒ๋ฆฌ (Error Handling) (0) | 2025.04.11 |
Swift ๊ณต์ ๋ฌธ์ ์ ๋ฆฌ - ์ต์ ๋ ์ฒด์ด๋ (Optional Chaining) (0) | 2025.04.11 |
Swift ๊ณต์ ๋ฌธ์ ์ ๋ฆฌ - ์ด๊ธฐํ ํด์ (Deinitialization) (1) | 2025.04.11 |