Swift 5.1 中引入了@dynamicMemberLookup特点包装器,它能够让咱们在运用点语法拜访类或结构体的某些值或许经过下标拜访字典或其他容器的值时不需求运用具体的特点或下标语法。接下来将从以下几个方面对@dynamicMemberLookup进行详细分析。

语法特性

@dynamicMemberLookup是一个特点包装器 (Property Wrapper),它能够用于类、结构体和枚举上,用于完成动态成员的拜访。它的语法如下所示:

@dynamicMemberLookup
struct SomeStruct {
    subscript(dynamicMember member: String) -> Int {
        // ...
    }
}

其中,@dynamicMemberLookup特点包装器需求放在结构体、类或枚举的界说前面。在上面的示例中,咱们为SomeStruct结构体添加了一个subscript(dynamicMember:) -> Int的下标办法,该下标办法接受一个字符串类型的成员称号,并回来一个整数。

咱们能够经过点语法来拜访SomeStruct结构体的成员,就像拜访常规特点一样,例如:

var structInstance = SomeStruct()
let someValue = structInstance.someMember // 点语法拜访成员

效果

在许多情况下,咱们需求在运行时拜访「不存在」的特点或许下标,而这些特点或下标可能是根据某些条件动态生成的。在这种情况下,运用@dynamicMemberLookup能够完成动态拜访成员,避免了在编写代码时就有必要知道特点或下标称号的烦恼。

运用@dynamicMemberLookup,咱们能够在更为动态的环境中编写代码,一起也能够供给一种更为友爱的语法,因而一般能够:

  1. 减少代码的复杂度
  2. 增强代码的可读性和保护性。
  3. 减少由于类型转化和判别而导致的运行时过错。

运用场景

下面是@dynamicMemberLookup能够用到的几个场景:

  1. 拜访 JSON 数据

在解析 JSON 数据时,咱们一般需求拜访 JSON 数据的成员,这时运用@dynamicMemberLookup特点包装器能够方便地完成动态拜访 JSON 数据。例如:

@dynamicMemberLookup
struct JSON {
    private let value: Any
    init(_ value: Any) {
        self.value = value
    }
    subscript(dynamicMember member: String) -> JSON {
        if let dict = value as? [String: Any], let value = dict[member] {
            return JSON(value)
        } else {
            return JSON(NSNull())
        }
    }
    subscript(index: Int) -> JSON {
        if let array = value as? [Any], array.indices.contains(index) {
            return JSON(array[index])
        } else {
            return JSON(NSNull())
        }
    }
}
let json = JSON(["name": "Tom", "age": 20, "hobbies": ["reading", "swimming"]])
print(json.name) // 输出 "Tom"
print(json.age) // 输出 20
print(json.hobbies[1]) // 输出 "swimming"

在上面的比如中,咱们运用@dynamicMemberLookup特点包装器为JSON结构体动态添加了特点,用于拜访 JSON 数据的成员。

  1. 动态 Swift KeyPaths

运用SwiftKeyPath能够让咱们方便地拜访目标的特点或办法,而运用@dynamicMemberLookup特点包装器能够完成经过字符串拜访KeyPath。例如:

@dynamicMemberLookup
struct Person {
    var name: String
    var age: Int
    subscript(dynamicMember member: String) -> KeyPath<Person, String>? {
        if member == "name" {
            return .name
        } else {
            return nil
        }
    }
}
let person = Person(name: "Tom", age: 20)
let nameKeyPath = person.name
let name = person[keyPath: nameKeyPath] // 输出 "Tom"

在上面的比如中,咱们运用@dynamicMemberLookup特点包装器为Person结构体动态添加了特点,用于拜访KeyPath

  1. 拜访不知道类型的目标

在一些动态类型的语言中,经常会出现需求拜访一个不知道类型的目标的特点或办法的场景。在Swift中,咱们能够运用@dynamicMemberLookup特点包装器来完成类似的拜访。例如:

@dynamicMemberLookup
struct AnyValue {
    private let value: Any
    init(_ value: Any) {
        self.value = value
    }
    subscript<T>(dynamicMember member: String) -> T? {
        return (value as? [String: Any])?[member] as? T
    }
}
let dict = ["name": "Tom", "age": 20, "hobbies": ["reading", "swimming"]]
let anyValue = AnyValue(dict)
let name = anyValue.name // 输出 "Tom"
let age = anyValue.age // 输出 20
let hobbies = anyValue.hobbies // 输出 ["reading", "swimming"]

在上面的比如中,咱们运用@dynamicMemberLookup特点包装器为AnyValue结构体动态添加了特点,用于拜访某个特点或办法并回来一个不知道类型的值。

运用圈套

在运用@dynamicMemberLookup时,需求注意以下几个圈套:

  1. dynamicMember有必要是字符串类型,不能运用其他类型,如整数或枚举界说等。
  2. 运用@dynamicMemberLookup最好是最后的手段,由于在编写代码时有必要动态推测拜访的目标的结构,然后可能导致意外行为。
  3. 尽管@dynamicMemberLookup能够让代码看起来更简洁,但是有时它可能会隐藏一些性能问题,如频繁的类型转化和判别会导致性能低下。