一、Key Paths 新语法
key-path 通常是用在键值编码(KVC)与键值观察(KVO)上的,KVC、KVO 相关内容可以参考我之前写的这篇文章:Swift - 反射(Reflection)的介绍与使用样例(附KVC介绍)
1.Swift 3 之前使用的是 String 类型的 key-Path
//用户类 class User: NSObject{ @objc var name:String = "" //姓名 @objc var age:Int = 0 //年龄 } //创建一个User实例对象 let user1 = User() user1.name = "hangge" user1.age = 100 //使用KVC取值 let name = user1.value(forKey: "name") print(name) //使用KVC赋值 user1.setValue("hangge.com", forKey: "name")
具体显示如下:
2.到了 Swift 3 新增了 #keyPath() 写法 使用 #keyPath() 写法,可以避免我们因为拼写错误而引发问题。
//用户类 class User: NSObject{ @objc var name:String = "" //姓名 @objc var age:Int = 0 //年龄 } //创建一个User实例对象 let user1 = User() user1.name = "hangge" user1.age = 100 //使用KVC取值 let name = user1.value(forKeyPath: #keyPath(User.name)) print(name) //使用KVC赋值 user1.setValue("hangge.com", forKeyPath: #keyPath(User.name))
3.Swift 4 中直接用 \ 作为开头创建 KeyPath 新的方式不仅使用更加简单,而且有如下优点:
- 类型可以定义为 class、struct
- 定义类型时无需加上 @objc 等关键字
- 性能更好
- 类型安全和类型推断,例如:user1.value(forKeyPath: #keyPath(User.name)) 返回的类型是 Any,user1[keyPath: \User.name] 直接返回 String 类型
- 可以在所有值类型上使用 (1)比如上面的样例在 Swift4 中可以这么写:
//用户类 class User: NSObject{ var name:String = "" //姓名 var age:Int = 0 //年龄 } //创建一个User实例对象 let user1 = User() user1.name = "hangge" user1.age = 100 //使用KVC取值 let name = user1[keyPath: \User.name] print(name) //使用KVC赋值 user1[keyPath: \User.name] = "hangge.com"
(2)keyPath 定义在外面也是可以的:
let keyPath = \User.name let name = user1[keyPath: keyPath] print(name) user1[keyPath: keyPath] = "hangge.com"
(3)可以使用 appending 方法向已定义的 Key Path 基础上填加新的 Key Path。
let keyPath1 = \User.phone let keyPath2 = keyPath1.appending(path: \.number)
二、类与协议的组合类型
在 Swift 4 中,可以把类(Class)和协议(Protocol)用 & 组合在一起作为一个类型使用。
使用样例1:
protocol MyProtocol { } class View { } class ViewSubclass: View, MyProtocol { } class MyClass { var delegate: (View & MyProtocol)? } let myClass = MyClass() myClass.delegate = ViewSubclass() //这个编译正常 myClass.delegate = View() //这个编译报错:
具体错误信息如下:
使用样例2:
protocol Shakeable { func shake() } extension UIButton: Shakeable { func shake() { /* ... */ } } extension UISlider: Shakeable { func shake() { /* ... */ } } func shakeEm(controls: [UIControl & Shakeable]) { for control in controls where control.isEnabled { control.shake() } }
三、下标支持泛型
1.下标的返回类型支持泛型 有时候我们会写一些数据容器,Swift 支持通过下标来读写容器中的数据。但是如果容器类中的数据类型定义为泛型,过去下标语法就只能返回 Any,在取出值后需要用 as? 来转换类型。现在 Swift 4 定义下标也可以使用泛型了。
struct GenericDictionary<Key: Hashable, Value> { private var data: [Key: Value] init(data: [Key: Value]) { self.data = data } subscript<T>(key: Key) -> T? { return data[key] as? T } } //字典类型: [String: Any] let earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1]) //自动转换类型,不需要在写 "as? String" let name: String? = earthData["name"] print(name) //自动转换类型,不需要在写 "as? Int" let population: Int? = earthData["population"] print(population)
2.下标类型同样支持泛型
extension GenericDictionary { subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key { var values: [Value] = [] for key in keys { if let value = data[key] { values.append(value) } } return values } } // Array下标 let nameAndMoons = earthData[["moons", "name"]] // [1, "Earth"] // Set下标 let nameAndMoons2 = earthData[Set(["moons", "name"])] // [1, "Earth"]