属性包装器 (Property Wrappers)
基本概念
属性包装器是 Swift 5.1 引入的特性,允许你将通用的属性访问逻辑封装到可重用的包装器中。通过 @propertyWrapper 属性标记,可以创建自定义的属性行为。
基础语法
@propertyWrapper
struct MyWrapper<T> {
private var value: T
init(wrappedValue: T) {
self.value = wrappedValue
}
var wrappedValue: T {
get { value }
set { value = newValue }
}
}
// 使用
struct MyStruct {
@MyWrapper var property: String = "Hello"
}
核心组件详解
1. wrappedValue - 包装值
@propertyWrapper
struct Capitalized {
private var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.capitalized }
}
init(wrappedValue: String) {
self.value = wrappedValue.capitalized
}
}
struct Person {
@Capitalized var firstName: String = ""
@Capitalized var lastName: String = ""
}
var person = Person()
person.firstName = "john" // 自动转换为 "John"
person.lastName = "doe" // 自动转换为 "Doe"
print("\(person.firstName) \(person.lastName)") // "John Doe"
2. projectedValue - 投影值
@propertyWrapper
struct Logged<T> {
private var value: T
private(set) var accessCount = 0
init(wrappedValue: T) {
self.value = wrappedValue
}
var wrappedValue: T {
get {
accessCount += 1
return value
}
set {
value = newValue
}
}
// 投影值:通过 $ 前缀访问
var projectedValue: Int {
accessCount
}
}
struct DataModel {
@Logged var data: String = "Initial"
}
var model = DataModel()
print(model.data) // "Initial" (访问计数 +1)
print(model.data) // "Initial" (访问计数 +1)
print(model.$data) // 2 (通过 $ 访问投影值)
常用属性包装器实现
1. UserDefaults 包装器
@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T
var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
}
// 使用示例
struct Settings {
@UserDefault("username", defaultValue: "")
static var username: String
@UserDefault("isFirstLaunch", defaultValue: true)
static var isFirstLaunch: Bool
@UserDefault("maxConnections", defaultValue: 5)
static var maxConnections: Int
}
// 使用
Settings.username = "JohnDoe"
print(Settings.username) // "JohnDoe"
print(Settings.isFirstLaunch) // true
2. 数值范围限制包装器
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
private let range: ClosedRange<T>
init(wrappedValue: T, _ range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct GameCharacter {
@Clamped(0...100) var health: Int = 100
@Clamped(0...10) var level: Int = 1
@Clamped(0.0...1.0) var volume: Double = 0.5
}
var character = GameCharacter()
character.health = 150 // 自动限制为 100
character.level = -5 // 自动限制为 0
character.volume = 1.5 // 自动限制为 1.0
print(character.health) // 100
print(character.level) // 0
print(character.volume) // 1.0
3. 延迟初始化包装器
@propertyWrapper
struct Lazy<T> {
private var _value: T?
private let initializer: () -> T
init(wrappedValue: @escaping @autoclosure () -> T) {
self.initializer = wrappedValue
}
var wrappedValue: T {
mutating get {
if _value == nil {
_value = initializer()
print("懒加载初始化")
}
return _value!
}
set {
_value = newValue
}
}
}
struct ExpensiveResource {
@Lazy var data = createExpensiveData()
private func createExpensiveData() -> [String] {
print("创建昂贵的数据...")
return Array(1...1000).map { "Item \($0)" }
}
}
var resource = ExpensiveResource()
// 此时还没有创建数据
print("准备访问数据...")
let firstItem = resource.data.first // 现在才创建数据
4. 线程安全包装器
@propertyWrapper
struct Atomic<T> {
private var value: T
private let queue = DispatchQueue(label: "atomic.queue", attributes: .concurrent)
init(wrappedValue: T) {
self.value = wrappedValue
}
var wrappedValue: T {
get {
queue.sync { value }
}
set {
queue.async(flags: .barrier) { [weak self] in
self?.value = newValue
}
}
}
}
class Counter {
@Atomic var count: Int = 0
func increment() {
count += 1
}
}
// 多线程安全的计数器
let counter = Counter()
DispatchQueue.concurrentPerform(iterations: 1000) { _ in
counter.increment()
}
高级特性
1. 带参数的属性包装器
@propertyWrapper
struct Trimmed {
private var value: String = ""
private let characterSet: CharacterSet
init(wrappedValue: String, _ characterSet: CharacterSet = .whitespacesAndNewlines) {
self.characterSet = characterSet
self.value = wrappedValue.trimmingCharacters(in: characterSet)
}
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: characterSet) }
}
}
struct FormData {
@Trimmed var name: String = ""
@Trimmed(.punctuationCharacters) var cleanText: String = ""
}
var form = FormData()
form.name = " John Doe " // 自动去除空白字符
form.cleanText = "Hello, World!" // 自动去除标点符号
print("'\(form.name)'") // "'John Doe'"
print("'\(form.cleanText)'") // "'Hello World'"
2. 组合属性包装器
@propertyWrapper
struct ValidatedEmail {
private var value: String = ""
var wrappedValue: String {
get { value }
set {
if isValidEmail(newValue) {
value = newValue
} else {
print("无效的邮箱地址: \(newValue)")
}
}
}
private func isValidEmail(_ email: String) -> Bool {
let emailRegex = #"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"#
return email.range(of: emailRegex, options: .regularExpression) != nil
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
struct User {
@Trimmed @ValidatedEmail var email: String = ""
// 注意:多个包装器的组合需要仔细考虑顺序
}
结果构建器 (Result Builders)
基本概念
结果构建器是 Swift 5.4 引入的特性(前身是 Function Builders),允许你创建领域特定语言(DSL)。它可以将一系列表达式转换为单一的结果。
基础语法
@resultBuilder
struct StringBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined(separator: " ")
}
}
// 使用结果构建器
@StringBuilder
func makeMessage() -> String {
"Hello"
"World"
"from"
"Swift"
}
print(makeMessage()) // "Hello World from Swift"
核心方法详解
1. buildBlock - 构建块
@resultBuilder
struct ArrayBuilder<T> {
static func buildBlock(_ components: T...) -> [T] {
Array(components)
}
}
@ArrayBuilder<Int>
func buildNumbers() -> [Int] {
1
2
3
4
5
}
print(buildNumbers()) // [1, 2, 3, 4, 5]
2. buildIf - 条件构建
@resultBuilder
struct ConditionalBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined(separator: "\n")
}
static func buildIf(_ component: String?) -> String {
component ?? ""
}
}
@ConditionalBuilder
func makeConditionalMessage(includeGreeting: Bool) -> String {
if includeGreeting {
"Hello!"
}
"This is a message."
"End of message."
}
print(makeConditionalMessage(includeGreeting: true))
// Hello!
// This is a message.
// End of message.
print(makeConditionalMessage(includeGreeting: false))
// This is a message.
// End of message.
3. buildEither - 分支构建
@resultBuilder
struct EitherBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined(separator: "\n")
}
static func buildEither(first component: String) -> String {
"First: \(component)"
}
static func buildEither(second component: String) -> String {
"Second: \(component)"
}
}
@EitherBuilder
func makeEitherMessage(useFirst: Bool) -> String {
if useFirst {
"This is the first option"
} else {
"This is the second option"
}
}
print(makeEitherMessage(useFirst: true)) // "First: This is the first option"
print(makeEitherMessage(useFirst: false)) // "Second: This is the second option"
4. buildArray - 数组构建
@resultBuilder
struct LoopBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined(separator: "\n")
}
static func buildArray(_ components: [String]) -> String {
components.enumerated().map { index, item in
"\(index + 1). \(item)"
}.joined(separator: "\n")
}
}
@LoopBuilder
func makeList() -> String {
for fruit in ["Apple", "Banana", "Orange"] {
"I like \(fruit)"
}
}
print(makeList())
// 1\. I like Apple
// 2\. I like Banana
// 3\. I like Orange
实际应用示例
1. HTML 构建器
@resultBuilder
struct HTMLBuilder {
static func buildBlock(_ components: HTMLElement...) -> HTMLElement {
HTMLElement(tag: "div", content: components.map(\.html).joined())
}
static func buildIf(_ component: HTMLElement?) -> HTMLElement {
component ?? HTMLElement(tag: "div", content: "")
}
static func buildEither(first component: HTMLElement) -> HTMLElement {
component
}
static func buildEither(second component: HTMLElement) -> HTMLElement {
component
}
static func buildArray(_ components: [HTMLElement]) -> HTMLElement {
HTMLElement(tag: "div", content: components.map(\.html).joined())
}
}
struct HTMLElement {
let tag: String
let content: String
let attributes: [String: String]
init(tag: String, content: String, attributes: [String: String] = [:]) {
self.tag = tag
self.content = content
self.attributes = attributes
}
var html: String {
let attrs = attributes.map { key, value in
" \(key)=\"\(value)\""
}.joined()
return "<\(tag)\(attrs)>\(content)</\(tag)>"
}
}
// HTML 元素构造函数
func h1(_ content: String, attributes: [String: String] = [:]) -> HTMLElement {
HTMLElement(tag: "h1", content: content, attributes: attributes)
}
func p(_ content: String, attributes: [String: String] = [:]) -> HTMLElement {
HTMLElement(tag: "p", content: content, attributes: attributes)
}
func div(@HTMLBuilder content: () -> HTMLElement) -> HTMLElement {
HTMLElement(tag: "div", content: content().html)
}
func ul(@HTMLBuilder content: () -> HTMLElement) -> HTMLElement {
HTMLElement(tag: "ul", content: content().html)
}
func li(_ content: String) -> HTMLElement {
HTMLElement(tag: "li", content: content)
}
// 使用示例
@HTMLBuilder
func buildWebPage(showWelcome: Bool) -> HTMLElement {
h1("我的网站")
if showWelcome {
p("欢迎访问我的网站!")
}
div {
p("这是一个段落")
ul {
for item in ["项目1", "项目2", "项目3"] {
li(item)
}
}
}
}
let webpage = buildWebPage(showWelcome: true)
print(webpage.html)
2. SQL 查询构建器
@resultBuilder
struct SQLBuilder {
static func buildBlock(_ components: SQLComponent...) -> String {
components.map(\.sql).joined(separator: " ")
}
static func buildIf(_ component: SQLComponent?) -> SQLComponent {
component ?? SQLComponent("")
}
}
struct SQLComponent {
let sql: String
init(_ sql: String) {
self.sql = sql
}
}
func SELECT(_ columns: String...) -> SQLComponent {
SQLComponent("SELECT \(columns.joined(separator: ", "))")
}
func FROM(_ table: String) -> SQLComponent {
SQLComponent("FROM \(table)")
}
func WHERE(_ condition: String) -> SQLComponent {
SQLComponent("WHERE \(condition)")
}
func ORDER_BY(_ column: String) -> SQLComponent {
SQLComponent("ORDER BY \(column)")
}
func LIMIT(_ count: Int) -> SQLComponent {
SQLComponent("LIMIT \(count)")
}
@SQLBuilder
func buildUserQuery(includeOrder: Bool = false) -> String {
SELECT("id", "name", "email")
FROM("users")
WHERE("active = 1")
if includeOrder {
ORDER_BY("name")
}
LIMIT(10)
}
print(buildUserQuery(includeOrder: true))
// SELECT id, name, email FROM users WHERE active = 1 ORDER BY name LIMIT 10
3. 配置构建器
@resultBuilder
struct ConfigurationBuilder {
static func buildBlock(_ components: ConfigurationItem...) -> Configuration {
Configuration(items: components)
}
static func buildIf(_ component: ConfigurationItem?) -> ConfigurationItem {
component ?? ConfigurationItem(key: "", value: "")
}
}
struct ConfigurationItem {
let key: String
let value: String
}
struct Configuration {
let items: [ConfigurationItem]
subscript(key: String) -> String? {
items.first { $0.key == key }?.value
}
}
func setting(_ key: String, _ value: String) -> ConfigurationItem {
ConfigurationItem(key: key, value: value)
}
@ConfigurationBuilder
func buildAppConfig(isDevelopment: Bool) -> Configuration {
setting("appName", "MyApp")
setting("version", "1.0.0")
if isDevelopment {
setting("baseURL", "https://dev.api.example.com")
setting("debugMode", "true")
} else {
setting("baseURL", "https://api.example.com")
setting("debugMode", "false")
}
setting("timeout", "30")
}
let config = buildAppConfig(isDevelopment: true)
print(config["baseURL"]) // Optional("https://dev.api.example.com")
print(config["debugMode"]) // Optional("true")
实际应用场景
1. SwiftUI 中的应用
// SwiftUI 大量使用了结果构建器
struct ContentView: View {
@State private var isVisible = true
var body: some View { // 这里使用了 @ViewBuilder
VStack {
Text("Hello, World!")
if isVisible {
Text("我是可见的")
}
Button("切换可见性") {
isVisible.toggle()
}
}
}
}
2. 数据验证场景
@propertyWrapper
struct Validated<T> {
private var value: T
private let validator: (T) -> Bool
private let errorMessage: String
init(wrappedValue: T, validator: @escaping (T) -> Bool, errorMessage: String) {
self.validator = validator
self.errorMessage = errorMessage
if validator(wrappedValue) {
self.value = wrappedValue
} else {
fatalError(errorMessage)
}
}
var wrappedValue: T {
get { value }
set {
if validator(newValue) {
value = newValue
} else {
print("验证失败: \(errorMessage)")
}
}
}
}
struct UserRegistration {
@Validated(
validator: { $0.count >= 3 },
errorMessage: "用户名至少需要3个字符"
)
var username: String = ""
@Validated(
validator: { $0.contains("@") && $0.contains(".") },
errorMessage: "邮箱格式不正确"
)
var email: String = ""
@Validated(
validator: { $0.count >= 8 },
errorMessage: "密码至少需要8个字符"
)
var password: String = ""
}
3. API 构建场景
@resultBuilder
struct APIBuilder {
static func buildBlock(_ components: APIComponent...) -> APIRequest {
APIRequest(components: components)
}
}
protocol APIComponent {
func apply(to request: inout URLRequest)
}
struct APIRequest {
private let components: [APIComponent]
init(components: [APIComponent]) {
self.components = components
}
func build(baseURL: URL) -> URLRequest {
var request = URLRequest(url: baseURL)
components.forEach { $0.apply(to: &request) }
return request
}
}
struct HTTPMethod: APIComponent {
let method: String
func apply(to request: inout URLRequest) {
request.httpMethod = method
}
}
struct Header: APIComponent {
let key: String
let value: String
func apply(to request: inout URLRequest) {
request.setValue(value, forHTTPHeaderField: key)
}
}
struct JSONBody: APIComponent {
let data: Data
func apply(to request: inout URLRequest) {
request.httpBody = data
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
}
// 便利函数
func GET() -> HTTPMethod { HTTPMethod(method: "GET") }
func POST() -> HTTPMethod { HTTPMethod(method: "POST") }
func header(_ key: String, _ value: String) -> Header { Header(key: key, value: value) }
func jsonBody<T: Encodable>(_ object: T) -> JSONBody? {
guard let data = try? JSONEncoder().encode(object) else { return nil }
return JSONBody(data: data)
}
@APIBuilder
func buildLoginRequest() -> APIRequest {
POST()
header("Content-Type", "application/json")
header("Authorization", "Bearer token")
}
let baseURL = URL(string: "https://api.example.com/login")!
let request = buildLoginRequest().build(baseURL: baseURL)
最佳实践与注意事项
属性包装器最佳实践
1. 命名约定
// ✅ 好的命名
@propertyWrapper struct Capitalized { }
@propertyWrapper struct UserDefault { }
@propertyWrapper struct Atomic { }
// ❌ 避免的命名
@propertyWrapper struct MyWrapper { }
@propertyWrapper struct Thing { }
2. 初始化器设计
@propertyWrapper
struct Clamped<T: Comparable> {
// ✅ 提供多种初始化方式
init(wrappedValue: T, _ range: ClosedRange<T>) {
// 实现
}
init(_ range: ClosedRange<T>) {
// 提供默认值的便利初始化器
}
}
// 使用
struct Example {
@Clamped(0...100) var percentage: Int = 50 // 使用第一个初始化器
@Clamped(0...10) var level: Int // 使用第二个初始化器,需要后续赋值
}
3. 投影值的合理使用
@propertyWrapper
struct Published<T> {
private var value: T
var wrappedValue: T {
get { value }
set {
value = newValue
publisher.send(newValue)
}
}
// ✅ 投影值提供额外功能
var projectedValue: AnyPublisher<T, Never> {
publisher.eraseToAnyPublisher()
}
private let publisher = PassthroughSubject<T, Never>()
init(wrappedValue: T) {
self.value = wrappedValue
}
}
结果构建器最佳实践
1. 完整的方法实现
@resultBuilder
struct CompleteBuilder {
// 基础构建
static func buildBlock(_ components: String...) -> String {
components.joined(separator: "\n")
}
// 条件构建
static func buildIf(_ component: String?) -> String {
component ?? ""
}
// 分支构建
static func buildEither(first component: String) -> String {
component
}
static func buildEither(second component: String) -> String {
component
}
// 数组构建(循环)
static func buildArray(_ components: [String]) -> String {
components.joined(separator: "\n")
}
// 可选构建
static func buildOptional(_ component: String?) -> String {
component ?? ""
}
}
2. 类型安全
// ✅ 类型安全的构建器
@resultBuilder
struct TypeSafeBuilder<T> {
static func buildBlock(_ components: T...) -> [T] {
Array(components)
}
}
// ❌ 避免类型不安全
@resultBuilder
struct UnsafeBuilder {
static func buildBlock(_ components: Any...) -> String {
// 可能导致运行时错误
components.map { "\($0)" }.joined()
}
}
性能考虑
1. 属性包装器性能
// ✅ 高效的实现
@propertyWrapper
struct Efficient<T> {
private var _value: T
var wrappedValue: T {
get { _value }
set { _value = newValue }
}
}
// ❌ 低效的实现
@propertyWrapper
struct Inefficient<T> {
var wrappedValue: T {
get {
// 每次访问都进行昂贵的计算
return performExpensiveOperation()
}
set {
// 复杂的设置逻辑
}
}
}
2. 结果构建器性能
// ✅ 高效的字符串构建
@resultBuilder
struct EfficientStringBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined() // 直接连接
}
}
// ❌ 低效的实现
@resultBuilder
struct InefficientStringBuilder {
static func buildBlock(_ components: String...) -> String {
var result = ""
for component in components {
result += component // 多次字符串拷贝
}
return result
}
}
调试技巧
1. 添加调试信息
@propertyWrapper
struct DebugWrapper<T> {
private var value: T
private let name: String
init(wrappedValue: T, _ name: String = #function) {
self.value = wrappedValue
self.name = name
print("🐛 初始化 \(name): \(wrappedValue)")
}
var wrappedValue: T {
get {
print("🐛 读取 \(name): \(value)")
return value
}
set {
print("🐛 设置 \(name): \(value) -> \(newValue)")
value = newValue
}
}
}
2. 错误处理
@propertyWrapper
struct SafeWrapper<T> {
private var value: T
private let validator: (T) -> Bool
var wrappedValue: T {
get { value }
set {
guard validator(newValue) else {
print("⚠️ 验证失败,保持原值: \(value)")
return
}
value = newValue
}
}
init(wrappedValue: T, validator: @escaping (T) -> Bool) {
self.validator = validator
if validator(wrappedValue) {
self.value = wrappedValue
} else {
fatalError("初始值验证失败")
}
}
}
这两个特性是现代 Swift 开发中非常重要的工具,它们可以显著提高代码的可读性、可维护性和复用性。在实际项目中,合理使用这些特性可以创建更优雅和类型安全的 API。