相关文档:
apple - Key Path Member Lookup
SwiftUI 和 Swift 5.1 新特性(3) Key Path Member Lookup
Protected
定义一个协议 Lock
,让实现该协议的类拥有lock()
、unlock()
、around()
能力,其中lock()
、unlock()
需要遵循协议的类自定义实现。
|
|
自定义锁 UnfairLock
遵循 Lock
协议,并自定义实现lock()
、unlock()
能力。
|
|
使用 @propertyWrapper
和 @dynamicMemberLookup
两个批注,自定义属性包裹器 Protected
,实现属性的读写安全。
|
|
应用:
|
|
@propertyWrapper
属性包装器是一种通用结构,用于封装属性的读写访问,或添加其他行为。比如限制可用的属性值、向读写访问添加额外逻辑、添加方法等。
Swift 5.1 为创建属性包装器提供了一种新的解决方案,使用@propertyWrapper
批注标记属性包装器。
wrappedValue
:使用@propertyWrapper
的包装器,要求包装器对象必须包含一个被包装的值的非静态属性。
|
|
projectedValue
:@propertyWrapper
提供了另一种语法糖——投影值,通过在属性前加$
前缀来访问属性值。
@dynamicMemberLookup
@dynamicMemberLookup
称为动态成员查找,是Swift的一项功能特性,允许动态成员查找调用看起来像范文类型属性的常规调用。
|
|
name
是从字典中查找的,而不是作为 dic
的属性访问的。
实现支持动态查找的People
类:
- 使用
@dynamicMemberLookup
标注People
的类型; - 实现
subscript(dynamicMember key:)
方法。
|
|
没有使用 @dynamicMemberLookup
标注的情况会报错:
1. KeyPath
KeyPath<Root, Value>
泛型类型,用来表示从 Root
类型到 Value
属性的访问路径。
|
|
这里用到了一个语法 \Person.name
,它的类型是 KeyPath<Person, String>
同一类型
KeyPath<Root, Value>
可以代表多种获取路径:KeyPath<Person, Int>
既可以表示\Person.age
,也可以表示\Person.name.count
。两个 KeyPath 可以拼接成一个新的 KeyPath:
KeyPath1
的类型是KeyPath<Person, String>
,\String.count
的类型是KeyPath<String, Int>
,调用appending
函数后变成了KeyPath<Person, Int>
类型。
|
|
KeyPath
是WriteableKeyPath
的父类,WriteableKeyPath
是ReferenceWritableKeyPath
的父类。KeyPath
的能力是最弱的,只能以只读的方式访问属性。
|
|
WriteableKeyPath
可以对可变属性进行读写操作。将let
改成var
,则person.name
的类型就变成了WriteableKeyPath
,可以直接修改p[keyPath: keyPath] = "lisi"
|
|
ReferenceWritableKeyPath
可以对引用类型的可变属性进行写入。将struct
改成class
,则Person.name
的类型变成ReferenceWritableKeyPath
|
|
2. 动态成员查找 Dynamic Member Lookup
Swift4.2 引入了动态成员查找特性,实现使用静态的语法做动态的查找。
|
|
支持动态成员查找的类型首先需要用 @dynamicMemberLookup
来修饰,然后实现动态查找方法subscript(dynamicMember member: String)
。
直接使用 .property
的语法访问属性貌似静态实则动态,该语法实际上调用的是subscript(dynamicMember member: String)
方法。
3. 成员查找 Key Path Member Lookup
Swift5.1 引入的成员查找。
编译器可以从 KeyPath
查询所有的目标,以及它们的类型:
|
|
person.info.name
可以直接写成 person.name
。
@dynamicMemberLookup
非常适用于包装类型:
|
|
链式调用
通过回传 self
,实现链式调用:
|
|
这种方法被动性比较大,如果 Label
的属性变化,则对应的 setter
方法也得改变。
使用动态成员查找实现:
|
|
创建一个 UILabel
:
线程安全的属性 MutableState
在 Request.swift 文件中,Alamofire 使用 @Protected
标注,实现了一个线程安全的对象 mutableState
:
|
|
MutableState
中定义了很多变量,这些变量可能在异步线程调用:
|
|
比如:
|
|
或者
|
|
AlamofireExtended
Alamofire 扩展包裹器,用来对系统类进行扩展时包裹使用,避免方法入侵。
第一种方式
通过添加全局方法的方式扩展两个新的方法:
|
|
通过这种方式添加的方法,在所有能访问到该扩展的地方都可以调用。调用时,扩展的方法直接出现在 UIButton
提示的方法列表中。这有一个问题,一方面会给调用者造成困惑,另一方发面会存在同名的风险,造成方法污染👇:
第二种方式
写一个包裹器,将 UIButton 包裹起来,在包裹器中添加需要扩展的方法。
|
|
通过实现一个包裹器wrapper
,扩展的方法只会出现在wrapper
的调用提示方法列表里,UIButton
的调用提示方法列表里只新增了一个 wrapper,从而避免了对UIButton
的方法污染。
UIButtonWrapper
是针对UIButton
设计的一个包裹器,只适用于UIButton。可以借用Swift泛型与扩展约束的力量,实现一个泛型包裹器,从而实现为需要的任何类型扩展方法。
第三种方式
使用泛型和扩展约束,实现一个泛型包裹器:
|
|
通过泛型 AlamofireExtension<ExtendedType>
实现了对UIButton
和UILabel
的同时支持,扩展约束 where ExtendedType == UIButton
实现了对UIButton
和UILabel
扩展方法的区分,这种区分可以从调用提示方法列表中看出来:
UILabel
提示方法列表:
UIButton
提示方法列表:
通过UIButton
和UILabel
的扩展方式可以看出,在为类型扩展方法时,首先需要添加 af
计算属性来返回泛型包裹器AlamofireExtension<ExtendedType>
。
|
|
可以使用泛型协议和扩展的方式,添加默认实现,从而省略掉这一步。
第四种方式
使用泛型协议和扩展的,实现af
计算属性:
|
|
应用:
|
|
静态包裹器扩展了类型方法:
|
|
扩展的类型方法,也有对应的提示方法列表:
EventMonitor
protocol EventMonitor
协议概述了 Alamofire 生命周期里的内部事件。EventMonitor 既包括从各种 URLSession
委托协议接收的事件,也包括Request
及其子类生命周期内的各种事件。
|
|
实例代码中只展示了URLSessionDelegate
协议相关方法的声明,其它协议的声明省略。
在 EventMonitor
协议声明完方法后,还要对这些方法提供默认实现:
|
|
因为 EventMonitor
协议包含了 URLSession 和 Request 的各种事件,所以方法比较多。实现中什么也没做,只是提供了默认实现。
EventMonitor
协议就像一个显示器,完整且全面的展示了 Alamofire 内部发生的各种事件,透过它可以大概了解 Alamofire 都做了些什么。然而,EventMonitor
只是一个抽象类,在Alamofire的逻辑代码部分用到的并不是它,而是遵循了 EventMonitor
协议的类 CompositeEventMonitor
,负责处理 Alamofire 中的各种事件。
|
|
CompositeEventMonitor
CompositeEventMonitor
,负责处理 Alamofire 中的各种事件。从命名就可以看出,这是一个批量处理事件的“管理类”,可以称为事件监视器集合。
|
|
CompositeEventMonitor
遵循了 EventMonitor
协议,并且实现了全部方法。这里也只提供两个方法,其它方法省略。
CompositeEventMonitor
实现这些方法为了是使用队列来调度方法的调用,所以方法实现部分都是一样的,都调用了 performEvent
方法:
- 在 Session 和 Request 事件回调时,最终都会来到
performEvent
方法; - 依次取出数组里的事件监视器,放到同步队列里执行。
|
|
event(monitor)
调用了闭包,闭包内部实现👇,$0
是参数 monitor
:
|
|
defaultEventMonitors
在 Session 的初始化方法里👆🏻,eventMonitors 数组里包含两个部分,一个是默认的 defaultEventMonitors
,另一个是调用者添加的自定义事件监听器:
|
|
defaultEventMonitors
的定义:
|
|
AlamofireNotifications
提供Alamofire通知的事件监视器。
- 遵循 EventMonitor 协议;
- 实现需要监听的方法;
|
|
在 CompositeEventMonitor
的 performEvent
方法里,event(monitor)
会调用 AlamofireNotifications
,触发对应的方法。
ClosureEventMonitor
事件监视器——允许将可选闭包设置为接收事件。
|
|
1、CompositeEventMonitor 的 performEvent,获取到 ClosureEventMonitor,将事件放入队列中;
|
|
2、ClosureEventMonitor 调用实现的代理方法;
|
|
3、从而调用对应的闭包
|
|
Concurrency
async-await
|
|
打印结果:
|
|
增加测试数据:
|
|
打印结果:
|
|
使用 Task.sleep(nanoseconds:)
:
|
|
打印结果:
|
|
await Task.sleep(nanoseconds:)
方法有可能会调用失败,所以需要加 try
: