๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿฅ– Bread Basics/Swift

Swift ๊ณต์‹ ๋ฌธ์„œ ์ •๋ฆฌ - ๋งคํฌ๋กœ (Macros)

by BreadDev 2025. 4. 11.
728x90

์•ˆ๋…•ํ•˜์„ธ์š”! ์˜ค๋Š˜์€ 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๊ฐ€ ๋งคํฌ๋กœ๋ฅผ ํ™•์žฅํ•˜๋Š” ๊ณผ์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. ์ฝ”๋“œ ํŒŒ์‹ฑ: ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ฝ๊ณ  ์ถ”์ƒ ๊ตฌ๋ฌธ ํŠธ๋ฆฌ(AST)๋ผ๋Š” ๋ฉ”๋ชจ๋ฆฌ ํ‘œํ˜„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  2. ๋งคํฌ๋กœ ์‹คํ–‰: ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ AST์˜ ์ผ๋ถ€๋ฅผ ๋งคํฌ๋กœ ๊ตฌํ˜„์— ์ „์†กํ•˜์—ฌ ํ™•์žฅ์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
  3. ์ฝ”๋“œ ๋Œ€์ฒด: ํ™•์žฅ๋œ ์ฝ”๋“œ๋กœ ๋งคํฌ๋กœ ํ˜ธ์ถœ์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.
  4. ์ปดํŒŒ์ผ ์ง„ํ–‰: ํ™•์žฅ๋œ ์†Œ์Šค ์ฝ”๋“œ๋กœ ์ปดํŒŒ์ผ์„ ๊ณ„์†ํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, #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 = 
        /* ... ๋งคํฌ๋กœ ๊ตฌํ˜„ ์œ„์น˜... */

๐Ÿ“Œ ๋งคํฌ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ

๋งคํฌ๋กœ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‘ ๊ฐ€์ง€ ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

  1. ๋งคํฌ๋กœ ํ™•์žฅ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํƒ€์ž…
  2. ๋งคํฌ๋กœ๋ฅผ API๋กœ ๋…ธ์ถœํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

Swift Package Manager๋กœ ๋งคํฌ๋กœ ์ƒ์„ฑํ•˜๊ธฐ

์ƒˆ ๋งคํฌ๋กœ ํŒจํ‚ค์ง€ ์ƒ์„ฑ:

swift package init --type macro

๊ธฐ์กด ํ”„๋กœ์ ํŠธ์— ๋งคํฌ๋กœ ์ถ”๊ฐ€:

  1. Package.swift ํŒŒ์ผ์—์„œ Swift ๋„๊ตฌ ๋ฒ„์ „์„ 5.9 ์ด์ƒ์œผ๋กœ ์„ค์ •
  2. CompilerPluginSupport ๋ชจ๋“ˆ ๊ฐ€์ ธ์˜ค๊ธฐ
  3. ์ตœ์†Œ ๋ฐฐํฌ ๋Œ€์ƒ์— macOS 10.15 ํฌํ•จ
  4. ๋งคํฌ๋กœ ๊ตฌํ˜„ ๋ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํƒ€๊ฒŸ ์ถ”๊ฐ€
 
// 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)

๐Ÿ“Œ ์‹ค์šฉ์ ์ธ ๋งคํฌ๋กœ ํ™œ์šฉ ์‚ฌ๋ก€

๋งคํฌ๋กœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ž‘์—…์— ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:

  1. ์ƒ์šฉ๊ตฌ ์ฝ”๋“œ ์ƒ์„ฑ: JSON ์ง๋ ฌํ™”, ์ฝ”๋”ฉ ํ‚ค ์ƒ์„ฑ ๋“ฑ
  2. ๋””์ž์ธ ํŒจํ„ด ๊ตฌํ˜„: ์‹ฑ๊ธ€ํ†ค, ํŒฉํ† ๋ฆฌ, ๋นŒ๋” ํŒจํ„ด ๋“ฑ
  3. ํ”„๋กœํ† ์ฝœ ์ค€์ˆ˜ ์ž๋™ํ™”: Equatable, Hashable, Codable ๋“ฑ
  4. ๋กœ๊น… ๋ฐ ๋””๋ฒ„๊น… ์ง€์›: ํ•จ์ˆ˜ ์ถ”์ , ์„ฑ๋Šฅ ์ธก์ • ๋“ฑ
  5. DSL(Domain-Specific Language) ๋งŒ๋“ค๊ธฐ: SwiftUI ์Šคํƒ€์ผ์˜ ์„ ์–ธ์  ๊ตฌ๋ฌธ ๋“ฑ

๐Ÿ“Œ ๋งคํฌ๋กœ ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ

  1. ๋งคํฌ๋กœ๋ฅผ ๊ณผ๋„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”: ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉ
  2. ๋งคํฌ๋กœ ํ™•์žฅ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜์„ธ์š”: ์ƒ์„ฑ๋œ ์ฝ”๋“œ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์•Œ์•„์•ผ ํ•จ
  3. ๋งคํฌ๋กœ ๋ฌธ์„œํ™”๋ฅผ ์ฒ ์ €ํžˆ ํ•˜์„ธ์š”: ๋™์ž‘ ๋ฐฉ์‹๊ณผ ์ƒ์„ฑํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ช…ํ™•ํžˆ ์„ค๋ช…
  4. ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”: ๋‹ค์–‘ํ•œ ์ž…๋ ฅ์— ๋Œ€ํ•œ ๋งคํฌ๋กœ ๋™์ž‘์„ ํ…Œ์ŠคํŠธ
  5. ๋งคํฌ๋กœ ๊ตฌํ˜„์—์„œ ์™ธ๋ถ€ ์ƒํƒœ์— ์˜์กดํ•˜์ง€ ๋งˆ์„ธ์š”: ๊ฒฐ์ •์ ์ธ ๊ฒฐ๊ณผ ์ƒ์„ฑ

๐Ÿ“Œ ์ •๋ฆฌ

Swift ๋งคํฌ๋กœ๋Š” ์ปดํŒŒ์ผ ์‹œ๊ฐ„์— ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์„ ์ž๋™ํ™”ํ•˜๊ณ  ์ฝ”๋“œ ํ’ˆ์งˆ์„ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๋…๋ฆฝ ๋งคํฌ๋กœ์™€ ์ฒจ๋ถ€ ๋งคํฌ๋กœ๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ์œ ํ˜•์„ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ์ง€์›ํ•˜๋ฉฐ, ์—„๊ฒฉํ•œ ์•ˆ์ „์„ฑ ๋ณด์žฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๋งคํฌ๋กœ๋ฅผ ์ ์ ˆํžˆ ํ™œ์šฉํ•˜๋ฉด:

  • ์ฝ”๋“œ ์ค‘๋ณต์„ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
  • ๊ฐœ๋ฐœ์ž ์ƒ์‚ฐ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

Swift ๋งคํฌ๋กœ๋Š” ์•„์ง ๋ฐœ์ „ ์ค‘์ธ ๊ธฐ๋Šฅ์ด์ง€๋งŒ, ์ด๋ฏธ ๋งŽ์€ ์ž ์žฌ๋ ฅ์„ ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณต์žกํ•œ ์ƒ์šฉ๊ตฌ ์ฝ”๋“œ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ๋” ํ‘œํ˜„๋ ฅ ์žˆ๋Š” API๋ฅผ ์„ค๊ณ„ํ•˜๋Š” ๋ฐ ๋งคํฌ๋กœ๋ฅผ ํ™œ์šฉํ•ด ๋ณด์„ธ์š”!