Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 리펙토링
- ios
- map
- swift documentation
- 애니메이션
- rxswift
- MVVM
- uiscrollview
- Protocol
- 클린 코드
- combine
- tableView
- UICollectionView
- Human interface guide
- swiftUI
- uitableview
- SWIFT
- RxCocoa
- Observable
- clean architecture
- Xcode
- HIG
- 리팩토링
- ribs
- 리펙터링
- Refactoring
- UITextView
- 스위프트
- collectionview
- Clean Code
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] Swift Macro 예제 - URLMacro 본문
(URLMacro 코드는 DougGregor Git repo를 참고하였습니다)
URLMacro 소개
- 보통 URL(string:)을 사용하여 URL을 만들면 run time시점에 해당 string이 URL 형태인지 확인하여 Optional을 반환하지만 swift macro를 사용하면 compile time에 해당 string이 유효한 URL의 형태인지 파악이 가능
- URLMacro의 목적
- 잘못된 URL 형태의 문자열로 URL을 초기화할 때 컴파일 타임에 알 수 있도록 제공
URLMacro 구현
- swift macro 프로젝트 생성 방법은 이전 포스팅 글 참고
- attached 매크로와 freestanding 매크로 중에 URLMacro는 독립적으로 연산자처럼 사용되는 freestanding 매크로이며 expressionMacro이므로 아래처럼 선언
// URLMacro.swift
import Foundation
import SwiftSyntax
import SwiftSyntaxMacros
public struct URLMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
// TODO...
}
}
- 이전 포스팅 글에서 알아본 대로 SwiftSyntax에의해 문자열이 tree형태의 자료 구조로 저장되기 때문에 node로 부터 expression을 획득
- expression은 사용하는쪽에서 입력한 문자열 그대로의 값
guard
let argument = node.argumentList.first?.expression
else { throw CustomError.message("#URL requires a static string literal") }
- 사용하는쪽에서 콤마로 여러개의 문자열을 넘기는 경우도 있을 수 있으므로 단 하나의 문자열만 넘기는지 체크하는 조건도 추가
- String 타입만 넘어오도록 String타입만 받는 조건도 추가
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("#URL requires a static string literal")
}
- 입력받은 URL string값을 URL(string:)에 넣어서 초기화 했을 때 nil이면 컴파일 에러를 발생하고, 만약 nil이 아니면 강제 unwrapping한 값을 리턴
if URL(string: literalSegment.content.text) == nil {
throw CustomError.message("malformed url: \(argument)")
} else {
return "URL(string: \(argument))!"
}
(전체 코드)
import Foundation
import SwiftSyntax
import SwiftSyntaxMacros
public struct URLMacro: 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("#URL requires a static string literal")
}
if URL(string: literalSegment.content.text) == nil {
throw CustomError.message("malformed url: \(argument)")
} else {
return "URL(string: \(argument))!"
}
}
}
Plugin에 위에서 정의한 URLMacro 추가
- swift macro를 처음 만들면 StringifyMacro.self에 관한 매크로가 추가된 상태인데, 이 아래에 URLMacro.self도 추가
// Plugins.swift
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
@main
struct ExMacrosPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
StringifyMacro.self,
URLMacro.self
]
}
매크로의 인터페이스를 정의
- freestanding(expression) 형태이며, 매크로의 이름은 #URL로 선언
- 인터페이스 정의하는 방법은 구체적인 방법은 이전 포스팅 글 참고
// ExMacros.swift
@freestanding(expression)
public macro URL(_ stringLiteral: String) -> URL = #externalMacro(module: "ExMacrosMacros", type: "URLMacro")
만든 매크로 테스트
- 만들 매크로를 테스트 해볼 수 있는 main.swift에 작성
print(#URL("https://ios-development.tistory.com/"))
print(#URL("iOS 앱 개발 알아가기"))
(실행하면 기대동작 확인 완료)
- 잘못된 형식의 URL 문자열에서 컴파일 에러가 발생
* 전체 코드: https://github.com/JK0369/URLMacro
* 참고
'Swift Macro' 카테고리의 다른 글
Comments